react-currency-input
Advanced tools
| var path = require('path'); | ||
| var webpack = require('webpack'); | ||
| module.exports = { | ||
| entry: './examples/index.js', | ||
| output: { | ||
| path: path.resolve(__dirname, 'examples'), | ||
| filename: 'bundle.js' | ||
| }, | ||
| module: { | ||
| loaders: [ | ||
| { | ||
| test: /\.js$/, | ||
| loader: 'babel-loader', | ||
| query: { | ||
| presets: ["es2015", "react", "stage-3"] | ||
| } | ||
| } | ||
| ] | ||
| }, | ||
| plugins: [ | ||
| new webpack.HotModuleReplacementPlugin() | ||
| ], | ||
| devServer: { | ||
| hot: true, | ||
| contentBase: './examples' | ||
| }, | ||
| stats: { | ||
| colors: true | ||
| }, | ||
| devtool: 'source-map' | ||
| }; |
+245
-136
@@ -9,2 +9,8 @@ 'use strict'; | ||
| var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | ||
| var _propTypes = require('prop-types'); | ||
| var _propTypes2 = _interopRequireDefault(_propTypes); | ||
| var _react = require('react'); | ||
@@ -14,2 +20,6 @@ | ||
| var _reactDom = require('react-dom'); | ||
| var _reactDom2 = _interopRequireDefault(_reactDom); | ||
| var _mask3 = require('./mask.js'); | ||
@@ -21,174 +31,273 @@ | ||
| var CurrencyInput = _react2.default.createClass({ | ||
| displayName: 'CurrencyInput', | ||
| function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
| function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } | ||
| /** | ||
| * Prop validation. | ||
| * @see https://facebook.github.io/react/docs/component-specs.html#proptypes | ||
| */ | ||
| propTypes: { | ||
| onChange: _react.PropTypes.func, | ||
| value: _react.PropTypes.oneOfType([_react.PropTypes.number, _react.PropTypes.string]), | ||
| decimalSeparator: _react.PropTypes.string, | ||
| thousandSeparator: _react.PropTypes.string, | ||
| precision: _react.PropTypes.oneOfType([_react.PropTypes.number, _react.PropTypes.string]), | ||
| inputType: _react.PropTypes.string, | ||
| allowNegative: _react.PropTypes.bool, | ||
| allowEmpty: _react.PropTypes.bool, | ||
| prefix: _react.PropTypes.string, | ||
| suffix: _react.PropTypes.string | ||
| }, | ||
| function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } | ||
| // IE* parseFloat polyfill | ||
| // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/parseFloat#Polyfill | ||
| Number.parseFloat = parseFloat; | ||
| var CurrencyInput = function (_Component) { | ||
| _inherits(CurrencyInput, _Component); | ||
| function CurrencyInput(props) { | ||
| _classCallCheck(this, CurrencyInput); | ||
| var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(CurrencyInput).call(this, props)); | ||
| _this.prepareProps = _this.prepareProps.bind(_this); | ||
| _this.handleChange = _this.handleChange.bind(_this); | ||
| _this.state = _this.prepareProps(_this.props); | ||
| return _this; | ||
| } | ||
| /** | ||
| * Component lifecycle function. | ||
| * Exposes the current masked value. | ||
| * | ||
| * Invoked once and cached when the class is created. Values in the mapping will be set on this.props if that | ||
| * prop is not specified by the parent component | ||
| * | ||
| * @see https://facebook.github.io/react/docs/component-specs.html#getdefaultprops | ||
| * @returns {String} | ||
| */ | ||
| getDefaultProps: function getDefaultProps() { | ||
| return { | ||
| onChange: function onChange(maskValue, value, event) {/*no-op*/}, | ||
| value: '0', | ||
| decimalSeparator: '.', | ||
| thousandSeparator: ',', | ||
| precision: '2', | ||
| inputType: 'text', | ||
| allowNegative: false, | ||
| prefix: '', | ||
| suffix: '' | ||
| }; | ||
| }, | ||
| /** | ||
| * General function used to cleanup and define the final props used for rendering | ||
| * @returns {{ maskedValue: {String}, value: {Number}, customProps: {Object} }} | ||
| */ | ||
| prepareProps: function prepareProps(props) { | ||
| var customProps = _extends({}, props); // babeljs converts to Object.assign, then polyfills. | ||
| delete customProps.onChange; | ||
| delete customProps.value; | ||
| delete customProps.decimalSeparator; | ||
| delete customProps.thousandSeparator; | ||
| delete customProps.precision; | ||
| delete customProps.inputType; | ||
| delete customProps.allowNegative; | ||
| delete customProps.allowEmpty; | ||
| delete customProps.prefix; | ||
| delete customProps.suffix; | ||
| _createClass(CurrencyInput, [{ | ||
| key: 'getMaskedValue', | ||
| value: function getMaskedValue() { | ||
| return this.state.maskedValue; | ||
| } | ||
| var initialValue = props.value; | ||
| if (!initialValue) { | ||
| initialValue = props.allowEmpty ? null : ''; | ||
| } else { | ||
| /** | ||
| * General function used to cleanup and define the final props used for rendering | ||
| * @returns {{ maskedValue: {String}, value: {Number}, customProps: {Object} }} | ||
| */ | ||
| if (typeof initialValue == 'string') { | ||
| // Some people, when confronted with a problem, think "I know, I'll use regular expressions." | ||
| // Now they have two problems. | ||
| }, { | ||
| key: 'prepareProps', | ||
| value: function prepareProps(props) { | ||
| var customProps = _extends({}, props); // babeljs converts to Object.assign, then polyfills. | ||
| delete customProps.onChange; | ||
| delete customProps.value; | ||
| delete customProps.decimalSeparator; | ||
| delete customProps.thousandSeparator; | ||
| delete customProps.precision; | ||
| delete customProps.inputType; | ||
| delete customProps.allowNegative; | ||
| delete customProps.allowEmpty; | ||
| delete customProps.prefix; | ||
| delete customProps.suffix; | ||
| // Strip out thousand separators, prefix, and suffix, etc. | ||
| if (props.thousandSeparator === ".") { | ||
| // special handle the . thousand separator | ||
| initialValue = initialValue.replace(/\./g, ''); | ||
| } | ||
| var initialValue = props.value; | ||
| if (initialValue === null) { | ||
| initialValue = props.allowEmpty ? null : ''; | ||
| } else { | ||
| if (props.decimalSeparator != ".") { | ||
| // fix the decimal separator | ||
| initialValue = initialValue.replace(new RegExp(props.decimalSeparator, 'g'), '.'); | ||
| if (typeof initialValue == 'string') { | ||
| // Some people, when confronted with a problem, think "I know, I'll use regular expressions." | ||
| // Now they have two problems. | ||
| // Strip out thousand separators, prefix, and suffix, etc. | ||
| if (props.thousandSeparator === ".") { | ||
| // special handle the . thousand separator | ||
| initialValue = initialValue.replace(/\./g, ''); | ||
| } | ||
| if (props.decimalSeparator != ".") { | ||
| // fix the decimal separator | ||
| initialValue = initialValue.replace(new RegExp(props.decimalSeparator, 'g'), '.'); | ||
| } | ||
| //Strip out anything that is not a digit, -, or decimal separator | ||
| initialValue = initialValue.replace(/[^0-9-.]/g, ''); | ||
| // now we can parse. | ||
| initialValue = Number.parseFloat(initialValue); | ||
| } | ||
| initialValue = Number(initialValue).toLocaleString(undefined, { | ||
| style: 'decimal', | ||
| minimumFractionDigits: props.precision, | ||
| maximumFractionDigits: props.precision | ||
| }); | ||
| } | ||
| //Strip out anything that is not a digit, -, or decimal separator | ||
| initialValue = initialValue.replace(/[^0-9-.]/g, ''); | ||
| var _mask = (0, _mask4.default)(initialValue, props.precision, props.decimalSeparator, props.thousandSeparator, props.allowNegative, props.prefix, props.suffix); | ||
| // now we can parse. | ||
| initialValue = Number.parseFloat(initialValue); | ||
| var maskedValue = _mask.maskedValue; | ||
| var value = _mask.value; | ||
| return { maskedValue: maskedValue, value: value, customProps: customProps }; | ||
| } | ||
| /** | ||
| * Component lifecycle function. | ||
| * Invoked when a component is receiving new props. This method is not called for the initial render. | ||
| * | ||
| * @param nextProps | ||
| * @see https://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops | ||
| */ | ||
| }, { | ||
| key: 'componentWillReceiveProps', | ||
| value: function componentWillReceiveProps(nextProps) { | ||
| this.setState(this.prepareProps(nextProps)); | ||
| } | ||
| /** | ||
| * Component lifecycle function. | ||
| * @returns {XML} | ||
| * @see https://facebook.github.io/react/docs/react-component.html#componentdidmount | ||
| */ | ||
| }, { | ||
| key: 'componentDidMount', | ||
| value: function componentDidMount() { | ||
| var node = _reactDom2.default.findDOMNode(this.theInput); | ||
| var selectionEnd = Math.min(node.selectionEnd, this.theInput.value.length - this.props.suffix.length); | ||
| var selectionStart = Math.min(node.selectionStart, selectionEnd); | ||
| //console.log("normal", selectionStart, selectionEnd); | ||
| node.setSelectionRange(selectionStart, selectionEnd); | ||
| } | ||
| /** | ||
| * Component lifecycle function. | ||
| * @returns {XML} | ||
| * @see https://facebook.github.io/react/docs/react-component.html#componentdidupdate | ||
| */ | ||
| }, { | ||
| key: 'componentDidUpdate', | ||
| value: function componentDidUpdate(prevProps, prevState) { | ||
| var node = _reactDom2.default.findDOMNode(this.theInput); | ||
| var selectionEnd = Math.min(this.state.selectionEnd, this.theInput.value.length - this.props.suffix.length); | ||
| var selectionStart = Math.min(this.state.selectionStart, selectionEnd); | ||
| // moves the cursor to the right when digits are added. | ||
| var adjustment = Math.max(this.state.maskedValue.length - prevState.maskedValue.length - 1, 0); | ||
| var baselength = this.props.suffix.length + this.props.prefix.length + this.props.decimalSeparator.length + Number(this.props.precision) + 1; // This is to account for the default '0' value that comes before the decimal separator | ||
| if (this.state.maskedValue.length == baselength) { | ||
| // if we are already at base length, position the cursor at the end. | ||
| selectionEnd = this.theInput.value.length - this.props.suffix.length; | ||
| selectionStart = selectionEnd; | ||
| adjustment = 0; | ||
| } | ||
| initialValue = Number(initialValue).toLocaleString(undefined, { | ||
| style: 'decimal', | ||
| minimumFractionDigits: props.precision, | ||
| maximumFractionDigits: props.precision | ||
| }); | ||
| node.setSelectionRange(selectionStart + adjustment, selectionEnd + adjustment); | ||
| } | ||
| var _mask = (0, _mask4.default)(initialValue, props.precision, props.decimalSeparator, props.thousandSeparator, props.allowNegative, props.prefix, props.suffix); | ||
| /** | ||
| * onChange Event Handler | ||
| * @param event | ||
| */ | ||
| var maskedValue = _mask.maskedValue; | ||
| var value = _mask.value; | ||
| }, { | ||
| key: 'handleChange', | ||
| value: function handleChange(event) { | ||
| var _this2 = this; | ||
| event.preventDefault(); | ||
| return { maskedValue: maskedValue, value: value, customProps: customProps }; | ||
| }, | ||
| var _mask2 = (0, _mask4.default)(event.target.value, this.props.precision, this.props.decimalSeparator, this.props.thousandSeparator, this.props.allowNegative, this.props.prefix, this.props.suffix); | ||
| var maskedValue = _mask2.maskedValue; | ||
| var value = _mask2.value; | ||
| /** | ||
| * Component lifecycle function. | ||
| * Invoked once before the component is mounted. The return value will be used as the initial value of this.state | ||
| * | ||
| * @returns {{ maskedValue: {String}, value: {Number}, customProps: {Object} }} | ||
| * @see https://facebook.github.io/react/docs/component-specs.html#getinitialstate | ||
| */ | ||
| getInitialState: function getInitialState() { | ||
| return this.prepareProps(this.props); | ||
| }, | ||
| var node = _reactDom2.default.findDOMNode(this.theInput); | ||
| var selectionEnd = Math.min(node.selectionEnd, this.theInput.value.length - this.props.suffix.length); | ||
| var selectionStart = Math.min(node.selectionStart, selectionEnd); | ||
| /** | ||
| * Component lifecycle function. | ||
| * Invoked when a component is receiving new props. This method is not called for the initial render. | ||
| * | ||
| * @param nextProps | ||
| * @see https://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops | ||
| */ | ||
| componentWillReceiveProps: function componentWillReceiveProps(nextProps) { | ||
| this.setState(this.prepareProps(nextProps)); | ||
| }, | ||
| event.persist(); // fixes issue #23 | ||
| this.setState({ maskedValue: maskedValue, value: value }, function () { | ||
| _this2.props.onChange(maskedValue, value, event); | ||
| _this2.props.onChangeEvent(event, maskedValue, value); | ||
| }); | ||
| } | ||
| /** | ||
| * Exposes the current masked value. | ||
| * | ||
| * @returns {String} | ||
| */ | ||
| getMaskedValue: function getMaskedValue() { | ||
| return this.state.maskedValue; | ||
| }, | ||
| /** | ||
| * onFocus Event Handler | ||
| * @param event | ||
| */ | ||
| }, { | ||
| key: 'handleFocus', | ||
| value: function handleFocus(event) { | ||
| //Whenever we receive focus check to see if the position is before the suffix, if not, move it. | ||
| var selectionEnd = this.theInput.value.length - this.props.suffix.length; | ||
| var selectionStart = this.props.prefix.length; | ||
| console.log(selectionStart, selectionEnd); | ||
| event.target.setSelectionRange(selectionStart, selectionEnd); | ||
| this.setState({ selectionStart: selectionStart, selectionEnd: selectionEnd }); | ||
| } | ||
| }, { | ||
| key: 'handleBlur', | ||
| value: function handleBlur(event) { | ||
| this.setState({ | ||
| selectionStart: null, | ||
| selectionEnd: null | ||
| }); | ||
| } | ||
| /** | ||
| * onChange Event Handler | ||
| * @param event | ||
| */ | ||
| handleChange: function handleChange(event) { | ||
| var _this = this; | ||
| /** | ||
| * Component lifecycle function. | ||
| * @returns {XML} | ||
| * @see https://facebook.github.io/react/docs/component-specs.html#render | ||
| */ | ||
| event.preventDefault(); | ||
| }, { | ||
| key: 'render', | ||
| value: function render() { | ||
| var _this3 = this; | ||
| var _mask2 = (0, _mask4.default)(event.target.value, this.props.precision, this.props.decimalSeparator, this.props.thousandSeparator, this.props.allowNegative, this.props.prefix, this.props.suffix); | ||
| return _react2.default.createElement('input', _extends({ | ||
| ref: function ref(input) { | ||
| _this3.theInput = input; | ||
| }, | ||
| type: this.props.inputType, | ||
| value: this.state.maskedValue, | ||
| onChange: this.handleChange, | ||
| onFocus: this.handleFocus, | ||
| onMouseUp: this.handleFocus | ||
| }, this.state.customProps)); | ||
| } | ||
| }]); | ||
| var maskedValue = _mask2.maskedValue; | ||
| var value = _mask2.value; | ||
| return CurrencyInput; | ||
| }(_react.Component); | ||
| this.setState({ maskedValue: maskedValue, value: value }, function () { | ||
| _this.props.onChange(maskedValue, value, event); | ||
| }); | ||
| }, | ||
| /** | ||
| * Prop validation. | ||
| * @see https://facebook.github.io/react/docs/component-specs.html#proptypes | ||
| */ | ||
| CurrencyInput.propTypes = { | ||
| onChange: _propTypes2.default.func, | ||
| value: _propTypes2.default.oneOfType([_propTypes2.default.number, _propTypes2.default.string]), | ||
| decimalSeparator: _propTypes2.default.string, | ||
| thousandSeparator: _propTypes2.default.string, | ||
| precision: _propTypes2.default.oneOfType([_propTypes2.default.number, _propTypes2.default.string]), | ||
| inputType: _propTypes2.default.string, | ||
| allowNegative: _propTypes2.default.bool, | ||
| allowEmpty: _propTypes2.default.bool, | ||
| prefix: _propTypes2.default.string, | ||
| suffix: _propTypes2.default.string | ||
| }; | ||
| /** | ||
| * Component lifecycle function. | ||
| * @returns {XML} | ||
| * @see https://facebook.github.io/react/docs/component-specs.html#render | ||
| */ | ||
| render: function render() { | ||
| return _react2.default.createElement('input', _extends({ | ||
| type: this.props.inputType, | ||
| value: this.state.maskedValue, | ||
| onChange: this.handleChange | ||
| }, this.state.customProps)); | ||
| } | ||
| }); | ||
| CurrencyInput.defaultProps = { | ||
| onChange: function onChange(maskValue, value, event) {/*no-op*/}, | ||
| onChangeEvent: function onChangeEvent(event, maskValue, value) {/*no-op*/}, | ||
| value: '0', | ||
| decimalSeparator: '.', | ||
| thousandSeparator: ',', | ||
| precision: '2', | ||
| inputType: 'text', | ||
| allowNegative: false, | ||
| prefix: '', | ||
| suffix: '' | ||
| }; | ||
| exports.default = CurrencyInput; |
+12
-4
| { | ||
| "name": "react-currency-input", | ||
| "version": "1.2.6", | ||
| "version": "1.3.0", | ||
| "description": "React component for inputing currency amounts", | ||
@@ -13,3 +13,5 @@ "main": "lib/index.js", | ||
| "build-example": "browserify example/example.js -o example/bundle.js -t [ babelify --presets [ es2015 react ] ]", | ||
| "test": "mocha --compilers js:babel-register " | ||
| "test": "mocha --compilers js:babel-register ", | ||
| "webpack": "webpack", | ||
| "webpack-dev": "webpack-dev-server" | ||
| }, | ||
@@ -36,6 +38,9 @@ "repository": { | ||
| "dependencies": { | ||
| "react": ">=0.14.0 || >=15.0.0" | ||
| "react": "^15.5.0", | ||
| "react-dom": "^15.5.0", | ||
| "prop-types": "^15.5.0" | ||
| }, | ||
| "devDependencies": { | ||
| "babel-cli": "^6.18.0", | ||
| "babel-loader": "6.4.0", | ||
| "babel-preset-es2015": "^6.9.0", | ||
@@ -51,2 +56,3 @@ "babel-preset-react": "^6.5.0", | ||
| "mocha": "^2.5.3", | ||
| "prop-types": "^15.5.9", | ||
| "react-addons-test-utils": "^15.1.0", | ||
@@ -56,4 +62,6 @@ "react-dom": "^15.1.0", | ||
| "sinon": "^1.17.4", | ||
| "sinon-chai": "^2.8.0" | ||
| "sinon-chai": "^2.8.0", | ||
| "webpack": "2.2.1", | ||
| "webpack-dev-server": "2.4.1" | ||
| } | ||
| } |
+17
-6
@@ -8,2 +8,12 @@ # react-currency-input | ||
| ## Changes | ||
| V1.3.0: | ||
| ------- | ||
| * Depecrated "onChange" option in favor of "onChangeEvent". This fixes the argument order to better match React's default input handling | ||
| * Updated dependencies to React 15 | ||
| * Added parseFloat polyfill | ||
| * Persist events to deal with an issue of event pooling | ||
| * Other bug fixes. | ||
| ## Installation | ||
@@ -28,4 +38,4 @@ ``` | ||
| handleChange(newValue){ | ||
| this.setState({amount: newValue}); | ||
| handleChange(event, maskedvalue, floatvalue){ | ||
| this.setState({amount: maskedvalue}); | ||
| }, | ||
@@ -35,7 +45,7 @@ render() { | ||
| <div> | ||
| <CurrencyInput value={this.state.amount} onChange={this.handleChange}/> | ||
| <CurrencyInput value={this.state.amount} onChangeEvent={this.handleChange}/> | ||
| </div> | ||
| ); | ||
| } | ||
| } | ||
| }); | ||
| export default MyApp | ||
@@ -63,3 +73,3 @@ ``` | ||
| } | ||
| } | ||
| }); | ||
| export default MyApp | ||
@@ -128,3 +138,4 @@ ``` | ||
| | value | 0 | The initial currency value | | ||
| | onChange | n/a | Callback function to handle value changes | | ||
| | onChange | n/a | Callback function to handle value changes. Deprecated, use onChangeEvent. | | ||
| | onChangeEvent | n/a | Callback function to handle value changes | | ||
| | precision | 2 | Number of digits after the decimal separator | | ||
@@ -131,0 +142,0 @@ | decimalSeparator | '.' | The decimal separator | |
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
133021
5.5%8
14.29%358
46.12%149
7.97%3
200%20
25%3
50%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
Updated