react-intl-number-input 0.5.1 → 0.7.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/README.md +123 -99
- package/dist/index.d.ts +18 -14
- package/dist/index.js +187 -151
- package/package.json +46 -25
- package/jest.config.cjs +0 -6
- package/jest.setup.js +0 -1
package/README.md
CHANGED
|
@@ -1,76 +1,56 @@
|
|
|
1
1
|
# react-intl-number-input
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
A React component for masked and formatted number input with `Intl.NumberFormat` locale support.
|
|
3
4
|
|
|
4
5
|
[Demo](https://nandorip.github.io/react-intl-number-input/example/dist/)
|
|
5
6
|
|
|
6
7
|
## Requirements
|
|
7
8
|
|
|
8
|
-
-
|
|
9
|
-
- React / ReactDOM: `>=16.8.0 <20`
|
|
9
|
+
- React: `>=16.8.0 <20`
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
|
-
```
|
|
13
|
+
```bash
|
|
14
14
|
npm install react-intl-number-input
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
## Development
|
|
18
|
-
|
|
19
|
-
```
|
|
20
|
-
npm install
|
|
21
|
-
npm start
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
Open `http://localhost:3001`.
|
|
25
|
-
|
|
26
|
-
## Scripts
|
|
27
|
-
|
|
28
|
-
- `npm start`: runs webpack dev server
|
|
29
|
-
- `npm run build`: builds the demo bundle in `example/dist`
|
|
30
|
-
- `npm run transpile`: transpiles library source from `src` to `dist`
|
|
31
|
-
- `npm test`: runs unit tests with Jest
|
|
32
|
-
- `npm run publish-demo`: builds and deploys demo to GitHub Pages
|
|
33
|
-
|
|
34
17
|
## Usage
|
|
35
18
|
|
|
36
19
|
```javascript
|
|
37
|
-
import React from 'react'
|
|
20
|
+
import React, { useState } from 'react';
|
|
38
21
|
import IntlNumberInput from 'react-intl-number-input';
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
render() {
|
|
58
|
-
return (
|
|
59
|
-
<div>
|
|
60
|
-
<p>
|
|
61
|
-
<IntlNumberInput onChange={this.handleChange} />
|
|
62
|
-
</p>
|
|
63
|
-
<p>
|
|
64
|
-
value: {this.state.value}
|
|
65
|
-
</p>
|
|
66
|
-
<p>
|
|
67
|
-
maskedValue: {this.state.maskedValue}
|
|
68
|
-
</p>
|
|
69
|
-
</div>
|
|
70
|
-
);
|
|
71
|
-
}
|
|
22
|
+
|
|
23
|
+
function App() {
|
|
24
|
+
const [value, setValue] = useState(0);
|
|
25
|
+
const [maskedValue, setMaskedValue] = useState('0.00');
|
|
26
|
+
|
|
27
|
+
const handleChange = (event, nextValue, nextMaskedValue) => {
|
|
28
|
+
setValue(nextValue);
|
|
29
|
+
setMaskedValue(nextMaskedValue);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div>
|
|
34
|
+
<IntlNumberInput onChange={handleChange} />
|
|
35
|
+
<p>value: {value}</p>
|
|
36
|
+
<p>maskedValue: {maskedValue}</p>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
72
39
|
}
|
|
40
|
+
```
|
|
73
41
|
|
|
42
|
+
### TypeScript
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import IntlNumberInput, { IntlNumberInputProps } from 'react-intl-number-input';
|
|
46
|
+
|
|
47
|
+
const props: IntlNumberInputProps = {
|
|
48
|
+
locale: 'pt-BR',
|
|
49
|
+
precision: 2,
|
|
50
|
+
onChange: (event, value, maskedValue) => {
|
|
51
|
+
console.log(value, maskedValue);
|
|
52
|
+
},
|
|
53
|
+
};
|
|
74
54
|
```
|
|
75
55
|
|
|
76
56
|
React 18+:
|
|
@@ -78,7 +58,7 @@ React 18+:
|
|
|
78
58
|
```javascript
|
|
79
59
|
import { createRoot } from 'react-dom/client';
|
|
80
60
|
|
|
81
|
-
const root = createRoot(document.getElementById(
|
|
61
|
+
const root = createRoot(document.getElementById('root'));
|
|
82
62
|
root.render(<App />);
|
|
83
63
|
```
|
|
84
64
|
|
|
@@ -87,47 +67,91 @@ React 16/17:
|
|
|
87
67
|
```javascript
|
|
88
68
|
import ReactDOM from 'react-dom';
|
|
89
69
|
|
|
90
|
-
ReactDOM.render(<App />, document.getElementById(
|
|
70
|
+
ReactDOM.render(<App />, document.getElementById('root'));
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Properties
|
|
74
|
+
|
|
75
|
+
| Name | Type | Default | Description |
|
|
76
|
+
| --- | --- | :---: | --- |
|
|
77
|
+
| value | `number` \| `string` | `0` | Controlled numeric value |
|
|
78
|
+
| 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)) |
|
|
79
|
+
| prefix | `string` | `''` | Prefix shown in the masked value |
|
|
80
|
+
| suffix | `string` | `''` | Suffix shown in the masked value |
|
|
81
|
+
| precision | `number` | `2` | Fraction digits (clamped to `0`–`20`) |
|
|
82
|
+
| onChange | `function` | — | `(event, value, maskedValue) => void` |
|
|
83
|
+
| onBlur | `function` | — | `(event, value, maskedValue) => void` |
|
|
84
|
+
| disabled | `boolean` | `false` | Disables the input |
|
|
85
|
+
| autoFocus | `boolean` | — | Native input autofocus |
|
|
86
|
+
| minValue | `number` | — | Minimum allowed value |
|
|
87
|
+
| maxValue | `number` | — | Maximum allowed value |
|
|
88
|
+
| showStepButtons | `boolean` | `false` | Renders +/- step buttons |
|
|
89
|
+
| step | `number` | `1` | Step increment for buttons |
|
|
90
|
+
| inputMode | `'numeric'` \| `'decimal'` | auto | Overrides mobile keyboard mode |
|
|
91
|
+
| className | `string` | — | Input CSS class |
|
|
92
|
+
| style | `object` | — | Input inline styles |
|
|
93
|
+
| id | `string` | — | Input id |
|
|
94
|
+
| name | `string` | — | Input name |
|
|
95
|
+
| placeholder | `string` | — | Input placeholder |
|
|
96
|
+
| readOnly | `boolean` | — | Read-only input |
|
|
97
|
+
| required | `boolean` | — | Required input |
|
|
98
|
+
| tabIndex | `number` | — | Input tab index |
|
|
99
|
+
|
|
100
|
+
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.
|
|
101
|
+
|
|
102
|
+
## Examples
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
// maskedValue: 1,234,567.89
|
|
106
|
+
<IntlNumberInput />
|
|
91
107
|
```
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
// maskedValue: 12,345.6789
|
|
111
|
+
<IntlNumberInput precision={4} />
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
// maskedValue: $1,234,567.89
|
|
116
|
+
<IntlNumberInput prefix="$" />
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
// maskedValue: 1,234%
|
|
121
|
+
<IntlNumberInput suffix="%" precision={0} />
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
```javascript
|
|
125
|
+
// maskedValue: 12.50% — type digits only; decimal is implied by precision
|
|
126
|
+
<IntlNumberInput suffix="%" precision={2} />
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
```javascript
|
|
130
|
+
// maskedValue: R$ 1.234.567,89
|
|
131
|
+
<IntlNumberInput locale="pt-BR" prefix="R$ " precision={2} />
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```javascript
|
|
135
|
+
// With min/max and step buttons
|
|
136
|
+
<IntlNumberInput
|
|
137
|
+
value={50}
|
|
138
|
+
minValue={0}
|
|
139
|
+
maxValue={100}
|
|
140
|
+
step={5}
|
|
141
|
+
precision={0}
|
|
142
|
+
showStepButtons
|
|
143
|
+
onChange={(event, value) => console.log(value)}
|
|
144
|
+
/>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## How input works
|
|
148
|
+
|
|
149
|
+
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.
|
|
150
|
+
|
|
151
|
+
## Contributing
|
|
152
|
+
|
|
153
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, testing, and publishing instructions.
|
|
154
|
+
|
|
155
|
+
## Changelog
|
|
156
|
+
|
|
157
|
+
See [CHANGELOG.md](CHANGELOG.md) for release history.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
import React from 'react';
|
|
2
|
+
type IntlNumberInputOwnProps = {
|
|
3
|
+
locale?: string;
|
|
4
|
+
prefix?: string;
|
|
5
|
+
suffix?: string;
|
|
6
|
+
precision?: number;
|
|
7
|
+
value?: number | string;
|
|
8
|
+
showStepButtons?: boolean;
|
|
9
|
+
step?: number;
|
|
10
|
+
minValue?: number;
|
|
11
|
+
maxValue?: number;
|
|
12
|
+
inputMode?: 'numeric' | 'decimal';
|
|
13
|
+
onChange?: (event: React.ChangeEvent<HTMLInputElement>, value: number, maskedValue: string) => void;
|
|
14
|
+
onBlur?: (event: React.FocusEvent<HTMLInputElement>, value: number, maskedValue: string) => void;
|
|
15
|
+
};
|
|
16
|
+
export type IntlNumberInputProps = IntlNumberInputOwnProps & Omit<React.ComponentPropsWithoutRef<'input'>, keyof IntlNumberInputOwnProps | 'value' | 'type' | 'defaultValue' | 'children'>;
|
|
17
|
+
declare const _default: React.NamedExoticComponent<IntlNumberInputOwnProps & Omit<Omit<React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "ref">, "type" | "defaultValue" | "children" | keyof IntlNumberInputOwnProps> & React.RefAttributes<HTMLInputElement>>;
|
|
18
|
+
export default _default;
|
package/dist/index.js
CHANGED
|
@@ -1,171 +1,207 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
4
3
|
Object.defineProperty(exports, "__esModule", {
|
|
5
4
|
value: true
|
|
6
5
|
});
|
|
7
|
-
exports
|
|
6
|
+
exports.default = void 0;
|
|
8
7
|
var _react = _interopRequireWildcard(require("react"));
|
|
9
|
-
|
|
10
|
-
function
|
|
11
|
-
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
|
|
8
|
+
const _excluded = ["locale", "prefix", "suffix", "precision", "value", "showStepButtons", "step", "minValue", "maxValue", "inputMode", "onChange", "onBlur", "disabled"];
|
|
9
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
12
10
|
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
13
|
-
function
|
|
14
|
-
function
|
|
15
|
-
function
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
function
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
function IntlNumberInput(props) {
|
|
37
|
-
var _this;
|
|
38
|
-
_classCallCheck(this, IntlNumberInput);
|
|
39
|
-
_this = _callSuper(this, IntlNumberInput, [props]);
|
|
40
|
-
_this.state = {
|
|
41
|
-
maskedValue: '0',
|
|
42
|
-
value: 0
|
|
43
|
-
};
|
|
44
|
-
return _this;
|
|
11
|
+
function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; }
|
|
12
|
+
function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
|
|
13
|
+
function sanitizeString(str) {
|
|
14
|
+
return str.replace(/<[^>]*>/g, '');
|
|
15
|
+
}
|
|
16
|
+
function resolveLocale(locale) {
|
|
17
|
+
try {
|
|
18
|
+
const supported = Intl.NumberFormat.supportedLocalesOf([locale]);
|
|
19
|
+
return supported.length > 0 ? supported[0] : 'en-US';
|
|
20
|
+
} catch {
|
|
21
|
+
return 'en-US';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function isValidNumber(value) {
|
|
25
|
+
return Number.isFinite(value) && !Number.isNaN(value);
|
|
26
|
+
}
|
|
27
|
+
function clamp(value, min, max) {
|
|
28
|
+
return Math.min(Math.max(value, min), max);
|
|
29
|
+
}
|
|
30
|
+
function clampToBounds(value, min, max) {
|
|
31
|
+
let result = value;
|
|
32
|
+
if (min !== undefined && result < min) {
|
|
33
|
+
result = min;
|
|
45
34
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
35
|
+
if (max !== undefined && result > max) {
|
|
36
|
+
result = max;
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
const IntlNumberInput = /*#__PURE__*/(0, _react.forwardRef)(function IntlNumberInput(_ref, ref) {
|
|
41
|
+
let {
|
|
42
|
+
locale = 'en-US',
|
|
43
|
+
prefix = '',
|
|
44
|
+
suffix = '',
|
|
45
|
+
precision = 2,
|
|
46
|
+
value,
|
|
47
|
+
showStepButtons = false,
|
|
48
|
+
step = 1,
|
|
49
|
+
minValue,
|
|
50
|
+
maxValue,
|
|
51
|
+
inputMode: inputModeProp,
|
|
52
|
+
onChange,
|
|
53
|
+
onBlur,
|
|
54
|
+
disabled = false
|
|
55
|
+
} = _ref,
|
|
56
|
+
inputProps = _objectWithoutProperties(_ref, _excluded);
|
|
57
|
+
const safePrecision = (0, _react.useMemo)(() => {
|
|
58
|
+
if (typeof precision !== 'number' || Number.isNaN(precision)) return 2;
|
|
59
|
+
return clamp(Math.round(precision), 0, 20);
|
|
60
|
+
}, [precision]);
|
|
61
|
+
const safeStep = (0, _react.useMemo)(() => {
|
|
62
|
+
if (typeof step !== 'number' || Number.isNaN(step) || step <= 0) return 1;
|
|
63
|
+
return step;
|
|
64
|
+
}, [step]);
|
|
65
|
+
const [effectiveMin, effectiveMax] = (0, _react.useMemo)(() => {
|
|
66
|
+
if (minValue !== undefined && maxValue !== undefined && minValue > maxValue) {
|
|
67
|
+
return [maxValue, minValue];
|
|
51
68
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
69
|
+
return [minValue, maxValue];
|
|
70
|
+
}, [minValue, maxValue]);
|
|
71
|
+
const getPrecisionValue = (0, _react.useCallback)(p => {
|
|
72
|
+
return Math.pow(10, p);
|
|
73
|
+
}, []);
|
|
74
|
+
const getNumberValue = (0, _react.useCallback)(strValue => {
|
|
75
|
+
const inputValue = String(strValue || '');
|
|
76
|
+
const hasNegativeSign = inputValue.includes('-');
|
|
77
|
+
const onlyDigits = inputValue.replace(/[^0-9]/g, '');
|
|
78
|
+
if (!onlyDigits) {
|
|
79
|
+
return hasNegativeSign ? -0 : 0;
|
|
58
80
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
81
|
+
const numberValue = Number(onlyDigits);
|
|
82
|
+
const parsedValue = numberValue / getPrecisionValue(safePrecision);
|
|
83
|
+
return hasNegativeSign ? -parsedValue : parsedValue;
|
|
84
|
+
}, [getPrecisionValue, safePrecision]);
|
|
85
|
+
const numberFormatter = (0, _react.useMemo)(() => {
|
|
86
|
+
const options = {
|
|
87
|
+
style: 'decimal',
|
|
88
|
+
minimumFractionDigits: safePrecision,
|
|
89
|
+
maximumFractionDigits: safePrecision
|
|
90
|
+
};
|
|
91
|
+
const resolvedLocale = resolveLocale(locale);
|
|
92
|
+
return new Intl.NumberFormat(resolvedLocale, options);
|
|
93
|
+
}, [locale, safePrecision]);
|
|
94
|
+
const formatNumber = (0, _react.useCallback)(val => {
|
|
95
|
+
let numberValue;
|
|
96
|
+
if (typeof val === 'string') {
|
|
97
|
+
numberValue = getNumberValue(val);
|
|
98
|
+
} else {
|
|
99
|
+
numberValue = val;
|
|
66
100
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
value: function getMaskedValue() {
|
|
70
|
-
return this.state.maskedValue;
|
|
101
|
+
if (!isValidNumber(numberValue)) {
|
|
102
|
+
numberValue = 0;
|
|
71
103
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
104
|
+
numberValue = clampToBounds(numberValue, effectiveMin, effectiveMax);
|
|
105
|
+
const formattedNumber = numberFormatter.format(numberValue);
|
|
106
|
+
const safePrefix = sanitizeString(prefix);
|
|
107
|
+
const safeSuffix = sanitizeString(suffix);
|
|
108
|
+
return `${safePrefix}${formattedNumber}${safeSuffix}`;
|
|
109
|
+
}, [getNumberValue, numberFormatter, prefix, suffix, effectiveMin, effectiveMax]);
|
|
110
|
+
const getInitialValue = (0, _react.useCallback)(() => {
|
|
111
|
+
const initial = value !== undefined ? value : 0;
|
|
112
|
+
return formatNumber(initial);
|
|
113
|
+
}, [value, formatNumber]);
|
|
114
|
+
const [maskedValue, setMaskedValue] = (0, _react.useState)(getInitialValue);
|
|
115
|
+
(0, _react.useEffect)(() => {
|
|
116
|
+
setMaskedValue(formatNumber(value ?? 0));
|
|
117
|
+
}, [value, formatNumber]);
|
|
118
|
+
const handleChange = (0, _react.useCallback)(event => {
|
|
119
|
+
const rawValue = event.target.value;
|
|
120
|
+
const newValue = getNumberValue(rawValue);
|
|
121
|
+
if (!isValidNumber(newValue)) {
|
|
122
|
+
return;
|
|
76
123
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
124
|
+
const clampedValue = clampToBounds(newValue, effectiveMin, effectiveMax);
|
|
125
|
+
const newMaskedValue = formatNumber(clampedValue);
|
|
126
|
+
setMaskedValue(newMaskedValue);
|
|
127
|
+
if (onChange) {
|
|
128
|
+
onChange(event, clampedValue, newMaskedValue);
|
|
81
129
|
}
|
|
82
|
-
},
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
130
|
+
}, [getNumberValue, formatNumber, effectiveMin, effectiveMax, onChange]);
|
|
131
|
+
const handleBlur = (0, _react.useCallback)(event => {
|
|
132
|
+
const rawValue = event.target.value;
|
|
133
|
+
const currentValue = getNumberValue(rawValue);
|
|
134
|
+
const clampedValue = clampToBounds(currentValue, effectiveMin, effectiveMax);
|
|
135
|
+
const formattedValue = formatNumber(clampedValue);
|
|
136
|
+
setMaskedValue(formattedValue);
|
|
137
|
+
if (onBlur) {
|
|
138
|
+
onBlur(event, clampedValue, formattedValue);
|
|
87
139
|
}
|
|
88
|
-
},
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
140
|
+
}, [getNumberValue, formatNumber, effectiveMin, effectiveMax, onBlur]);
|
|
141
|
+
const handleStep = (0, _react.useCallback)(delta => {
|
|
142
|
+
if (disabled) return;
|
|
143
|
+
const currentValue = getNumberValue(maskedValue);
|
|
144
|
+
const stepValue = safeStep * Math.pow(10, safePrecision);
|
|
145
|
+
const newValue = clampToBounds(currentValue + delta * stepValue, effectiveMin, effectiveMax);
|
|
146
|
+
const newMaskedValue = formatNumber(newValue);
|
|
147
|
+
setMaskedValue(newMaskedValue);
|
|
148
|
+
const syntheticEvent = {
|
|
149
|
+
target: {
|
|
150
|
+
value: newMaskedValue,
|
|
151
|
+
name: inputProps.name || '',
|
|
152
|
+
id: inputProps.id || ''
|
|
153
|
+
},
|
|
154
|
+
preventDefault: () => {}
|
|
155
|
+
};
|
|
156
|
+
if (onChange) {
|
|
157
|
+
onChange(syntheticEvent, newValue, newMaskedValue);
|
|
102
158
|
}
|
|
103
|
-
},
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
var maskedValue = this.formatNumber(value);
|
|
108
|
-
return [value, maskedValue];
|
|
159
|
+
}, [disabled, getNumberValue, maskedValue, safeStep, safePrecision, effectiveMin, effectiveMax, formatNumber, onChange, inputProps.name, inputProps.id]);
|
|
160
|
+
const inputMode = (0, _react.useMemo)(() => {
|
|
161
|
+
if (inputModeProp) {
|
|
162
|
+
return inputModeProp;
|
|
109
163
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
164
|
+
return safePrecision > 0 ? 'decimal' : 'numeric';
|
|
165
|
+
}, [inputModeProp, safePrecision]);
|
|
166
|
+
const inputElement = /*#__PURE__*/_react.default.createElement("input", _extends({
|
|
167
|
+
ref: ref,
|
|
168
|
+
type: "text",
|
|
169
|
+
inputMode: inputMode,
|
|
170
|
+
value: maskedValue,
|
|
171
|
+
disabled: disabled,
|
|
172
|
+
onChange: handleChange,
|
|
173
|
+
onBlur: handleBlur
|
|
174
|
+
}, inputProps));
|
|
175
|
+
if (!showStepButtons) {
|
|
176
|
+
return inputElement;
|
|
177
|
+
}
|
|
178
|
+
return /*#__PURE__*/_react.default.createElement("div", {
|
|
179
|
+
style: {
|
|
180
|
+
display: 'inline-flex',
|
|
181
|
+
alignItems: 'center',
|
|
182
|
+
gap: '4px'
|
|
128
183
|
}
|
|
129
|
-
}, {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
delete customProps.value;
|
|
139
|
-
delete customProps.onChange;
|
|
140
|
-
return customProps;
|
|
184
|
+
}, /*#__PURE__*/_react.default.createElement("button", {
|
|
185
|
+
type: "button",
|
|
186
|
+
onClick: () => handleStep(-1),
|
|
187
|
+
disabled: disabled,
|
|
188
|
+
"aria-label": "Decrease value",
|
|
189
|
+
style: {
|
|
190
|
+
padding: '4px 8px',
|
|
191
|
+
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
192
|
+
opacity: disabled ? 0.5 : 1
|
|
141
193
|
}
|
|
142
|
-
}, {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
}, this.customProps()));
|
|
194
|
+
}, "-"), inputElement, /*#__PURE__*/_react.default.createElement("button", {
|
|
195
|
+
type: "button",
|
|
196
|
+
onClick: () => handleStep(1),
|
|
197
|
+
disabled: disabled,
|
|
198
|
+
"aria-label": "Increase value",
|
|
199
|
+
style: {
|
|
200
|
+
padding: '4px 8px',
|
|
201
|
+
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
202
|
+
opacity: disabled ? 0.5 : 1
|
|
153
203
|
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
IntlNumberInput.
|
|
157
|
-
|
|
158
|
-
prefix: _propTypes["default"].string.isRequired,
|
|
159
|
-
suffix: _propTypes["default"].string.isRequired,
|
|
160
|
-
precision: _propTypes["default"].number.isRequired,
|
|
161
|
-
value: _propTypes["default"].oneOfType([_propTypes["default"].number, _propTypes["default"].string]),
|
|
162
|
-
disabled: _propTypes["default"].bool,
|
|
163
|
-
onChange: _propTypes["default"].func
|
|
164
|
-
};
|
|
165
|
-
IntlNumberInput.defaultProps = {
|
|
166
|
-
locale: 'en-US',
|
|
167
|
-
prefix: '',
|
|
168
|
-
suffix: '',
|
|
169
|
-
precision: 2
|
|
170
|
-
};
|
|
171
|
-
var _default = exports["default"] = IntlNumberInput;
|
|
204
|
+
}, "+"));
|
|
205
|
+
});
|
|
206
|
+
IntlNumberInput.displayName = 'IntlNumberInput';
|
|
207
|
+
var _default = exports.default = /*#__PURE__*/(0, _react.memo)(IntlNumberInput);
|
package/package.json
CHANGED
|
@@ -1,13 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-intl-number-input",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "A React component for masked and formatted number input.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"require": "./dist/index.js",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"browserslist": [
|
|
15
|
+
"> 0.5%",
|
|
16
|
+
"last 2 versions",
|
|
17
|
+
"not dead"
|
|
18
|
+
],
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
24
|
+
"sideEffects": false,
|
|
6
25
|
"scripts": {
|
|
7
26
|
"test": "jest --runInBand",
|
|
8
27
|
"start": "webpack serve --mode development",
|
|
9
|
-
"transpile": "babel src -d dist --
|
|
10
|
-
"
|
|
28
|
+
"transpile": "babel src -d dist --extensions \".ts,.tsx\" --ignore \"**/*.test.ts\" --ignore \"**/*.test.tsx\"",
|
|
29
|
+
"build:types": "tsc --emitDeclarationOnly",
|
|
30
|
+
"prepublishOnly": "npm test && npm run transpile && npm run build:types",
|
|
11
31
|
"build": "webpack --mode production",
|
|
12
32
|
"deploy": "gh-pages -d example/dist",
|
|
13
33
|
"publish-demo": "npm run build && npm run deploy"
|
|
@@ -25,7 +45,8 @@
|
|
|
25
45
|
"money",
|
|
26
46
|
"number",
|
|
27
47
|
"intl",
|
|
28
|
-
"input"
|
|
48
|
+
"input",
|
|
49
|
+
"typescript"
|
|
29
50
|
],
|
|
30
51
|
"author": "Fernando Pires Reherman <nandorip@live.com>",
|
|
31
52
|
"license": "MIT",
|
|
@@ -34,35 +55,35 @@
|
|
|
34
55
|
},
|
|
35
56
|
"homepage": "https://github.com/nandorip/react-intl-number-input#readme",
|
|
36
57
|
"peerDependencies": {
|
|
37
|
-
"react": ">=16.8.0 <20"
|
|
38
|
-
"react-dom": ">=16.8.0 <20"
|
|
39
|
-
},
|
|
40
|
-
"dependencies": {
|
|
41
|
-
"prop-types": "^15.8.1"
|
|
58
|
+
"react": ">=16.8.0 <20"
|
|
42
59
|
},
|
|
43
60
|
"devDependencies": {
|
|
44
|
-
"@babel/cli": "^7.
|
|
45
|
-
"@babel/core": "^7.
|
|
46
|
-
"@babel/plugin-transform-object-rest-spread": "^7.
|
|
47
|
-
"@babel/preset-env": "^7.29.
|
|
48
|
-
"@babel/preset-react": "^7.
|
|
61
|
+
"@babel/cli": "^7.29.7",
|
|
62
|
+
"@babel/core": "^7.29.7",
|
|
63
|
+
"@babel/plugin-transform-object-rest-spread": "^7.29.7",
|
|
64
|
+
"@babel/preset-env": "^7.29.7",
|
|
65
|
+
"@babel/preset-react": "^7.29.7",
|
|
66
|
+
"@babel/preset-typescript": "^7.29.7",
|
|
49
67
|
"@testing-library/jest-dom": "^6.9.1",
|
|
50
|
-
"@testing-library/react": "^16.3.
|
|
51
|
-
"
|
|
52
|
-
"babel-
|
|
68
|
+
"@testing-library/react": "^16.3.2",
|
|
69
|
+
"@types/react": "^19.2.17",
|
|
70
|
+
"babel-jest": "^30.4.1",
|
|
71
|
+
"babel-loader": "^10.1.1",
|
|
53
72
|
"gh-pages": "^6.3.0",
|
|
54
|
-
"html-webpack-plugin": "^5.6.
|
|
55
|
-
"jest": "^30.2
|
|
56
|
-
"jest-environment-jsdom": "^30.
|
|
57
|
-
"react": "^19.2.
|
|
58
|
-
"react-dom": "^19.2.
|
|
59
|
-
"
|
|
60
|
-
"webpack
|
|
61
|
-
"webpack-
|
|
73
|
+
"html-webpack-plugin": "^5.6.7",
|
|
74
|
+
"jest": "^30.4.2",
|
|
75
|
+
"jest-environment-jsdom": "^30.4.1",
|
|
76
|
+
"react": "^19.2.7",
|
|
77
|
+
"react-dom": "^19.2.7",
|
|
78
|
+
"typescript": "^6.0.3",
|
|
79
|
+
"webpack": "^5.107.2",
|
|
80
|
+
"webpack-cli": "^7.0.3",
|
|
81
|
+
"webpack-dev-server": "^5.2.5"
|
|
62
82
|
},
|
|
63
83
|
"overrides": {
|
|
64
84
|
"fast-uri": "^3.1.1",
|
|
65
85
|
"follow-redirects": "^1.16.0",
|
|
86
|
+
"js-yaml": "^4.1.1",
|
|
66
87
|
"uuid": "^11.1.1"
|
|
67
88
|
}
|
|
68
89
|
}
|
package/jest.config.cjs
DELETED
package/jest.setup.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import "@testing-library/jest-dom";
|