react-currency-input
Advanced tools
Comparing version 1.2.6 to 1.3.0
381
lib/index.js
@@ -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; |
{ | ||
"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" | ||
} | ||
} |
@@ -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 | |
133021
8
358
149
3
20
+ Addedprop-types@^15.5.0
+ Addedreact-dom@^15.5.0
+ Addedasap@2.0.6(transitive)
+ Addedcore-js@1.2.7(transitive)
+ Addedcreate-react-class@15.7.0(transitive)
+ Addedencoding@0.1.13(transitive)
+ Addedfbjs@0.8.18(transitive)
+ Addediconv-lite@0.6.3(transitive)
+ Addedis-stream@1.1.0(transitive)
+ Addedisomorphic-fetch@2.2.1(transitive)
+ Addedjs-tokens@4.0.0(transitive)
+ Addedloose-envify@1.4.0(transitive)
+ Addednode-fetch@1.7.3(transitive)
+ Addedobject-assign@4.1.1(transitive)
+ Addedpromise@7.3.1(transitive)
+ Addedprop-types@15.8.1(transitive)
+ Addedreact@15.7.0(transitive)
+ Addedreact-dom@15.7.0(transitive)
+ Addedreact-is@16.13.1(transitive)
+ Addedsafer-buffer@2.1.2(transitive)
+ Addedsetimmediate@1.0.5(transitive)
+ Addedua-parser-js@0.7.40(transitive)
+ Addedwhatwg-fetch@3.6.20(transitive)
- Removedreact@19.0.0(transitive)
Updatedreact@^15.5.0