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 CHANGED
@@ -1,76 +1,56 @@
1
1
  # react-intl-number-input
2
- A React component for masked and formatted number input.
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
- - Node.js 18+ (recommended: latest LTS)
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
- class App extends React.Component {
41
- constructor(props) {
42
- super(props);
43
-
44
- this.state = {
45
- value: 0,
46
- maskedValue: 0,
47
- }
48
-
49
- this.handleChange = this.handleChange.bind(this);
50
- }
51
-
52
- handleChange(event, value, maskedValue) {
53
- event.preventDefault();
54
- this.setState({ value, maskedValue });
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("root"));
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("root"));
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
- ## Properties
94
-
95
- | Name | Type | Default Value | Description |
96
- | --- | --- | :---: | --- |
97
- | value | number or string | 0 | Initial numeric value |
98
- | locale | string | 'en-US' | BCP 47 language tag. Defines a language code and a country or region code.<br>[Intl Locale Identification](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#Locale_identification_and_negotiation) *e.g.* 'de-DE', 'pt-BR' and 'ja-JP' |
99
- | prefix | string | '' | String used as prefix for the masked value |
100
- | suffix | string | '' | String used as suffix for the masked value |
101
- | precision | number | 2 | Number of fraction digits to use |
102
- | onChange | function | n/a | `(event, value, maskedValue) => {}`<br>Callback function to handle value changes |
103
-
104
- ## Examples
105
-
106
- ```javascript
107
- // maskedValue: 1,234,567.89
108
- <IntlNumberInput />
109
- ```
110
-
111
- ```javascript
112
- // maskedValue: 12,345.6789
113
- <IntlNumberInput precision={4} />
114
- ```
115
-
116
- ```javascript
117
- // maskedValue: $1,234,567.89
118
- <IntlNumberInput prefix="$" />
119
- ```
120
-
121
- ```javascript
122
- // maskedValue: 1,234%
123
- <IntlNumberInput suffix="%" precision={0} />
124
- ```
125
-
126
- ```javascript
127
- // maskedValue: R$ 1.234.567,89
128
- <IntlNumberInput
129
- locale="pt-BR"
130
- prefix="R$ "
131
- precision={2}
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 * as React from 'react';
2
-
3
- export interface IntlNumberInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
4
- locale?: string;
5
- prefix?: string;
6
- suffix?: string;
7
- precision?: number;
8
- value?: number | string;
9
- onChange?: (event: React.ChangeEvent<HTMLInputElement>, value: number, maskedValue: string) => void;
10
- }
11
-
12
- declare const IntlNumberInput: React.ForwardRefExoticComponent<IntlNumberInputProps & React.RefAttributes<HTMLInputElement>>;
13
-
14
- export default IntlNumberInput;
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["default"] = void 0;
6
+ exports.default = void 0;
8
7
  var _react = _interopRequireWildcard(require("react"));
9
- var _propTypes = _interopRequireDefault(require("prop-types"));
10
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
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 ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
14
- function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
15
- function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
16
- function _readOnlyError(r) { throw new TypeError('"' + r + '" is read-only'); }
17
- function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
18
- function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
19
- function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
20
- function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
21
- function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
22
- function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
23
- function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
24
- function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
25
- function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
26
- function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
27
- function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
28
- function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
29
- function _possibleConstructorReturn(t, e) { if (e && ("object" == _typeof(e) || "function" == typeof e)) return e; if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined"); return _assertThisInitialized(t); }
30
- function _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return e; }
31
- function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
32
- function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); }
33
- function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, "prototype", { writable: !1 }), e && _setPrototypeOf(t, e); }
34
- function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); }
35
- var IntlNumberInput = /*#__PURE__*/function (_Component) {
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
- _inherits(IntlNumberInput, _Component);
47
- return _createClass(IntlNumberInput, [{
48
- key: "componentDidMount",
49
- value: function componentDidMount() {
50
- this.setMaskedValue(this.props.value);
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
- key: "componentDidUpdate",
54
- value: function componentDidUpdate(prevProps) {
55
- if (prevProps.value !== this.props.value) {
56
- this.setMaskedValue(this.props.value);
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
- key: "setMaskedValue",
61
- value: function setMaskedValue() {
62
- var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
63
- this.setState({
64
- maskedValue: this.formatNumber(value)
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
- key: "getMaskedValue",
69
- value: function getMaskedValue() {
70
- return this.state.maskedValue;
101
+ if (!isValidNumber(numberValue)) {
102
+ numberValue = 0;
71
103
  }
72
- }, {
73
- key: "getValue",
74
- value: function getValue() {
75
- return this.state.value;
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
- key: "getPrecisionValue",
79
- value: function getPrecisionValue(precision) {
80
- return Math.pow(10, precision);
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
- key: "getNumberValue",
84
- value: function getNumberValue(strValue) {
85
- var numberValue = Number(strValue.replace(/[^0-9]/g, ''));
86
- return numberValue / this.getPrecisionValue(this.props.precision);
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
- key: "formatNumber",
90
- value: function formatNumber() {
91
- var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
92
- var numberValue = value;
93
- if (typeof numberValue === 'string') {
94
- numberValue = this.getNumberValue(numberValue);
95
- }
96
- var formattedNumber = new Intl.NumberFormat(this.props.locale, {
97
- style: 'decimal',
98
- minimumFractionDigits: this.props.precision,
99
- maximumFractionDigits: this.props.precision
100
- }).format(numberValue);
101
- return "".concat(this.props.prefix).concat(formattedNumber).concat(this.props.suffix);
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
- key: "updateValues",
105
- value: function updateValues(event) {
106
- var value = this.getNumberValue(event.target.value);
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
- key: "handleChange",
112
- value: function handleChange(event) {
113
- var _this2 = this;
114
- event.preventDefault();
115
- var _this$updateValues = this.updateValues(event),
116
- _this$updateValues2 = _slicedToArray(_this$updateValues, 2),
117
- value = _this$updateValues2[0],
118
- maskedValue = _this$updateValues2[1];
119
- event.persist();
120
- if (this.props.onChange && maskedValue) {
121
- this.setState({
122
- value: value,
123
- maskedValue: maskedValue
124
- }, function () {
125
- _this2.props.onChange(event, value, maskedValue);
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
- key: "customProps",
131
- value: function customProps() {
132
- var customProps = _objectSpread({}, this.props);
133
- delete customProps.locale;
134
- delete customProps.prefix;
135
- delete customProps.suffix;
136
- delete customProps.precision;
137
- delete customProps.autoFocus;
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
- key: "render",
144
- value: function render() {
145
- var _this3 = this;
146
- return /*#__PURE__*/_react["default"].createElement("input", _extends({
147
- value: this.state.maskedValue,
148
- disabled: this.props.disabled,
149
- onChange: function onChange(e) {
150
- return _this3.handleChange(e);
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
- }(_react.Component);
156
- IntlNumberInput.propTypes = {
157
- locale: _propTypes["default"].string.isRequired,
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.5.1",
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 --copy-files --no-copy-ignored --ignore \"**/*.test.jsx\"",
10
- "prepublishOnly": "npm run transpile",
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.27.2",
45
- "@babel/core": "^7.27.4",
46
- "@babel/plugin-transform-object-rest-spread": "^7.28.4",
47
- "@babel/preset-env": "^7.29.5",
48
- "@babel/preset-react": "^7.27.1",
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.0",
51
- "babel-jest": "^30.2.0",
52
- "babel-loader": "^10.0.0",
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.4",
55
- "jest": "^30.2.0",
56
- "jest-environment-jsdom": "^30.2.0",
57
- "react": "^19.2.0",
58
- "react-dom": "^19.2.0",
59
- "webpack": "^5.101.3",
60
- "webpack-cli": "^6.0.1",
61
- "webpack-dev-server": "^5.2.4"
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
@@ -1,6 +0,0 @@
1
- module.exports = {
2
- testEnvironment: "jsdom",
3
- setupFilesAfterEnv: ["<rootDir>/jest.setup.js"],
4
- moduleFileExtensions: ["js", "jsx"],
5
- testMatch: ["**/*.test.jsx"]
6
- };
package/jest.setup.js DELETED
@@ -1 +0,0 @@
1
- import "@testing-library/jest-dom";