├── .babelrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── examples ├── index.html └── index.js ├── lib ├── react-currency-input.cjs.js ├── react-currency-input.cjs.js.map ├── react-currency-input.es.js ├── react-currency-input.es.js.map ├── react-currency-input.min.js └── react-currency-input.min.js.map ├── package.json ├── rollup.config.js ├── src ├── index.js ├── mask.js └── object-assign-polyfill.js ├── test ├── index.spec.js ├── mask.spec.js └── setup.js ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { "presets": ["es2015", "react", "stage-3"] } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | npm-debug.log 4 | coverage -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | src/ 3 | test/ 4 | .idea/ 5 | .babelrc 6 | .npmignore 7 | .gitignore 8 | examples/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | - "4" 5 | - "6" 6 | - "7" 7 | - "8" 8 | install: 9 | - yarn install 10 | script: 11 | - yarn test 12 | - yarn run build-example 13 | cache: 14 | yarn: true 15 | directories: 16 | - node_modules 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Cedric Dugas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-currency-input 2 | 3 | An ES2015 react component for currency. Supports custom decimal and thousand separators as well as precision. 4 | 5 | [![Build Status](https://travis-ci.org/jsillitoe/react-currency-input.svg?branch=master)](https://travis-ci.org/jsillitoe/react-currency-input) 6 | 7 | ## Changes 8 | 9 | ## v1.3.0: 10 | 11 | - Deprecated "onChange" option in favor of "onChangeEvent". This fixes the argument order to better match React's default input handling 12 | - Updated dependencies to React 15 13 | - Added parseFloat polyfill 14 | - Persist events to deal with an issue of event pooling 15 | - Other bug fixes. 16 | 17 | ## Installation 18 | 19 | ``` 20 | npm install react-currency-input --save 21 | ``` 22 | 23 | ## Integration 24 | 25 | You can store the value passed in to the change handler in your state. 26 | 27 | ```javascript 28 | import React from 'react' 29 | import CurrencyInput from 'react-currency-input'; 30 | 31 | const MyApp = React.createClass({ 32 | getInitialState(){ 33 | return ({amount: "0.00"}); 34 | }, 35 | 36 | handleChange(event, maskedvalue, floatvalue){ 37 | this.setState({amount: maskedvalue}); 38 | }, 39 | render() { 40 | return ( 41 |
42 | 43 |
44 | ); 45 | } 46 | }); 47 | export default MyApp 48 | ``` 49 | 50 | You can also assign a reference then access the value using a call to getMaskedValue(). 51 | 52 | ```javascript 53 | import React from 'react' 54 | import CurrencyInput from 'react-currency-input'; 55 | 56 | const MyApp = React.createClass({ 57 | handleSubmit(event){ 58 | event.preventDefault(); 59 | console.log(this.refs.myinput.getMaskedValue()) 60 | }, 61 | render() { 62 | return ( 63 |
64 | 65 | 66 | ); 67 | } 68 | }); 69 | export default MyApp 70 | ``` 71 | 72 | ## Separators and Precision 73 | 74 | Specify custom decimal and thousand separators: 75 | 76 | ```javascript 77 | // 1.234.567,89 78 | 79 | ``` 80 | 81 | Specify a specific precision: 82 | 83 | ```javascript 84 | // 123,456.789 85 | 86 | ``` 87 | 88 | ```javascript 89 | // 123,456,789 90 | 91 | ``` 92 | 93 | ## Currency 94 | 95 | Optionally set a currency symbol as a prefix or suffix 96 | 97 | ```javascript 98 | // $1,234,567.89 99 | 100 | ``` 101 | 102 | ```javascript 103 | // 1,234,567.89 kr 104 | 105 | ``` 106 | 107 | Negative signs come before the prefix 108 | 109 | ```javascript 110 | // -$20.00 111 | 112 | ``` 113 | 114 | All other attributes are applied to the input element. For example, you can integrate bootstrap styling: 115 | 116 | ```javascript 117 | 118 | ``` 119 | 120 | ## Options 121 | 122 | Option | Default Value | Description 123 | ----------------- | ------------- | ----------------------------------------------------------------------------- 124 | value | 0 | The initial currency value 125 | onChange | n/a | Callback function to handle value changes. Deprecated, use onChangeEvent. 126 | onChangeEvent | n/a | Callback function to handle value changes 127 | precision | 2 | Number of digits after the decimal separator 128 | decimalSeparator | '.' | The decimal separator 129 | thousandSeparator | ',' | The thousand separator 130 | inputType | "text" | Input field tag type. You may want to use `number` or `tel`* 131 | allowNegative | false | Allows negative numbers in the input 132 | allowEmpty | false | If no `value` is given, defines if it starts as null (`true`) or '' (`false`) 133 | selectAllOnFocus | false | Selects all text on focus or does not 134 | prefix | '' | Currency prefix 135 | suffix | '' | Currency suffix 136 | autoFocus | false | Autofocus 137 | 138 | ***Note:** Enabling any mask-related features such as prefix, suffix or separators with an inputType="number" or "tel" could trigger errors. Most of those characters would be invalid in such input types. 139 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React Currency Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
0
14 | 15 | 16 |
1
17 | 18 | 19 |
2
20 | 21 | 22 |
3
23 | 24 | 25 |
4
26 | 27 | 28 |
5
29 | 30 | 31 |
6
32 | 33 |
7
34 | 35 | Autofocus: 36 |
8
37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jrs1 on 3/7/17. 3 | */ 4 | 5 | import React from 'react'; 6 | import ReactDOM from 'react-dom'; 7 | 8 | import CurrencyInput from '../src/index'; 9 | 10 | 11 | ReactDOM.render(, document.getElementById('example0')); 12 | 13 | ReactDOM.render(, document.getElementById('example1')); 14 | 15 | ReactDOM.render(, document.getElementById('example2')); 16 | 17 | ReactDOM.render(, document.getElementById('example3')); 18 | 19 | ReactDOM.render(, document.getElementById('example4')); 20 | 21 | ReactDOM.render(, document.getElementById('example5')); 22 | 23 | ReactDOM.render(, document.getElementById('example6')); 24 | 25 | var onChangeEvent = function(event, mask, floatValue) { 26 | console.log(event) 27 | console.log(mask) 28 | console.log(floatValue) 29 | } 30 | 31 | ReactDOM.render( 32 | , 33 | document.getElementById('example7') 34 | ); 35 | 36 | ReactDOM.render(, document.getElementById('example8')); 37 | 38 | -------------------------------------------------------------------------------- /lib/react-currency-input.cjs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } 4 | 5 | var PropTypes = _interopDefault(require('prop-types')); 6 | var React = require('react'); 7 | var React__default = _interopDefault(React); 8 | var ReactDOM = _interopDefault(require('react-dom')); 9 | 10 | Object.assign = Object.assign || 11 | function(target) { 12 | var arguments$1 = arguments; 13 | 14 | for (var i = 1; i < arguments.length; i++) { 15 | var source = arguments$1[i]; 16 | for (var key in source) { 17 | if (Object.prototype.hasOwnProperty.call(source, key)) { 18 | target[key] = source[key]; 19 | } 20 | } 21 | } 22 | return target; 23 | }; 24 | 25 | function mask(value, precision, decimalSeparator, thousandSeparator, allowNegative, prefix, suffix){ 26 | if ( precision === void 0 ) precision = 2; 27 | if ( decimalSeparator === void 0 ) decimalSeparator = '.'; 28 | if ( thousandSeparator === void 0 ) thousandSeparator = ','; 29 | if ( allowNegative === void 0 ) allowNegative = false; 30 | if ( prefix === void 0 ) prefix = ''; 31 | if ( suffix === void 0 ) suffix = ''; 32 | 33 | // provide some default values and arg validation. 34 | if (precision < 0) { precision = 0; } // precision cannot be negative 35 | if (precision > 20) { precision = 20; } // precision cannot be greater than 20 36 | 37 | if (value === null || value===undefined) { 38 | return { 39 | value: 0, 40 | maskedValue: '' 41 | }; 42 | } 43 | 44 | value = String(value); //if the given value is a Number, let's convert into String to manipulate that 45 | 46 | if (value.length == 0) { 47 | return { 48 | value: 0, 49 | maskedValue: '' 50 | }; 51 | } 52 | 53 | 54 | // extract digits. if no digits, fill in a zero. 55 | var digits = value.match(/\d/g) || ['0']; 56 | 57 | var numberIsNegative = false; 58 | if (allowNegative) { 59 | var negativeSignCount = (value.match(/-/g) || []).length; 60 | // number will be negative if we have an odd number of "-" 61 | // ideally, we should only ever have 0, 1 or 2 (positive number, making a number negative 62 | // and making a negative number positive, respectively) 63 | numberIsNegative = negativeSignCount % 2 === 1; 64 | 65 | // if every digit in the array is '0', then the number should never be negative 66 | var allDigitsAreZero = true; 67 | for (var idx=0; idx < digits.length; idx += 1) { 68 | if(digits[idx] !== '0') { 69 | allDigitsAreZero = false; 70 | break; 71 | } 72 | } 73 | if (allDigitsAreZero) { 74 | numberIsNegative = false; 75 | } 76 | } 77 | 78 | // zero-pad a input 79 | while (digits.length <= precision) { digits.unshift('0'); } 80 | 81 | if (precision > 0) { 82 | // add the decimal separator 83 | digits.splice(digits.length - precision, 0, "."); 84 | } 85 | 86 | // clean up extraneous digits like leading zeros. 87 | digits = Number(digits.join('')).toFixed(precision).split(''); 88 | var raw = Number(digits.join('')); 89 | 90 | var decimalpos = digits.length - precision - 1; // -1 needed to position the decimal separator before the digits. 91 | if (precision > 0) { 92 | // set the final decimal separator 93 | digits[decimalpos] = decimalSeparator; 94 | } else { 95 | // when precision is 0, there is no decimal separator. 96 | decimalpos = digits.length; 97 | } 98 | 99 | // add in any thousand separators 100 | for (var x=decimalpos - 3; x > 0; x = x - 3) { 101 | digits.splice(x, 0, thousandSeparator); 102 | } 103 | 104 | // if we have a prefix or suffix, add them in. 105 | if (prefix.length > 0) { digits.unshift(prefix); } 106 | if (suffix.length > 0) { digits.push(suffix); } 107 | 108 | // if the number is negative, insert a "-" to 109 | // the front of the array and negate the raw value 110 | if (allowNegative && numberIsNegative) { 111 | digits.unshift('-'); 112 | raw = -raw; 113 | } 114 | 115 | return { 116 | value: raw, 117 | maskedValue: digits.join('').trim() 118 | }; 119 | } 120 | 121 | // IE* parseFloat polyfill 122 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/parseFloat#Polyfill 123 | Number.parseFloat = parseFloat; 124 | 125 | var CurrencyInput = (function (Component$$1) { 126 | function CurrencyInput(props) { 127 | Component$$1.call(this, props); 128 | this.prepareProps = this.prepareProps.bind(this); 129 | this.handleChange = this.handleChange.bind(this); 130 | this.handleFocus = this.handleFocus.bind(this); 131 | this.state = this.prepareProps(this.props); 132 | 133 | this.inputSelectionStart = 1; 134 | this.inputSelectionEnd = 1; 135 | } 136 | 137 | if ( Component$$1 ) CurrencyInput.__proto__ = Component$$1; 138 | CurrencyInput.prototype = Object.create( Component$$1 && Component$$1.prototype ); 139 | CurrencyInput.prototype.constructor = CurrencyInput; 140 | 141 | 142 | /** 143 | * Exposes the current masked value. 144 | * 145 | * @returns {String} 146 | */ 147 | CurrencyInput.prototype.getMaskedValue = function getMaskedValue () { 148 | return this.state.maskedValue; 149 | }; 150 | 151 | 152 | /** 153 | * General function used to cleanup and define the final props used for rendering 154 | * @returns {{ maskedValue: {String}, value: {Number}, customProps: {Object} }} 155 | */ 156 | CurrencyInput.prototype.prepareProps = function prepareProps (props) { 157 | var customProps = Object.assign({}, props); // babeljs converts to Object.assign, then polyfills. 158 | delete customProps.onChange; 159 | delete customProps.onChangeEvent; 160 | delete customProps.value; 161 | delete customProps.decimalSeparator; 162 | delete customProps.thousandSeparator; 163 | delete customProps.precision; 164 | delete customProps.inputType; 165 | delete customProps.allowNegative; 166 | delete customProps.allowEmpty; 167 | delete customProps.prefix; 168 | delete customProps.suffix; 169 | delete customProps.selectAllOnFocus; 170 | delete customProps.autoFocus; 171 | 172 | var initialValue = props.value; 173 | if (initialValue === null) { 174 | initialValue = props.allowEmpty? null : ''; 175 | }else{ 176 | 177 | if (typeof initialValue == 'string') { 178 | // Some people, when confronted with a problem, think "I know, I'll use regular expressions." 179 | // Now they have two problems. 180 | 181 | // Strip out thousand separators, prefix, and suffix, etc. 182 | if (props.thousandSeparator === "."){ 183 | // special handle the . thousand separator 184 | initialValue = initialValue.replace(/\./g, ''); 185 | } 186 | 187 | if (props.decimalSeparator != "."){ 188 | // fix the decimal separator 189 | initialValue = initialValue.replace(new RegExp(props.decimalSeparator, 'g'), '.'); 190 | } 191 | 192 | //Strip out anything that is not a digit, -, or decimal separator 193 | initialValue = initialValue.replace(/[^0-9-.]/g, ''); 194 | 195 | // now we can parse. 196 | initialValue = Number.parseFloat(initialValue); 197 | } 198 | initialValue = Number(initialValue).toLocaleString(undefined, { 199 | style : 'decimal', 200 | minimumFractionDigits: props.precision, 201 | maximumFractionDigits: props.precision 202 | }); 203 | 204 | } 205 | 206 | var ref = mask( 207 | initialValue, 208 | props.precision, 209 | props.decimalSeparator, 210 | props.thousandSeparator, 211 | props.allowNegative, 212 | props.prefix, 213 | props.suffix 214 | ); 215 | var maskedValue = ref.maskedValue; 216 | var value = ref.value; 217 | 218 | return { maskedValue: maskedValue, value: value, customProps: customProps }; 219 | }; 220 | 221 | 222 | /** 223 | * Component lifecycle function. 224 | * Invoked when a component is receiving new props. This method is not called for the initial render. 225 | * 226 | * @param nextProps 227 | * @see https://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops 228 | */ 229 | CurrencyInput.prototype.componentWillReceiveProps = function componentWillReceiveProps (nextProps) { 230 | this.setState(this.prepareProps(nextProps)); 231 | }; 232 | 233 | 234 | /** 235 | * Component lifecycle function. 236 | * @returns {XML} 237 | * @see https://facebook.github.io/react/docs/react-component.html#componentdidmount 238 | */ 239 | CurrencyInput.prototype.componentDidMount = function componentDidMount (){ 240 | var node = ReactDOM.findDOMNode(this.theInput); 241 | var selectionStart, selectionEnd; 242 | 243 | if (this.props.autoFocus) { 244 | this.theInput.focus(); 245 | selectionEnd = this.state.maskedValue.length - this.props.suffix.length; 246 | selectionStart = selectionEnd; 247 | } else { 248 | selectionEnd = Math.min(node.selectionEnd, this.theInput.value.length - this.props.suffix.length); 249 | selectionStart = Math.min(node.selectionStart, selectionEnd); 250 | } 251 | 252 | node.setSelectionRange(selectionStart, selectionEnd); 253 | }; 254 | 255 | 256 | /** 257 | * Component lifecycle function 258 | * @returns {XML} 259 | * @see https://facebook.github.io/react/docs/react-component.html#componentwillupdate 260 | */ 261 | CurrencyInput.prototype.componentWillUpdate = function componentWillUpdate () { 262 | var node = ReactDOM.findDOMNode(this.theInput); 263 | this.inputSelectionStart = node.selectionStart; 264 | this.inputSelectionEnd = node.selectionEnd; 265 | }; 266 | 267 | 268 | /** 269 | * Component lifecycle function. 270 | * @returns {XML} 271 | * @see https://facebook.github.io/react/docs/react-component.html#componentdidupdate 272 | */ 273 | CurrencyInput.prototype.componentDidUpdate = function componentDidUpdate (prevProps, prevState){ 274 | var ref = this.props; 275 | var decimalSeparator = ref.decimalSeparator; 276 | var node = ReactDOM.findDOMNode(this.theInput); 277 | var isNegative = (this.theInput.value.match(/-/g) || []).length % 2 === 1; 278 | var minPos = this.props.prefix.length + (isNegative ? 1 : 0); 279 | var selectionEnd = Math.max(minPos, Math.min(this.inputSelectionEnd, this.theInput.value.length - this.props.suffix.length)); 280 | var selectionStart = Math.max(minPos, Math.min(this.inputSelectionEnd, selectionEnd)); 281 | 282 | var regexEscapeRegex = /[-[\]{}()*+?.,\\^$|#\s]/g; 283 | var separatorsRegex = new RegExp(decimalSeparator.replace(regexEscapeRegex, '\\$&') + '|' + this.props.thousandSeparator.replace(regexEscapeRegex, '\\$&'), 'g'); 284 | var currSeparatorCount = (this.state.maskedValue.match(separatorsRegex) || []).length; 285 | var prevSeparatorCount = (prevState.maskedValue.match(separatorsRegex) || []).length; 286 | var adjustment = Math.max(currSeparatorCount - prevSeparatorCount, 0); 287 | 288 | selectionEnd = selectionEnd + adjustment; 289 | selectionStart = selectionStart + adjustment; 290 | 291 | var precision = Number(this.props.precision); 292 | 293 | var baselength = this.props.suffix.length 294 | + this.props.prefix.length 295 | + (precision > 0 ? decimalSeparator.length : 0) // if precision is 0 there will be no decimal part 296 | + precision 297 | + 1; // This is to account for the default '0' value that comes before the decimal separator 298 | 299 | if (this.state.maskedValue.length == baselength){ 300 | // if we are already at base length, position the cursor at the end. 301 | selectionEnd = this.theInput.value.length - this.props.suffix.length; 302 | selectionStart = selectionEnd; 303 | } 304 | 305 | node.setSelectionRange(selectionStart, selectionEnd); 306 | this.inputSelectionStart = selectionStart; 307 | this.inputSelectionEnd = selectionEnd; 308 | }; 309 | 310 | 311 | /** 312 | * onChange Event Handler 313 | * @param event 314 | */ 315 | CurrencyInput.prototype.handleChange = function handleChange (event) { 316 | var this$1 = this; 317 | 318 | event.preventDefault(); 319 | var ref = mask( 320 | event.target.value, 321 | this.props.precision, 322 | this.props.decimalSeparator, 323 | this.props.thousandSeparator, 324 | this.props.allowNegative, 325 | this.props.prefix, 326 | this.props.suffix 327 | ); 328 | var maskedValue = ref.maskedValue; 329 | var value = ref.value; 330 | 331 | event.persist(); // fixes issue #23 332 | 333 | this.setState({ maskedValue: maskedValue, value: value }, function () { 334 | this$1.props.onChange(maskedValue, value, event); 335 | this$1.props.onChangeEvent(event, maskedValue, value); 336 | }); 337 | }; 338 | 339 | 340 | /** 341 | * onFocus Event Handler 342 | * @param event 343 | */ 344 | CurrencyInput.prototype.handleFocus = function handleFocus (event) { 345 | if (!this.theInput) { return; } 346 | 347 | //Whenever we receive focus check to see if the position is before the suffix, if not, move it. 348 | var selectionEnd = this.theInput.value.length - this.props.suffix.length; 349 | var isNegative = (this.theInput.value.match(/-/g) || []).length % 2 === 1; 350 | var selectionStart = this.props.prefix.length + (isNegative ? 1 : 0); 351 | this.props.selectAllOnFocus && event.target.setSelectionRange(selectionStart, selectionEnd); 352 | this.inputSelectionStart = selectionStart; 353 | this.inputSelectionEnd = selectionEnd; 354 | }; 355 | 356 | 357 | CurrencyInput.prototype.handleBlur = function handleBlur (event) { 358 | this.inputSelectionStart = 0; 359 | this.inputSelectionEnd = 0; 360 | }; 361 | 362 | 363 | /** 364 | * Component lifecycle function. 365 | * @returns {XML} 366 | * @see https://facebook.github.io/react/docs/component-specs.html#render 367 | */ 368 | CurrencyInput.prototype.render = function render () { 369 | var this$1 = this; 370 | 371 | return ( 372 | React__default.createElement( 'input', Object.assign({}, 373 | { ref: function (input) { this$1.theInput = input; }, type: this.props.inputType, value: this.state.maskedValue, onChange: this.handleChange, onFocus: this.handleFocus, onMouseUp: this.handleFocus }, this.state.customProps)) 374 | ) 375 | }; 376 | 377 | return CurrencyInput; 378 | }(React.Component)); 379 | 380 | 381 | 382 | /** 383 | * Prop validation. 384 | * @see https://facebook.github.io/react/docs/component-specs.html#proptypes 385 | */ 386 | 387 | CurrencyInput.propTypes = { 388 | onChange: PropTypes.func, 389 | value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 390 | decimalSeparator: PropTypes.string, 391 | thousandSeparator: PropTypes.string, 392 | precision: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 393 | inputType: PropTypes.string, 394 | allowNegative: PropTypes.bool, 395 | allowEmpty: PropTypes.bool, 396 | prefix: PropTypes.string, 397 | suffix: PropTypes.string, 398 | selectAllOnFocus: PropTypes.bool 399 | }; 400 | 401 | 402 | CurrencyInput.defaultProps = { 403 | onChange: function(maskValue, value, event) {/*no-op*/}, 404 | onChangeEvent: function(event, maskValue, value) {/*no-op*/}, 405 | autoFocus: false, 406 | value: '0', 407 | decimalSeparator: '.', 408 | thousandSeparator: ',', 409 | precision: '2', 410 | inputType: 'text', 411 | allowNegative: false, 412 | prefix: '', 413 | suffix: '', 414 | selectAllOnFocus: false 415 | }; 416 | 417 | module.exports = CurrencyInput; 418 | //# sourceMappingURL=react-currency-input.cjs.js.map 419 | -------------------------------------------------------------------------------- /lib/react-currency-input.cjs.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"react-currency-input.cjs.js","sources":["../src/object-assign-polyfill.js","../src/mask.js","../src/index.js"],"sourcesContent":["Object.assign = Object.assign ||\n function(target) {\n for (var i = 1; i < arguments.length; i++) {\n var source = arguments[i];\n for (var key in source) {\n if (Object.prototype.hasOwnProperty.call(source, key)) {\n target[key] = source[key];\n }\n }\n }\n return target;\n };\n","\nexport default function mask(value, precision = 2, decimalSeparator = '.', thousandSeparator = ',', allowNegative = false, prefix = '', suffix = ''){\n // provide some default values and arg validation.\n if (precision < 0) { precision = 0; } // precision cannot be negative\n if (precision > 20) { precision = 20; } // precision cannot be greater than 20\n \n if (value === null || value===undefined) {\n return {\n value: 0,\n maskedValue: ''\n };\n }\n \n value = String(value); //if the given value is a Number, let's convert into String to manipulate that\n\n if (value.length == 0) {\n return {\n value: 0,\n maskedValue: ''\n };\n }\n\n\n // extract digits. if no digits, fill in a zero.\n let digits = value.match(/\\d/g) || ['0'];\n \n let numberIsNegative = false;\n if (allowNegative) {\n let negativeSignCount = (value.match(/-/g) || []).length;\n // number will be negative if we have an odd number of \"-\"\n // ideally, we should only ever have 0, 1 or 2 (positive number, making a number negative\n // and making a negative number positive, respectively)\n numberIsNegative = negativeSignCount % 2 === 1;\n \n // if every digit in the array is '0', then the number should never be negative\n let allDigitsAreZero = true;\n for (let idx=0; idx < digits.length; idx += 1) {\n if(digits[idx] !== '0') {\n allDigitsAreZero = false;\n break;\n }\n }\n if (allDigitsAreZero) {\n numberIsNegative = false;\n }\n }\n\n // zero-pad a input\n while (digits.length <= precision) { digits.unshift('0'); }\n\n if (precision > 0) {\n // add the decimal separator\n digits.splice(digits.length - precision, 0, \".\");\n }\n\n // clean up extraneous digits like leading zeros.\n digits = Number(digits.join('')).toFixed(precision).split('');\n let raw = Number(digits.join(''));\n\n let decimalpos = digits.length - precision - 1; // -1 needed to position the decimal separator before the digits.\n if (precision > 0) {\n // set the final decimal separator\n digits[decimalpos] = decimalSeparator;\n } else {\n // when precision is 0, there is no decimal separator.\n decimalpos = digits.length;\n }\n\n // add in any thousand separators\n for (let x=decimalpos - 3; x > 0; x = x - 3) {\n digits.splice(x, 0, thousandSeparator);\n }\n\n // if we have a prefix or suffix, add them in.\n if (prefix.length > 0) { digits.unshift(prefix); }\n if (suffix.length > 0) { digits.push(suffix); }\n\n // if the number is negative, insert a \"-\" to\n // the front of the array and negate the raw value\n if (allowNegative && numberIsNegative) {\n digits.unshift('-');\n raw = -raw;\n }\n\n return {\n value: raw,\n maskedValue: digits.join('').trim()\n };\n}\n","import './object-assign-polyfill';\n\nimport PropTypes from 'prop-types';\nimport React, { Component } from 'react'\nimport ReactDOM from 'react-dom'\nimport mask from './mask.js'\n\n// IE* parseFloat polyfill\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/parseFloat#Polyfill\nNumber.parseFloat = parseFloat;\n\nclass CurrencyInput extends Component {\n constructor(props) {\n super(props);\n this.prepareProps = this.prepareProps.bind(this);\n this.handleChange = this.handleChange.bind(this);\n this.handleFocus = this.handleFocus.bind(this);\n this.state = this.prepareProps(this.props);\n\n this.inputSelectionStart = 1;\n this.inputSelectionEnd = 1;\n }\n\n\n /**\n * Exposes the current masked value.\n *\n * @returns {String}\n */\n getMaskedValue() {\n return this.state.maskedValue;\n }\n\n\n /**\n * General function used to cleanup and define the final props used for rendering\n * @returns {{ maskedValue: {String}, value: {Number}, customProps: {Object} }}\n */\n prepareProps(props) {\n let customProps = {...props}; // babeljs converts to Object.assign, then polyfills.\n delete customProps.onChange;\n delete customProps.onChangeEvent;\n delete customProps.value;\n delete customProps.decimalSeparator;\n delete customProps.thousandSeparator;\n delete customProps.precision;\n delete customProps.inputType;\n delete customProps.allowNegative;\n delete customProps.allowEmpty;\n delete customProps.prefix;\n delete customProps.suffix;\n delete customProps.selectAllOnFocus;\n delete customProps.autoFocus;\n\n let initialValue = props.value;\n if (initialValue === null) {\n initialValue = props.allowEmpty? null : '';\n }else{\n\n if (typeof initialValue == 'string') {\n // Some people, when confronted with a problem, think \"I know, I'll use regular expressions.\"\n // Now they have two problems.\n\n // Strip out thousand separators, prefix, and suffix, etc.\n if (props.thousandSeparator === \".\"){\n // special handle the . thousand separator\n initialValue = initialValue.replace(/\\./g, '');\n }\n\n if (props.decimalSeparator != \".\"){\n // fix the decimal separator\n initialValue = initialValue.replace(new RegExp(props.decimalSeparator, 'g'), '.');\n }\n\n //Strip out anything that is not a digit, -, or decimal separator\n initialValue = initialValue.replace(/[^0-9-.]/g, '');\n\n // now we can parse.\n initialValue = Number.parseFloat(initialValue);\n }\n initialValue = Number(initialValue).toLocaleString(undefined, {\n style : 'decimal',\n minimumFractionDigits: props.precision,\n maximumFractionDigits: props.precision\n })\n\n }\n\n const { maskedValue, value } = mask(\n initialValue,\n props.precision,\n props.decimalSeparator,\n props.thousandSeparator,\n props.allowNegative,\n props.prefix,\n props.suffix\n );\n\n return { maskedValue, value, customProps };\n }\n\n\n /**\n * Component lifecycle function.\n * Invoked when a component is receiving new props. This method is not called for the initial render.\n *\n * @param nextProps\n * @see https://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops\n */\n componentWillReceiveProps(nextProps) {\n this.setState(this.prepareProps(nextProps));\n }\n\n\n /**\n * Component lifecycle function.\n * @returns {XML}\n * @see https://facebook.github.io/react/docs/react-component.html#componentdidmount\n */\n componentDidMount(){\n let node = ReactDOM.findDOMNode(this.theInput);\n let selectionStart, selectionEnd;\n\n if (this.props.autoFocus) {\n this.theInput.focus();\n selectionEnd = this.state.maskedValue.length - this.props.suffix.length;\n selectionStart = selectionEnd;\n } else {\n selectionEnd = Math.min(node.selectionEnd, this.theInput.value.length - this.props.suffix.length);\n selectionStart = Math.min(node.selectionStart, selectionEnd);\n }\n\n node.setSelectionRange(selectionStart, selectionEnd);\n }\n\n\n /**\n * Component lifecycle function\n * @returns {XML}\n * @see https://facebook.github.io/react/docs/react-component.html#componentwillupdate\n */\n componentWillUpdate() {\n let node = ReactDOM.findDOMNode(this.theInput);\n this.inputSelectionStart = node.selectionStart;\n this.inputSelectionEnd = node.selectionEnd;\n }\n\n\n /**\n * Component lifecycle function.\n * @returns {XML}\n * @see https://facebook.github.io/react/docs/react-component.html#componentdidupdate\n */\n componentDidUpdate(prevProps, prevState){\n const { decimalSeparator } = this.props;\n let node = ReactDOM.findDOMNode(this.theInput);\n let isNegative = (this.theInput.value.match(/-/g) || []).length % 2 === 1;\n let minPos = this.props.prefix.length + (isNegative ? 1 : 0);\n let selectionEnd = Math.max(minPos, Math.min(this.inputSelectionEnd, this.theInput.value.length - this.props.suffix.length));\n let selectionStart = Math.max(minPos, Math.min(this.inputSelectionEnd, selectionEnd));\n\n let regexEscapeRegex = /[-[\\]{}()*+?.,\\\\^$|#\\s]/g;\n let separatorsRegex = new RegExp(decimalSeparator.replace(regexEscapeRegex, '\\\\$&') + '|' + this.props.thousandSeparator.replace(regexEscapeRegex, '\\\\$&'), 'g');\n let currSeparatorCount = (this.state.maskedValue.match(separatorsRegex) || []).length;\n let prevSeparatorCount = (prevState.maskedValue.match(separatorsRegex) || []).length;\n let adjustment = Math.max(currSeparatorCount - prevSeparatorCount, 0);\n\n selectionEnd = selectionEnd + adjustment;\n selectionStart = selectionStart + adjustment;\n\n const precision = Number(this.props.precision);\n\n let baselength = this.props.suffix.length\n + this.props.prefix.length\n + (precision > 0 ? decimalSeparator.length : 0) // if precision is 0 there will be no decimal part\n + precision\n + 1; // This is to account for the default '0' value that comes before the decimal separator\n\n if (this.state.maskedValue.length == baselength){\n // if we are already at base length, position the cursor at the end.\n selectionEnd = this.theInput.value.length - this.props.suffix.length;\n selectionStart = selectionEnd;\n }\n\n node.setSelectionRange(selectionStart, selectionEnd);\n this.inputSelectionStart = selectionStart;\n this.inputSelectionEnd = selectionEnd;\n }\n\n\n /**\n * onChange Event Handler\n * @param event\n */\n handleChange(event) {\n event.preventDefault();\n let { maskedValue, value } = mask(\n event.target.value,\n this.props.precision,\n this.props.decimalSeparator,\n this.props.thousandSeparator,\n this.props.allowNegative,\n this.props.prefix,\n this.props.suffix\n );\n\n event.persist(); // fixes issue #23\n\n this.setState({ maskedValue, value }, () => {\n this.props.onChange(maskedValue, value, event);\n this.props.onChangeEvent(event, maskedValue, value);\n });\n }\n\n\n /**\n * onFocus Event Handler\n * @param event\n */\n handleFocus(event) {\n if (!this.theInput) return;\n\n //Whenever we receive focus check to see if the position is before the suffix, if not, move it.\n let selectionEnd = this.theInput.value.length - this.props.suffix.length;\n let isNegative = (this.theInput.value.match(/-/g) || []).length % 2 === 1;\n let selectionStart = this.props.prefix.length + (isNegative ? 1 : 0);\n this.props.selectAllOnFocus && event.target.setSelectionRange(selectionStart, selectionEnd);\n this.inputSelectionStart = selectionStart;\n this.inputSelectionEnd = selectionEnd;\n }\n\n\n handleBlur(event) {\n this.inputSelectionStart = 0;\n this.inputSelectionEnd = 0;\n }\n\n\n /**\n * Component lifecycle function.\n * @returns {XML}\n * @see https://facebook.github.io/react/docs/component-specs.html#render\n */\n render() {\n return (\n { this.theInput = input; }}\n type={this.props.inputType}\n value={this.state.maskedValue}\n onChange={this.handleChange}\n onFocus={this.handleFocus}\n onMouseUp={this.handleFocus}\n {...this.state.customProps}\n />\n )\n }\n}\n\n\n\n/**\n * Prop validation.\n * @see https://facebook.github.io/react/docs/component-specs.html#proptypes\n */\n\nCurrencyInput.propTypes = {\n onChange: PropTypes.func,\n value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),\n decimalSeparator: PropTypes.string,\n thousandSeparator: PropTypes.string,\n precision: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),\n inputType: PropTypes.string,\n allowNegative: PropTypes.bool,\n allowEmpty: PropTypes.bool,\n prefix: PropTypes.string,\n suffix: PropTypes.string,\n selectAllOnFocus: PropTypes.bool\n};\n\n\nCurrencyInput.defaultProps = {\n onChange: function(maskValue, value, event) {/*no-op*/},\n onChangeEvent: function(event, maskValue, value) {/*no-op*/},\n autoFocus: false,\n value: '0',\n decimalSeparator: '.',\n thousandSeparator: ',',\n precision: '2',\n inputType: 'text',\n allowNegative: false,\n prefix: '',\n suffix: '',\n selectAllOnFocus: false\n};\n\n\nexport default CurrencyInput\n"],"names":["arguments","let","super","const","this","React","Component"],"mappings":"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"} -------------------------------------------------------------------------------- /lib/react-currency-input.es.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | 5 | Object.assign = Object.assign || 6 | function(target) { 7 | var arguments$1 = arguments; 8 | 9 | for (var i = 1; i < arguments.length; i++) { 10 | var source = arguments$1[i]; 11 | for (var key in source) { 12 | if (Object.prototype.hasOwnProperty.call(source, key)) { 13 | target[key] = source[key]; 14 | } 15 | } 16 | } 17 | return target; 18 | }; 19 | 20 | function mask(value, precision, decimalSeparator, thousandSeparator, allowNegative, prefix, suffix){ 21 | if ( precision === void 0 ) precision = 2; 22 | if ( decimalSeparator === void 0 ) decimalSeparator = '.'; 23 | if ( thousandSeparator === void 0 ) thousandSeparator = ','; 24 | if ( allowNegative === void 0 ) allowNegative = false; 25 | if ( prefix === void 0 ) prefix = ''; 26 | if ( suffix === void 0 ) suffix = ''; 27 | 28 | // provide some default values and arg validation. 29 | if (precision < 0) { precision = 0; } // precision cannot be negative 30 | if (precision > 20) { precision = 20; } // precision cannot be greater than 20 31 | 32 | if (value === null || value===undefined) { 33 | return { 34 | value: 0, 35 | maskedValue: '' 36 | }; 37 | } 38 | 39 | value = String(value); //if the given value is a Number, let's convert into String to manipulate that 40 | 41 | if (value.length == 0) { 42 | return { 43 | value: 0, 44 | maskedValue: '' 45 | }; 46 | } 47 | 48 | 49 | // extract digits. if no digits, fill in a zero. 50 | var digits = value.match(/\d/g) || ['0']; 51 | 52 | var numberIsNegative = false; 53 | if (allowNegative) { 54 | var negativeSignCount = (value.match(/-/g) || []).length; 55 | // number will be negative if we have an odd number of "-" 56 | // ideally, we should only ever have 0, 1 or 2 (positive number, making a number negative 57 | // and making a negative number positive, respectively) 58 | numberIsNegative = negativeSignCount % 2 === 1; 59 | 60 | // if every digit in the array is '0', then the number should never be negative 61 | var allDigitsAreZero = true; 62 | for (var idx=0; idx < digits.length; idx += 1) { 63 | if(digits[idx] !== '0') { 64 | allDigitsAreZero = false; 65 | break; 66 | } 67 | } 68 | if (allDigitsAreZero) { 69 | numberIsNegative = false; 70 | } 71 | } 72 | 73 | // zero-pad a input 74 | while (digits.length <= precision) { digits.unshift('0'); } 75 | 76 | if (precision > 0) { 77 | // add the decimal separator 78 | digits.splice(digits.length - precision, 0, "."); 79 | } 80 | 81 | // clean up extraneous digits like leading zeros. 82 | digits = Number(digits.join('')).toFixed(precision).split(''); 83 | var raw = Number(digits.join('')); 84 | 85 | var decimalpos = digits.length - precision - 1; // -1 needed to position the decimal separator before the digits. 86 | if (precision > 0) { 87 | // set the final decimal separator 88 | digits[decimalpos] = decimalSeparator; 89 | } else { 90 | // when precision is 0, there is no decimal separator. 91 | decimalpos = digits.length; 92 | } 93 | 94 | // add in any thousand separators 95 | for (var x=decimalpos - 3; x > 0; x = x - 3) { 96 | digits.splice(x, 0, thousandSeparator); 97 | } 98 | 99 | // if we have a prefix or suffix, add them in. 100 | if (prefix.length > 0) { digits.unshift(prefix); } 101 | if (suffix.length > 0) { digits.push(suffix); } 102 | 103 | // if the number is negative, insert a "-" to 104 | // the front of the array and negate the raw value 105 | if (allowNegative && numberIsNegative) { 106 | digits.unshift('-'); 107 | raw = -raw; 108 | } 109 | 110 | return { 111 | value: raw, 112 | maskedValue: digits.join('').trim() 113 | }; 114 | } 115 | 116 | // IE* parseFloat polyfill 117 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/parseFloat#Polyfill 118 | Number.parseFloat = parseFloat; 119 | 120 | var CurrencyInput = (function (Component$$1) { 121 | function CurrencyInput(props) { 122 | Component$$1.call(this, props); 123 | this.prepareProps = this.prepareProps.bind(this); 124 | this.handleChange = this.handleChange.bind(this); 125 | this.handleFocus = this.handleFocus.bind(this); 126 | this.state = this.prepareProps(this.props); 127 | 128 | this.inputSelectionStart = 1; 129 | this.inputSelectionEnd = 1; 130 | } 131 | 132 | if ( Component$$1 ) CurrencyInput.__proto__ = Component$$1; 133 | CurrencyInput.prototype = Object.create( Component$$1 && Component$$1.prototype ); 134 | CurrencyInput.prototype.constructor = CurrencyInput; 135 | 136 | 137 | /** 138 | * Exposes the current masked value. 139 | * 140 | * @returns {String} 141 | */ 142 | CurrencyInput.prototype.getMaskedValue = function getMaskedValue () { 143 | return this.state.maskedValue; 144 | }; 145 | 146 | 147 | /** 148 | * General function used to cleanup and define the final props used for rendering 149 | * @returns {{ maskedValue: {String}, value: {Number}, customProps: {Object} }} 150 | */ 151 | CurrencyInput.prototype.prepareProps = function prepareProps (props) { 152 | var customProps = Object.assign({}, props); // babeljs converts to Object.assign, then polyfills. 153 | delete customProps.onChange; 154 | delete customProps.onChangeEvent; 155 | delete customProps.value; 156 | delete customProps.decimalSeparator; 157 | delete customProps.thousandSeparator; 158 | delete customProps.precision; 159 | delete customProps.inputType; 160 | delete customProps.allowNegative; 161 | delete customProps.allowEmpty; 162 | delete customProps.prefix; 163 | delete customProps.suffix; 164 | delete customProps.selectAllOnFocus; 165 | delete customProps.autoFocus; 166 | 167 | var initialValue = props.value; 168 | if (initialValue === null) { 169 | initialValue = props.allowEmpty? null : ''; 170 | }else{ 171 | 172 | if (typeof initialValue == 'string') { 173 | // Some people, when confronted with a problem, think "I know, I'll use regular expressions." 174 | // Now they have two problems. 175 | 176 | // Strip out thousand separators, prefix, and suffix, etc. 177 | if (props.thousandSeparator === "."){ 178 | // special handle the . thousand separator 179 | initialValue = initialValue.replace(/\./g, ''); 180 | } 181 | 182 | if (props.decimalSeparator != "."){ 183 | // fix the decimal separator 184 | initialValue = initialValue.replace(new RegExp(props.decimalSeparator, 'g'), '.'); 185 | } 186 | 187 | //Strip out anything that is not a digit, -, or decimal separator 188 | initialValue = initialValue.replace(/[^0-9-.]/g, ''); 189 | 190 | // now we can parse. 191 | initialValue = Number.parseFloat(initialValue); 192 | } 193 | initialValue = Number(initialValue).toLocaleString(undefined, { 194 | style : 'decimal', 195 | minimumFractionDigits: props.precision, 196 | maximumFractionDigits: props.precision 197 | }); 198 | 199 | } 200 | 201 | var ref = mask( 202 | initialValue, 203 | props.precision, 204 | props.decimalSeparator, 205 | props.thousandSeparator, 206 | props.allowNegative, 207 | props.prefix, 208 | props.suffix 209 | ); 210 | var maskedValue = ref.maskedValue; 211 | var value = ref.value; 212 | 213 | return { maskedValue: maskedValue, value: value, customProps: customProps }; 214 | }; 215 | 216 | 217 | /** 218 | * Component lifecycle function. 219 | * Invoked when a component is receiving new props. This method is not called for the initial render. 220 | * 221 | * @param nextProps 222 | * @see https://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops 223 | */ 224 | CurrencyInput.prototype.componentWillReceiveProps = function componentWillReceiveProps (nextProps) { 225 | this.setState(this.prepareProps(nextProps)); 226 | }; 227 | 228 | 229 | /** 230 | * Component lifecycle function. 231 | * @returns {XML} 232 | * @see https://facebook.github.io/react/docs/react-component.html#componentdidmount 233 | */ 234 | CurrencyInput.prototype.componentDidMount = function componentDidMount (){ 235 | var node = ReactDOM.findDOMNode(this.theInput); 236 | var selectionStart, selectionEnd; 237 | 238 | if (this.props.autoFocus) { 239 | this.theInput.focus(); 240 | selectionEnd = this.state.maskedValue.length - this.props.suffix.length; 241 | selectionStart = selectionEnd; 242 | } else { 243 | selectionEnd = Math.min(node.selectionEnd, this.theInput.value.length - this.props.suffix.length); 244 | selectionStart = Math.min(node.selectionStart, selectionEnd); 245 | } 246 | 247 | node.setSelectionRange(selectionStart, selectionEnd); 248 | }; 249 | 250 | 251 | /** 252 | * Component lifecycle function 253 | * @returns {XML} 254 | * @see https://facebook.github.io/react/docs/react-component.html#componentwillupdate 255 | */ 256 | CurrencyInput.prototype.componentWillUpdate = function componentWillUpdate () { 257 | var node = ReactDOM.findDOMNode(this.theInput); 258 | this.inputSelectionStart = node.selectionStart; 259 | this.inputSelectionEnd = node.selectionEnd; 260 | }; 261 | 262 | 263 | /** 264 | * Component lifecycle function. 265 | * @returns {XML} 266 | * @see https://facebook.github.io/react/docs/react-component.html#componentdidupdate 267 | */ 268 | CurrencyInput.prototype.componentDidUpdate = function componentDidUpdate (prevProps, prevState){ 269 | var ref = this.props; 270 | var decimalSeparator = ref.decimalSeparator; 271 | var node = ReactDOM.findDOMNode(this.theInput); 272 | var isNegative = (this.theInput.value.match(/-/g) || []).length % 2 === 1; 273 | var minPos = this.props.prefix.length + (isNegative ? 1 : 0); 274 | var selectionEnd = Math.max(minPos, Math.min(this.inputSelectionEnd, this.theInput.value.length - this.props.suffix.length)); 275 | var selectionStart = Math.max(minPos, Math.min(this.inputSelectionEnd, selectionEnd)); 276 | 277 | var regexEscapeRegex = /[-[\]{}()*+?.,\\^$|#\s]/g; 278 | var separatorsRegex = new RegExp(decimalSeparator.replace(regexEscapeRegex, '\\$&') + '|' + this.props.thousandSeparator.replace(regexEscapeRegex, '\\$&'), 'g'); 279 | var currSeparatorCount = (this.state.maskedValue.match(separatorsRegex) || []).length; 280 | var prevSeparatorCount = (prevState.maskedValue.match(separatorsRegex) || []).length; 281 | var adjustment = Math.max(currSeparatorCount - prevSeparatorCount, 0); 282 | 283 | selectionEnd = selectionEnd + adjustment; 284 | selectionStart = selectionStart + adjustment; 285 | 286 | var precision = Number(this.props.precision); 287 | 288 | var baselength = this.props.suffix.length 289 | + this.props.prefix.length 290 | + (precision > 0 ? decimalSeparator.length : 0) // if precision is 0 there will be no decimal part 291 | + precision 292 | + 1; // This is to account for the default '0' value that comes before the decimal separator 293 | 294 | if (this.state.maskedValue.length == baselength){ 295 | // if we are already at base length, position the cursor at the end. 296 | selectionEnd = this.theInput.value.length - this.props.suffix.length; 297 | selectionStart = selectionEnd; 298 | } 299 | 300 | node.setSelectionRange(selectionStart, selectionEnd); 301 | this.inputSelectionStart = selectionStart; 302 | this.inputSelectionEnd = selectionEnd; 303 | }; 304 | 305 | 306 | /** 307 | * onChange Event Handler 308 | * @param event 309 | */ 310 | CurrencyInput.prototype.handleChange = function handleChange (event) { 311 | var this$1 = this; 312 | 313 | event.preventDefault(); 314 | var ref = mask( 315 | event.target.value, 316 | this.props.precision, 317 | this.props.decimalSeparator, 318 | this.props.thousandSeparator, 319 | this.props.allowNegative, 320 | this.props.prefix, 321 | this.props.suffix 322 | ); 323 | var maskedValue = ref.maskedValue; 324 | var value = ref.value; 325 | 326 | event.persist(); // fixes issue #23 327 | 328 | this.setState({ maskedValue: maskedValue, value: value }, function () { 329 | this$1.props.onChange(maskedValue, value, event); 330 | this$1.props.onChangeEvent(event, maskedValue, value); 331 | }); 332 | }; 333 | 334 | 335 | /** 336 | * onFocus Event Handler 337 | * @param event 338 | */ 339 | CurrencyInput.prototype.handleFocus = function handleFocus (event) { 340 | if (!this.theInput) { return; } 341 | 342 | //Whenever we receive focus check to see if the position is before the suffix, if not, move it. 343 | var selectionEnd = this.theInput.value.length - this.props.suffix.length; 344 | var isNegative = (this.theInput.value.match(/-/g) || []).length % 2 === 1; 345 | var selectionStart = this.props.prefix.length + (isNegative ? 1 : 0); 346 | this.props.selectAllOnFocus && event.target.setSelectionRange(selectionStart, selectionEnd); 347 | this.inputSelectionStart = selectionStart; 348 | this.inputSelectionEnd = selectionEnd; 349 | }; 350 | 351 | 352 | CurrencyInput.prototype.handleBlur = function handleBlur (event) { 353 | this.inputSelectionStart = 0; 354 | this.inputSelectionEnd = 0; 355 | }; 356 | 357 | 358 | /** 359 | * Component lifecycle function. 360 | * @returns {XML} 361 | * @see https://facebook.github.io/react/docs/component-specs.html#render 362 | */ 363 | CurrencyInput.prototype.render = function render () { 364 | var this$1 = this; 365 | 366 | return ( 367 | React.createElement( 'input', Object.assign({}, 368 | { ref: function (input) { this$1.theInput = input; }, type: this.props.inputType, value: this.state.maskedValue, onChange: this.handleChange, onFocus: this.handleFocus, onMouseUp: this.handleFocus }, this.state.customProps)) 369 | ) 370 | }; 371 | 372 | return CurrencyInput; 373 | }(Component)); 374 | 375 | 376 | 377 | /** 378 | * Prop validation. 379 | * @see https://facebook.github.io/react/docs/component-specs.html#proptypes 380 | */ 381 | 382 | CurrencyInput.propTypes = { 383 | onChange: PropTypes.func, 384 | value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 385 | decimalSeparator: PropTypes.string, 386 | thousandSeparator: PropTypes.string, 387 | precision: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 388 | inputType: PropTypes.string, 389 | allowNegative: PropTypes.bool, 390 | allowEmpty: PropTypes.bool, 391 | prefix: PropTypes.string, 392 | suffix: PropTypes.string, 393 | selectAllOnFocus: PropTypes.bool 394 | }; 395 | 396 | 397 | CurrencyInput.defaultProps = { 398 | onChange: function(maskValue, value, event) {/*no-op*/}, 399 | onChangeEvent: function(event, maskValue, value) {/*no-op*/}, 400 | autoFocus: false, 401 | value: '0', 402 | decimalSeparator: '.', 403 | thousandSeparator: ',', 404 | precision: '2', 405 | inputType: 'text', 406 | allowNegative: false, 407 | prefix: '', 408 | suffix: '', 409 | selectAllOnFocus: false 410 | }; 411 | 412 | export default CurrencyInput; 413 | //# sourceMappingURL=react-currency-input.es.js.map 414 | -------------------------------------------------------------------------------- /lib/react-currency-input.es.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"react-currency-input.es.js","sources":["../src/object-assign-polyfill.js","../src/mask.js","../src/index.js"],"sourcesContent":["Object.assign = Object.assign ||\n function(target) {\n for (var i = 1; i < arguments.length; i++) {\n var source = arguments[i];\n for (var key in source) {\n if (Object.prototype.hasOwnProperty.call(source, key)) {\n target[key] = source[key];\n }\n }\n }\n return target;\n };\n","\nexport default function mask(value, precision = 2, decimalSeparator = '.', thousandSeparator = ',', allowNegative = false, prefix = '', suffix = ''){\n // provide some default values and arg validation.\n if (precision < 0) { precision = 0; } // precision cannot be negative\n if (precision > 20) { precision = 20; } // precision cannot be greater than 20\n \n if (value === null || value===undefined) {\n return {\n value: 0,\n maskedValue: ''\n };\n }\n \n value = String(value); //if the given value is a Number, let's convert into String to manipulate that\n\n if (value.length == 0) {\n return {\n value: 0,\n maskedValue: ''\n };\n }\n\n\n // extract digits. if no digits, fill in a zero.\n let digits = value.match(/\\d/g) || ['0'];\n \n let numberIsNegative = false;\n if (allowNegative) {\n let negativeSignCount = (value.match(/-/g) || []).length;\n // number will be negative if we have an odd number of \"-\"\n // ideally, we should only ever have 0, 1 or 2 (positive number, making a number negative\n // and making a negative number positive, respectively)\n numberIsNegative = negativeSignCount % 2 === 1;\n \n // if every digit in the array is '0', then the number should never be negative\n let allDigitsAreZero = true;\n for (let idx=0; idx < digits.length; idx += 1) {\n if(digits[idx] !== '0') {\n allDigitsAreZero = false;\n break;\n }\n }\n if (allDigitsAreZero) {\n numberIsNegative = false;\n }\n }\n\n // zero-pad a input\n while (digits.length <= precision) { digits.unshift('0'); }\n\n if (precision > 0) {\n // add the decimal separator\n digits.splice(digits.length - precision, 0, \".\");\n }\n\n // clean up extraneous digits like leading zeros.\n digits = Number(digits.join('')).toFixed(precision).split('');\n let raw = Number(digits.join(''));\n\n let decimalpos = digits.length - precision - 1; // -1 needed to position the decimal separator before the digits.\n if (precision > 0) {\n // set the final decimal separator\n digits[decimalpos] = decimalSeparator;\n } else {\n // when precision is 0, there is no decimal separator.\n decimalpos = digits.length;\n }\n\n // add in any thousand separators\n for (let x=decimalpos - 3; x > 0; x = x - 3) {\n digits.splice(x, 0, thousandSeparator);\n }\n\n // if we have a prefix or suffix, add them in.\n if (prefix.length > 0) { digits.unshift(prefix); }\n if (suffix.length > 0) { digits.push(suffix); }\n\n // if the number is negative, insert a \"-\" to\n // the front of the array and negate the raw value\n if (allowNegative && numberIsNegative) {\n digits.unshift('-');\n raw = -raw;\n }\n\n return {\n value: raw,\n maskedValue: digits.join('').trim()\n };\n}\n","import './object-assign-polyfill';\n\nimport PropTypes from 'prop-types';\nimport React, { Component } from 'react'\nimport ReactDOM from 'react-dom'\nimport mask from './mask.js'\n\n// IE* parseFloat polyfill\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/parseFloat#Polyfill\nNumber.parseFloat = parseFloat;\n\nclass CurrencyInput extends Component {\n constructor(props) {\n super(props);\n this.prepareProps = this.prepareProps.bind(this);\n this.handleChange = this.handleChange.bind(this);\n this.handleFocus = this.handleFocus.bind(this);\n this.state = this.prepareProps(this.props);\n\n this.inputSelectionStart = 1;\n this.inputSelectionEnd = 1;\n }\n\n\n /**\n * Exposes the current masked value.\n *\n * @returns {String}\n */\n getMaskedValue() {\n return this.state.maskedValue;\n }\n\n\n /**\n * General function used to cleanup and define the final props used for rendering\n * @returns {{ maskedValue: {String}, value: {Number}, customProps: {Object} }}\n */\n prepareProps(props) {\n let customProps = {...props}; // babeljs converts to Object.assign, then polyfills.\n delete customProps.onChange;\n delete customProps.onChangeEvent;\n delete customProps.value;\n delete customProps.decimalSeparator;\n delete customProps.thousandSeparator;\n delete customProps.precision;\n delete customProps.inputType;\n delete customProps.allowNegative;\n delete customProps.allowEmpty;\n delete customProps.prefix;\n delete customProps.suffix;\n delete customProps.selectAllOnFocus;\n delete customProps.autoFocus;\n\n let initialValue = props.value;\n if (initialValue === null) {\n initialValue = props.allowEmpty? null : '';\n }else{\n\n if (typeof initialValue == 'string') {\n // Some people, when confronted with a problem, think \"I know, I'll use regular expressions.\"\n // Now they have two problems.\n\n // Strip out thousand separators, prefix, and suffix, etc.\n if (props.thousandSeparator === \".\"){\n // special handle the . thousand separator\n initialValue = initialValue.replace(/\\./g, '');\n }\n\n if (props.decimalSeparator != \".\"){\n // fix the decimal separator\n initialValue = initialValue.replace(new RegExp(props.decimalSeparator, 'g'), '.');\n }\n\n //Strip out anything that is not a digit, -, or decimal separator\n initialValue = initialValue.replace(/[^0-9-.]/g, '');\n\n // now we can parse.\n initialValue = Number.parseFloat(initialValue);\n }\n initialValue = Number(initialValue).toLocaleString(undefined, {\n style : 'decimal',\n minimumFractionDigits: props.precision,\n maximumFractionDigits: props.precision\n })\n\n }\n\n const { maskedValue, value } = mask(\n initialValue,\n props.precision,\n props.decimalSeparator,\n props.thousandSeparator,\n props.allowNegative,\n props.prefix,\n props.suffix\n );\n\n return { maskedValue, value, customProps };\n }\n\n\n /**\n * Component lifecycle function.\n * Invoked when a component is receiving new props. This method is not called for the initial render.\n *\n * @param nextProps\n * @see https://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops\n */\n componentWillReceiveProps(nextProps) {\n this.setState(this.prepareProps(nextProps));\n }\n\n\n /**\n * Component lifecycle function.\n * @returns {XML}\n * @see https://facebook.github.io/react/docs/react-component.html#componentdidmount\n */\n componentDidMount(){\n let node = ReactDOM.findDOMNode(this.theInput);\n let selectionStart, selectionEnd;\n\n if (this.props.autoFocus) {\n this.theInput.focus();\n selectionEnd = this.state.maskedValue.length - this.props.suffix.length;\n selectionStart = selectionEnd;\n } else {\n selectionEnd = Math.min(node.selectionEnd, this.theInput.value.length - this.props.suffix.length);\n selectionStart = Math.min(node.selectionStart, selectionEnd);\n }\n\n node.setSelectionRange(selectionStart, selectionEnd);\n }\n\n\n /**\n * Component lifecycle function\n * @returns {XML}\n * @see https://facebook.github.io/react/docs/react-component.html#componentwillupdate\n */\n componentWillUpdate() {\n let node = ReactDOM.findDOMNode(this.theInput);\n this.inputSelectionStart = node.selectionStart;\n this.inputSelectionEnd = node.selectionEnd;\n }\n\n\n /**\n * Component lifecycle function.\n * @returns {XML}\n * @see https://facebook.github.io/react/docs/react-component.html#componentdidupdate\n */\n componentDidUpdate(prevProps, prevState){\n const { decimalSeparator } = this.props;\n let node = ReactDOM.findDOMNode(this.theInput);\n let isNegative = (this.theInput.value.match(/-/g) || []).length % 2 === 1;\n let minPos = this.props.prefix.length + (isNegative ? 1 : 0);\n let selectionEnd = Math.max(minPos, Math.min(this.inputSelectionEnd, this.theInput.value.length - this.props.suffix.length));\n let selectionStart = Math.max(minPos, Math.min(this.inputSelectionEnd, selectionEnd));\n\n let regexEscapeRegex = /[-[\\]{}()*+?.,\\\\^$|#\\s]/g;\n let separatorsRegex = new RegExp(decimalSeparator.replace(regexEscapeRegex, '\\\\$&') + '|' + this.props.thousandSeparator.replace(regexEscapeRegex, '\\\\$&'), 'g');\n let currSeparatorCount = (this.state.maskedValue.match(separatorsRegex) || []).length;\n let prevSeparatorCount = (prevState.maskedValue.match(separatorsRegex) || []).length;\n let adjustment = Math.max(currSeparatorCount - prevSeparatorCount, 0);\n\n selectionEnd = selectionEnd + adjustment;\n selectionStart = selectionStart + adjustment;\n\n const precision = Number(this.props.precision);\n\n let baselength = this.props.suffix.length\n + this.props.prefix.length\n + (precision > 0 ? decimalSeparator.length : 0) // if precision is 0 there will be no decimal part\n + precision\n + 1; // This is to account for the default '0' value that comes before the decimal separator\n\n if (this.state.maskedValue.length == baselength){\n // if we are already at base length, position the cursor at the end.\n selectionEnd = this.theInput.value.length - this.props.suffix.length;\n selectionStart = selectionEnd;\n }\n\n node.setSelectionRange(selectionStart, selectionEnd);\n this.inputSelectionStart = selectionStart;\n this.inputSelectionEnd = selectionEnd;\n }\n\n\n /**\n * onChange Event Handler\n * @param event\n */\n handleChange(event) {\n event.preventDefault();\n let { maskedValue, value } = mask(\n event.target.value,\n this.props.precision,\n this.props.decimalSeparator,\n this.props.thousandSeparator,\n this.props.allowNegative,\n this.props.prefix,\n this.props.suffix\n );\n\n event.persist(); // fixes issue #23\n\n this.setState({ maskedValue, value }, () => {\n this.props.onChange(maskedValue, value, event);\n this.props.onChangeEvent(event, maskedValue, value);\n });\n }\n\n\n /**\n * onFocus Event Handler\n * @param event\n */\n handleFocus(event) {\n if (!this.theInput) return;\n\n //Whenever we receive focus check to see if the position is before the suffix, if not, move it.\n let selectionEnd = this.theInput.value.length - this.props.suffix.length;\n let isNegative = (this.theInput.value.match(/-/g) || []).length % 2 === 1;\n let selectionStart = this.props.prefix.length + (isNegative ? 1 : 0);\n this.props.selectAllOnFocus && event.target.setSelectionRange(selectionStart, selectionEnd);\n this.inputSelectionStart = selectionStart;\n this.inputSelectionEnd = selectionEnd;\n }\n\n\n handleBlur(event) {\n this.inputSelectionStart = 0;\n this.inputSelectionEnd = 0;\n }\n\n\n /**\n * Component lifecycle function.\n * @returns {XML}\n * @see https://facebook.github.io/react/docs/component-specs.html#render\n */\n render() {\n return (\n { this.theInput = input; }}\n type={this.props.inputType}\n value={this.state.maskedValue}\n onChange={this.handleChange}\n onFocus={this.handleFocus}\n onMouseUp={this.handleFocus}\n {...this.state.customProps}\n />\n )\n }\n}\n\n\n\n/**\n * Prop validation.\n * @see https://facebook.github.io/react/docs/component-specs.html#proptypes\n */\n\nCurrencyInput.propTypes = {\n onChange: PropTypes.func,\n value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),\n decimalSeparator: PropTypes.string,\n thousandSeparator: PropTypes.string,\n precision: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),\n inputType: PropTypes.string,\n allowNegative: PropTypes.bool,\n allowEmpty: PropTypes.bool,\n prefix: PropTypes.string,\n suffix: PropTypes.string,\n selectAllOnFocus: PropTypes.bool\n};\n\n\nCurrencyInput.defaultProps = {\n onChange: function(maskValue, value, event) {/*no-op*/},\n onChangeEvent: function(event, maskValue, value) {/*no-op*/},\n autoFocus: false,\n value: '0',\n decimalSeparator: '.',\n thousandSeparator: ',',\n precision: '2',\n inputType: 'text',\n allowNegative: false,\n prefix: '',\n suffix: '',\n selectAllOnFocus: false\n};\n\n\nexport default CurrencyInput\n"],"names":["arguments","let","super","const","this"],"mappings":"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"} -------------------------------------------------------------------------------- /lib/react-currency-input.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("prop-types"),require("react"),require("react-dom")):"function"==typeof define&&define.amd?define(["prop-types","react","react-dom"],t):e["react-currency-input"]=t(e.PropTypes,e.React,e.ReactDOM)}(this,function(e,t,g){"use strict";e=e&&e.hasOwnProperty("default")?e.default:e;var n="default"in t?t.default:t;function o(e,t,n,i,a,o,s){if(void 0===t&&(t=2),void 0===n&&(n="."),void 0===i&&(i=","),void 0===a&&(a=!1),void 0===o&&(o=""),void 0===s&&(s=""),t<0&&(t=0),20 20) { precision = 20; } // precision cannot be greater than 20\n \n if (value === null || value===undefined) {\n return {\n value: 0,\n maskedValue: ''\n };\n }\n \n value = String(value); //if the given value is a Number, let's convert into String to manipulate that\n\n if (value.length == 0) {\n return {\n value: 0,\n maskedValue: ''\n };\n }\n\n\n // extract digits. if no digits, fill in a zero.\n let digits = value.match(/\\d/g) || ['0'];\n \n let numberIsNegative = false;\n if (allowNegative) {\n let negativeSignCount = (value.match(/-/g) || []).length;\n // number will be negative if we have an odd number of \"-\"\n // ideally, we should only ever have 0, 1 or 2 (positive number, making a number negative\n // and making a negative number positive, respectively)\n numberIsNegative = negativeSignCount % 2 === 1;\n \n // if every digit in the array is '0', then the number should never be negative\n let allDigitsAreZero = true;\n for (let idx=0; idx < digits.length; idx += 1) {\n if(digits[idx] !== '0') {\n allDigitsAreZero = false;\n break;\n }\n }\n if (allDigitsAreZero) {\n numberIsNegative = false;\n }\n }\n\n // zero-pad a input\n while (digits.length <= precision) { digits.unshift('0'); }\n\n if (precision > 0) {\n // add the decimal separator\n digits.splice(digits.length - precision, 0, \".\");\n }\n\n // clean up extraneous digits like leading zeros.\n digits = Number(digits.join('')).toFixed(precision).split('');\n let raw = Number(digits.join(''));\n\n let decimalpos = digits.length - precision - 1; // -1 needed to position the decimal separator before the digits.\n if (precision > 0) {\n // set the final decimal separator\n digits[decimalpos] = decimalSeparator;\n } else {\n // when precision is 0, there is no decimal separator.\n decimalpos = digits.length;\n }\n\n // add in any thousand separators\n for (let x=decimalpos - 3; x > 0; x = x - 3) {\n digits.splice(x, 0, thousandSeparator);\n }\n\n // if we have a prefix or suffix, add them in.\n if (prefix.length > 0) { digits.unshift(prefix); }\n if (suffix.length > 0) { digits.push(suffix); }\n\n // if the number is negative, insert a \"-\" to\n // the front of the array and negate the raw value\n if (allowNegative && numberIsNegative) {\n digits.unshift('-');\n raw = -raw;\n }\n\n return {\n value: raw,\n maskedValue: digits.join('').trim()\n };\n}\n","Object.assign = Object.assign ||\n function(target) {\n for (var i = 1; i < arguments.length; i++) {\n var source = arguments[i];\n for (var key in source) {\n if (Object.prototype.hasOwnProperty.call(source, key)) {\n target[key] = source[key];\n }\n }\n }\n return target;\n };\n","import './object-assign-polyfill';\n\nimport PropTypes from 'prop-types';\nimport React, { Component } from 'react'\nimport ReactDOM from 'react-dom'\nimport mask from './mask.js'\n\n// IE* parseFloat polyfill\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/parseFloat#Polyfill\nNumber.parseFloat = parseFloat;\n\nclass CurrencyInput extends Component {\n constructor(props) {\n super(props);\n this.prepareProps = this.prepareProps.bind(this);\n this.handleChange = this.handleChange.bind(this);\n this.handleFocus = this.handleFocus.bind(this);\n this.state = this.prepareProps(this.props);\n\n this.inputSelectionStart = 1;\n this.inputSelectionEnd = 1;\n }\n\n\n /**\n * Exposes the current masked value.\n *\n * @returns {String}\n */\n getMaskedValue() {\n return this.state.maskedValue;\n }\n\n\n /**\n * General function used to cleanup and define the final props used for rendering\n * @returns {{ maskedValue: {String}, value: {Number}, customProps: {Object} }}\n */\n prepareProps(props) {\n let customProps = {...props}; // babeljs converts to Object.assign, then polyfills.\n delete customProps.onChange;\n delete customProps.onChangeEvent;\n delete customProps.value;\n delete customProps.decimalSeparator;\n delete customProps.thousandSeparator;\n delete customProps.precision;\n delete customProps.inputType;\n delete customProps.allowNegative;\n delete customProps.allowEmpty;\n delete customProps.prefix;\n delete customProps.suffix;\n delete customProps.selectAllOnFocus;\n delete customProps.autoFocus;\n\n let initialValue = props.value;\n if (initialValue === null) {\n initialValue = props.allowEmpty? null : '';\n }else{\n\n if (typeof initialValue == 'string') {\n // Some people, when confronted with a problem, think \"I know, I'll use regular expressions.\"\n // Now they have two problems.\n\n // Strip out thousand separators, prefix, and suffix, etc.\n if (props.thousandSeparator === \".\"){\n // special handle the . thousand separator\n initialValue = initialValue.replace(/\\./g, '');\n }\n\n if (props.decimalSeparator != \".\"){\n // fix the decimal separator\n initialValue = initialValue.replace(new RegExp(props.decimalSeparator, 'g'), '.');\n }\n\n //Strip out anything that is not a digit, -, or decimal separator\n initialValue = initialValue.replace(/[^0-9-.]/g, '');\n\n // now we can parse.\n initialValue = Number.parseFloat(initialValue);\n }\n initialValue = Number(initialValue).toLocaleString(undefined, {\n style : 'decimal',\n minimumFractionDigits: props.precision,\n maximumFractionDigits: props.precision\n })\n\n }\n\n const { maskedValue, value } = mask(\n initialValue,\n props.precision,\n props.decimalSeparator,\n props.thousandSeparator,\n props.allowNegative,\n props.prefix,\n props.suffix\n );\n\n return { maskedValue, value, customProps };\n }\n\n\n /**\n * Component lifecycle function.\n * Invoked when a component is receiving new props. This method is not called for the initial render.\n *\n * @param nextProps\n * @see https://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops\n */\n componentWillReceiveProps(nextProps) {\n this.setState(this.prepareProps(nextProps));\n }\n\n\n /**\n * Component lifecycle function.\n * @returns {XML}\n * @see https://facebook.github.io/react/docs/react-component.html#componentdidmount\n */\n componentDidMount(){\n let node = ReactDOM.findDOMNode(this.theInput);\n let selectionStart, selectionEnd;\n\n if (this.props.autoFocus) {\n this.theInput.focus();\n selectionEnd = this.state.maskedValue.length - this.props.suffix.length;\n selectionStart = selectionEnd;\n } else {\n selectionEnd = Math.min(node.selectionEnd, this.theInput.value.length - this.props.suffix.length);\n selectionStart = Math.min(node.selectionStart, selectionEnd);\n }\n\n node.setSelectionRange(selectionStart, selectionEnd);\n }\n\n\n /**\n * Component lifecycle function\n * @returns {XML}\n * @see https://facebook.github.io/react/docs/react-component.html#componentwillupdate\n */\n componentWillUpdate() {\n let node = ReactDOM.findDOMNode(this.theInput);\n this.inputSelectionStart = node.selectionStart;\n this.inputSelectionEnd = node.selectionEnd;\n }\n\n\n /**\n * Component lifecycle function.\n * @returns {XML}\n * @see https://facebook.github.io/react/docs/react-component.html#componentdidupdate\n */\n componentDidUpdate(prevProps, prevState){\n const { decimalSeparator } = this.props;\n let node = ReactDOM.findDOMNode(this.theInput);\n let isNegative = (this.theInput.value.match(/-/g) || []).length % 2 === 1;\n let minPos = this.props.prefix.length + (isNegative ? 1 : 0);\n let selectionEnd = Math.max(minPos, Math.min(this.inputSelectionEnd, this.theInput.value.length - this.props.suffix.length));\n let selectionStart = Math.max(minPos, Math.min(this.inputSelectionEnd, selectionEnd));\n\n let regexEscapeRegex = /[-[\\]{}()*+?.,\\\\^$|#\\s]/g;\n let separatorsRegex = new RegExp(decimalSeparator.replace(regexEscapeRegex, '\\\\$&') + '|' + this.props.thousandSeparator.replace(regexEscapeRegex, '\\\\$&'), 'g');\n let currSeparatorCount = (this.state.maskedValue.match(separatorsRegex) || []).length;\n let prevSeparatorCount = (prevState.maskedValue.match(separatorsRegex) || []).length;\n let adjustment = Math.max(currSeparatorCount - prevSeparatorCount, 0);\n\n selectionEnd = selectionEnd + adjustment;\n selectionStart = selectionStart + adjustment;\n\n const precision = Number(this.props.precision);\n\n let baselength = this.props.suffix.length\n + this.props.prefix.length\n + (precision > 0 ? decimalSeparator.length : 0) // if precision is 0 there will be no decimal part\n + precision\n + 1; // This is to account for the default '0' value that comes before the decimal separator\n\n if (this.state.maskedValue.length == baselength){\n // if we are already at base length, position the cursor at the end.\n selectionEnd = this.theInput.value.length - this.props.suffix.length;\n selectionStart = selectionEnd;\n }\n\n node.setSelectionRange(selectionStart, selectionEnd);\n this.inputSelectionStart = selectionStart;\n this.inputSelectionEnd = selectionEnd;\n }\n\n\n /**\n * onChange Event Handler\n * @param event\n */\n handleChange(event) {\n event.preventDefault();\n let { maskedValue, value } = mask(\n event.target.value,\n this.props.precision,\n this.props.decimalSeparator,\n this.props.thousandSeparator,\n this.props.allowNegative,\n this.props.prefix,\n this.props.suffix\n );\n\n event.persist(); // fixes issue #23\n\n this.setState({ maskedValue, value }, () => {\n this.props.onChange(maskedValue, value, event);\n this.props.onChangeEvent(event, maskedValue, value);\n });\n }\n\n\n /**\n * onFocus Event Handler\n * @param event\n */\n handleFocus(event) {\n if (!this.theInput) return;\n\n //Whenever we receive focus check to see if the position is before the suffix, if not, move it.\n let selectionEnd = this.theInput.value.length - this.props.suffix.length;\n let isNegative = (this.theInput.value.match(/-/g) || []).length % 2 === 1;\n let selectionStart = this.props.prefix.length + (isNegative ? 1 : 0);\n this.props.selectAllOnFocus && event.target.setSelectionRange(selectionStart, selectionEnd);\n this.inputSelectionStart = selectionStart;\n this.inputSelectionEnd = selectionEnd;\n }\n\n\n handleBlur(event) {\n this.inputSelectionStart = 0;\n this.inputSelectionEnd = 0;\n }\n\n\n /**\n * Component lifecycle function.\n * @returns {XML}\n * @see https://facebook.github.io/react/docs/component-specs.html#render\n */\n render() {\n return (\n { this.theInput = input; }}\n type={this.props.inputType}\n value={this.state.maskedValue}\n onChange={this.handleChange}\n onFocus={this.handleFocus}\n onMouseUp={this.handleFocus}\n {...this.state.customProps}\n />\n )\n }\n}\n\n\n\n/**\n * Prop validation.\n * @see https://facebook.github.io/react/docs/component-specs.html#proptypes\n */\n\nCurrencyInput.propTypes = {\n onChange: PropTypes.func,\n value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),\n decimalSeparator: PropTypes.string,\n thousandSeparator: PropTypes.string,\n precision: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),\n inputType: PropTypes.string,\n allowNegative: PropTypes.bool,\n allowEmpty: PropTypes.bool,\n prefix: PropTypes.string,\n suffix: PropTypes.string,\n selectAllOnFocus: PropTypes.bool\n};\n\n\nCurrencyInput.defaultProps = {\n onChange: function(maskValue, value, event) {/*no-op*/},\n onChangeEvent: function(event, maskValue, value) {/*no-op*/},\n autoFocus: false,\n value: '0',\n decimalSeparator: '.',\n thousandSeparator: ',',\n precision: '2',\n inputType: 'text',\n allowNegative: false,\n prefix: '',\n suffix: '',\n selectAllOnFocus: false\n};\n\n\nexport default CurrencyInput\n"],"names":["mask","value","precision","decimalSeparator","thousandSeparator","allowNegative","prefix","suffix","maskedValue","String","length","let","digits","match","numberIsNegative","allDigitsAreZero","idx","unshift","splice","Number","join","toFixed","split","raw","decimalpos","x","push","trim","Object","assign","target","i","arguments","source","key","prototype","hasOwnProperty","call","parseFloat","CurrencyInput","props","super","this","prepareProps","bind","handleChange","handleFocus","state","inputSelectionStart","inputSelectionEnd","getMaskedValue","customProps","onChange","onChangeEvent","inputType","allowEmpty","selectAllOnFocus","autoFocus","initialValue","replace","RegExp","toLocaleString","undefined","style","minimumFractionDigits","maximumFractionDigits","componentWillReceiveProps","nextProps","setState","componentDidMount","selectionStart","selectionEnd","node","ReactDOM","findDOMNode","theInput","focus","Math","min","setSelectionRange","componentWillUpdate","componentDidUpdate","prevProps","prevState","isNegative","minPos","max","regexEscapeRegex","separatorsRegex","currSeparatorCount","prevSeparatorCount","adjustment","const","baselength","event","preventDefault","persist","handleBlur","render","React","ref","input","type","onFocus","onMouseUp","Component","propTypes","PropTypes","func","oneOfType","number","string","bool","defaultProps","maskValue"],"mappings":"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"} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-currency-input", 3 | "version": "1.3.7", 4 | "description": "React component for inputing currency amounts", 5 | "main": "lib/react-currency-input.cjs.js", 6 | "jsnext:main": "lib/react-currency-input.es.js", 7 | "module": "lib/react-currency-input.es.js", 8 | "scripts": { 9 | "lint": "eslint src/**", 10 | "build:umd": "cross-env NODE_ENV=umd rollup -c", 11 | "build:cjs": "cross-env NODE_ENV=cjs rollup -c", 12 | "build:es": "cross-env NODE_ENV=es rollup -c", 13 | "build": "rimraf lib && npm run build:umd && npm run build:cjs && npm run build:es", 14 | "prepublish": "npm run build", 15 | "build-example": "browserify examples/index.js -o examples/bundle.js -t [ babelify --presets [ es2015 react ] ]", 16 | "test": "mocha --compilers js:babel-register", 17 | "webpack": "webpack", 18 | "webpack-dev": "webpack-dev-server" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/jsillitoe/react-currency-input.git" 23 | }, 24 | "keywords": [ 25 | "react", 26 | "es6", 27 | "javascript", 28 | "money", 29 | "currency", 30 | "i18n", 31 | "react-component" 32 | ], 33 | "author": "Joe Sillitoe ", 34 | "license": "MIT", 35 | "bugs": { 36 | "url": "https://github.com/jsillitoe/react-currency-input/issues" 37 | }, 38 | "homepage": "https://github.com/jsillitoe/react-currency-input#readme", 39 | "peerDependencies": { 40 | "prop-types": "^15.6.0", 41 | "react": "^16.0.0", 42 | "react-dom": "^16.0.0" 43 | }, 44 | "devDependencies": { 45 | "babel-cli": "^6.18.0", 46 | "babel-loader": "6.4.0", 47 | "babel-preset-es2015": "^6.9.0", 48 | "babel-preset-react": "^6.5.0", 49 | "babel-preset-stage-3": "6.22.0", 50 | "babel-register": "^6.9.0", 51 | "babelify": "^7.3.0", 52 | "browserify": "^13.0.1", 53 | "chai": "^3.5.0", 54 | "cross-env": "^5.1.0", 55 | "eslint": "^2.13.1", 56 | "mocha": "^2.5.3", 57 | "rimraf": "^2.5.2", 58 | "rollup": "^0.50.0", 59 | "rollup-plugin-buble": "^0.16.0", 60 | "rollup-plugin-commonjs": "^8.2.1", 61 | "rollup-plugin-filesize": "^1.4.2", 62 | "rollup-plugin-node-resolve": "^3.0.0", 63 | "rollup-plugin-peer-deps-external": "^1.0.0", 64 | "rollup-plugin-uglify": "^2.0.1", 65 | "sinon": "^1.17.4", 66 | "sinon-chai": "^2.8.0", 67 | "webpack": "2.2.1", 68 | "webpack-dev-server": "2.4.1" 69 | }, 70 | "dependencies": { 71 | "jsdom": "^9.12.0", 72 | "prop-types": "^15.6.0", 73 | "react": "^16.4.1", 74 | "react-dom": "^16.4.1" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import buble from 'rollup-plugin-buble'; 2 | import resolve from 'rollup-plugin-node-resolve'; 3 | import commonjs from 'rollup-plugin-commonjs'; 4 | import uglify from 'rollup-plugin-uglify'; 5 | import filesize from 'rollup-plugin-filesize'; 6 | import peerDeps from 'rollup-plugin-peer-deps-external'; 7 | 8 | const format = process.env.NODE_ENV; 9 | const isUmd = format === 'umd'; 10 | const file = `lib/react-currency-input.${isUmd ? 'min' : format}.js` 11 | 12 | const config = { 13 | input: './src/index.js', 14 | name: 'react-currency-input', 15 | sourcemap: true, 16 | output: { 17 | file, 18 | format, 19 | }, 20 | plugins: [ 21 | peerDeps(), 22 | resolve({ 23 | jsnext: true, 24 | main: true, 25 | browser: true, 26 | }), 27 | buble({ 28 | objectAssign: 'Object.assign', 29 | exclude: ['node_modules/**'], 30 | }), 31 | commonjs(), 32 | ], 33 | }; 34 | 35 | isUmd && config.plugins.push(uglify(), filesize()); 36 | 37 | export default config; 38 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import './object-assign-polyfill'; 2 | 3 | import PropTypes from 'prop-types'; 4 | import React, { Component } from 'react' 5 | import ReactDOM from 'react-dom' 6 | import mask from './mask.js' 7 | 8 | // IE* parseFloat polyfill 9 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/parseFloat#Polyfill 10 | Number.parseFloat = parseFloat; 11 | 12 | class CurrencyInput extends Component { 13 | constructor(props) { 14 | super(props); 15 | this.prepareProps = this.prepareProps.bind(this); 16 | this.handleChange = this.handleChange.bind(this); 17 | this.handleFocus = this.handleFocus.bind(this); 18 | this.setSelectionRange = this.setSelectionRange.bind(this); 19 | this.state = this.prepareProps(this.props); 20 | 21 | this.inputSelectionStart = 1; 22 | this.inputSelectionEnd = 1; 23 | } 24 | 25 | 26 | /** 27 | * Exposes the current masked value. 28 | * 29 | * @returns {String} 30 | */ 31 | getMaskedValue() { 32 | return this.state.maskedValue; 33 | } 34 | 35 | 36 | /** 37 | * General function used to cleanup and define the final props used for rendering 38 | * @returns {{ maskedValue: {String}, value: {Number}, customProps: {Object} }} 39 | */ 40 | prepareProps(props) { 41 | let customProps = {...props}; // babeljs converts to Object.assign, then polyfills. 42 | delete customProps.onChange; 43 | delete customProps.onChangeEvent; 44 | delete customProps.value; 45 | delete customProps.decimalSeparator; 46 | delete customProps.thousandSeparator; 47 | delete customProps.precision; 48 | delete customProps.inputType; 49 | delete customProps.allowNegative; 50 | delete customProps.allowEmpty; 51 | delete customProps.prefix; 52 | delete customProps.suffix; 53 | delete customProps.selectAllOnFocus; 54 | delete customProps.autoFocus; 55 | 56 | let initialValue = props.value; 57 | if (initialValue === null) { 58 | initialValue = props.allowEmpty? null : ''; 59 | }else{ 60 | 61 | if (typeof initialValue == 'string') { 62 | // Some people, when confronted with a problem, think "I know, I'll use regular expressions." 63 | // Now they have two problems. 64 | 65 | // Strip out thousand separators, prefix, and suffix, etc. 66 | if (props.thousandSeparator === "."){ 67 | // special handle the . thousand separator 68 | initialValue = initialValue.replace(/\./g, ''); 69 | } 70 | 71 | if (props.decimalSeparator != "."){ 72 | // fix the decimal separator 73 | initialValue = initialValue.replace(new RegExp(props.decimalSeparator, 'g'), '.'); 74 | } 75 | 76 | //Strip out anything that is not a digit, -, or decimal separator 77 | initialValue = initialValue.replace(/[^0-9-.]/g, ''); 78 | 79 | // now we can parse. 80 | initialValue = Number.parseFloat(initialValue); 81 | } 82 | initialValue = Number(initialValue).toLocaleString(undefined, { 83 | style : 'decimal', 84 | minimumFractionDigits: props.precision, 85 | maximumFractionDigits: props.precision 86 | }) 87 | 88 | } 89 | 90 | const { maskedValue, value } = mask( 91 | initialValue, 92 | props.precision, 93 | props.decimalSeparator, 94 | props.thousandSeparator, 95 | props.allowNegative, 96 | props.prefix, 97 | props.suffix 98 | ); 99 | 100 | return { maskedValue, value, customProps }; 101 | } 102 | 103 | 104 | /** 105 | * Component lifecycle function. 106 | * Invoked when a component is receiving new props. This method is not called for the initial render. 107 | * 108 | * @param nextProps 109 | * @see https://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops 110 | */ 111 | componentWillReceiveProps(nextProps) { 112 | this.setState(this.prepareProps(nextProps)); 113 | } 114 | 115 | 116 | /** 117 | * Component lifecycle function. 118 | * @returns {XML} 119 | * @see https://facebook.github.io/react/docs/react-component.html#componentdidmount 120 | */ 121 | componentDidMount(){ 122 | let node = ReactDOM.findDOMNode(this.theInput); 123 | let selectionStart, selectionEnd; 124 | 125 | if (this.props.autoFocus) { 126 | this.theInput.focus(); 127 | selectionEnd = this.state.maskedValue.length - this.props.suffix.length; 128 | selectionStart = selectionEnd; 129 | } else { 130 | selectionEnd = Math.min(node.selectionEnd, this.theInput.value.length - this.props.suffix.length); 131 | selectionStart = Math.min(node.selectionStart, selectionEnd); 132 | } 133 | 134 | this.setSelectionRange(node, selectionStart, selectionEnd); 135 | } 136 | 137 | 138 | /** 139 | * Component lifecycle function 140 | * @returns {XML} 141 | * @see https://facebook.github.io/react/docs/react-component.html#componentwillupdate 142 | */ 143 | componentWillUpdate() { 144 | let node = ReactDOM.findDOMNode(this.theInput); 145 | this.inputSelectionStart = node.selectionStart; 146 | this.inputSelectionEnd = node.selectionEnd; 147 | } 148 | 149 | 150 | /** 151 | * Component lifecycle function. 152 | * @returns {XML} 153 | * @see https://facebook.github.io/react/docs/react-component.html#componentdidupdate 154 | */ 155 | componentDidUpdate(prevProps, prevState){ 156 | const { decimalSeparator } = this.props; 157 | let node = ReactDOM.findDOMNode(this.theInput); 158 | let isNegative = (this.theInput.value.match(/-/g) || []).length % 2 === 1; 159 | let minPos = this.props.prefix.length + (isNegative ? 1 : 0); 160 | let selectionEnd = Math.max(minPos, Math.min(this.inputSelectionEnd, this.theInput.value.length - this.props.suffix.length)); 161 | let selectionStart = Math.max(minPos, Math.min(this.inputSelectionEnd, selectionEnd)); 162 | 163 | let regexEscapeRegex = /[-[\]{}()*+?.,\\^$|#\s]/g; 164 | let separatorsRegex = new RegExp(decimalSeparator.replace(regexEscapeRegex, '\\$&') + '|' + this.props.thousandSeparator.replace(regexEscapeRegex, '\\$&'), 'g'); 165 | let currSeparatorCount = (this.state.maskedValue.match(separatorsRegex) || []).length; 166 | let prevSeparatorCount = (prevState.maskedValue.match(separatorsRegex) || []).length; 167 | let adjustment = Math.max(currSeparatorCount - prevSeparatorCount, 0); 168 | 169 | selectionEnd = selectionEnd + adjustment; 170 | selectionStart = selectionStart + adjustment; 171 | 172 | const precision = Number(this.props.precision); 173 | 174 | let baselength = this.props.suffix.length 175 | + this.props.prefix.length 176 | + (precision > 0 ? decimalSeparator.length : 0) // if precision is 0 there will be no decimal part 177 | + precision 178 | + 1; // This is to account for the default '0' value that comes before the decimal separator 179 | 180 | if (this.state.maskedValue.length == baselength){ 181 | // if we are already at base length, position the cursor at the end. 182 | selectionEnd = this.theInput.value.length - this.props.suffix.length; 183 | selectionStart = selectionEnd; 184 | } 185 | 186 | this.setSelectionRange(node, selectionStart, selectionEnd); 187 | this.inputSelectionStart = selectionStart; 188 | this.inputSelectionEnd = selectionEnd; 189 | } 190 | 191 | /** 192 | * Set selection range only if input is in focused state 193 | * @param node DOMElement 194 | * @param start number 195 | * @param end number 196 | */ 197 | setSelectionRange(node, start, end) { 198 | if (document.activeElement === node) { 199 | node.setSelectionRange(start, end); 200 | } 201 | } 202 | 203 | 204 | /** 205 | * onChange Event Handler 206 | * @param event 207 | */ 208 | handleChange(event) { 209 | event.preventDefault(); 210 | let { maskedValue, value } = mask( 211 | event.target.value, 212 | this.props.precision, 213 | this.props.decimalSeparator, 214 | this.props.thousandSeparator, 215 | this.props.allowNegative, 216 | this.props.prefix, 217 | this.props.suffix 218 | ); 219 | 220 | event.persist(); // fixes issue #23 221 | 222 | this.setState({ maskedValue, value }, () => { 223 | this.props.onChange(maskedValue, value, event); 224 | this.props.onChangeEvent(event, maskedValue, value); 225 | }); 226 | } 227 | 228 | 229 | /** 230 | * onFocus Event Handler 231 | * @param event 232 | */ 233 | handleFocus(event) { 234 | if (!this.theInput) return; 235 | 236 | //Whenever we receive focus check to see if the position is before the suffix, if not, move it. 237 | let selectionEnd = this.theInput.value.length - this.props.suffix.length; 238 | let isNegative = (this.theInput.value.match(/-/g) || []).length % 2 === 1; 239 | let selectionStart = this.props.prefix.length + (isNegative ? 1 : 0); 240 | this.props.selectAllOnFocus && event.target.setSelectionRange(selectionStart, selectionEnd); 241 | this.inputSelectionStart = selectionStart; 242 | this.inputSelectionEnd = selectionEnd; 243 | } 244 | 245 | 246 | handleBlur(event) { 247 | this.inputSelectionStart = 0; 248 | this.inputSelectionEnd = 0; 249 | } 250 | 251 | 252 | /** 253 | * Component lifecycle function. 254 | * @returns {XML} 255 | * @see https://facebook.github.io/react/docs/component-specs.html#render 256 | */ 257 | render() { 258 | return ( 259 | { this.theInput = input; }} 261 | type={this.props.inputType} 262 | value={this.state.maskedValue} 263 | onChange={this.handleChange} 264 | onFocus={this.handleFocus} 265 | onMouseUp={this.handleFocus} 266 | {...this.state.customProps} 267 | /> 268 | ) 269 | } 270 | } 271 | 272 | 273 | 274 | /** 275 | * Prop validation. 276 | * @see https://facebook.github.io/react/docs/component-specs.html#proptypes 277 | */ 278 | 279 | CurrencyInput.propTypes = { 280 | onChange: PropTypes.func, 281 | value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 282 | decimalSeparator: PropTypes.string, 283 | thousandSeparator: PropTypes.string, 284 | precision: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 285 | inputType: PropTypes.string, 286 | allowNegative: PropTypes.bool, 287 | allowEmpty: PropTypes.bool, 288 | prefix: PropTypes.string, 289 | suffix: PropTypes.string, 290 | selectAllOnFocus: PropTypes.bool 291 | }; 292 | 293 | 294 | CurrencyInput.defaultProps = { 295 | onChange: function(maskValue, value, event) {/*no-op*/}, 296 | onChangeEvent: function(event, maskValue, value) {/*no-op*/}, 297 | autoFocus: false, 298 | value: '0', 299 | decimalSeparator: '.', 300 | thousandSeparator: ',', 301 | precision: '2', 302 | inputType: 'text', 303 | allowNegative: false, 304 | prefix: '', 305 | suffix: '', 306 | selectAllOnFocus: false 307 | }; 308 | 309 | 310 | export default CurrencyInput 311 | -------------------------------------------------------------------------------- /src/mask.js: -------------------------------------------------------------------------------- 1 | 2 | export default function mask(value, precision = 2, decimalSeparator = '.', thousandSeparator = ',', allowNegative = false, prefix = '', suffix = ''){ 3 | // provide some default values and arg validation. 4 | if (precision < 0) { precision = 0; } // precision cannot be negative 5 | if (precision > 20) { precision = 20; } // precision cannot be greater than 20 6 | 7 | if (value === null || value===undefined) { 8 | return { 9 | value: 0, 10 | maskedValue: '' 11 | }; 12 | } 13 | 14 | value = String(value); //if the given value is a Number, let's convert into String to manipulate that 15 | 16 | if (value.length == 0) { 17 | return { 18 | value: 0, 19 | maskedValue: '' 20 | }; 21 | } 22 | 23 | 24 | // extract digits. if no digits, fill in a zero. 25 | let digits = value.match(/\d/g) || ['0']; 26 | 27 | let numberIsNegative = false; 28 | if (allowNegative) { 29 | let negativeSignCount = (value.match(/-/g) || []).length; 30 | // number will be negative if we have an odd number of "-" 31 | // ideally, we should only ever have 0, 1 or 2 (positive number, making a number negative 32 | // and making a negative number positive, respectively) 33 | numberIsNegative = negativeSignCount % 2 === 1; 34 | 35 | // if every digit in the array is '0', then the number should never be negative 36 | let allDigitsAreZero = true; 37 | for (let idx=0; idx < digits.length; idx += 1) { 38 | if(digits[idx] !== '0') { 39 | allDigitsAreZero = false; 40 | break; 41 | } 42 | } 43 | if (allDigitsAreZero) { 44 | numberIsNegative = false; 45 | } 46 | } 47 | 48 | // zero-pad a input 49 | while (digits.length <= precision) { digits.unshift('0'); } 50 | 51 | if (precision > 0) { 52 | // add the decimal separator 53 | digits.splice(digits.length - precision, 0, "."); 54 | } 55 | 56 | // clean up extraneous digits like leading zeros. 57 | digits = Number(digits.join('')).toFixed(precision).split(''); 58 | let raw = Number(digits.join('')); 59 | 60 | let decimalpos = digits.length - precision - 1; // -1 needed to position the decimal separator before the digits. 61 | if (precision > 0) { 62 | // set the final decimal separator 63 | digits[decimalpos] = decimalSeparator; 64 | } else { 65 | // when precision is 0, there is no decimal separator. 66 | decimalpos = digits.length; 67 | } 68 | 69 | // add in any thousand separators 70 | for (let x=decimalpos - 3; x > 0; x = x - 3) { 71 | digits.splice(x, 0, thousandSeparator); 72 | } 73 | 74 | // if we have a prefix or suffix, add them in. 75 | if (prefix.length > 0) { digits.unshift(prefix); } 76 | if (suffix.length > 0) { digits.push(suffix); } 77 | 78 | // if the number is negative, insert a "-" to 79 | // the front of the array and negate the raw value 80 | if (allowNegative && numberIsNegative) { 81 | digits.unshift('-'); 82 | raw = -raw; 83 | } 84 | 85 | return { 86 | value: raw, 87 | maskedValue: digits.join('').trim() 88 | }; 89 | } 90 | -------------------------------------------------------------------------------- /src/object-assign-polyfill.js: -------------------------------------------------------------------------------- 1 | Object.assign = Object.assign || 2 | function(target) { 3 | for (var i = 1; i < arguments.length; i++) { 4 | var source = arguments[i]; 5 | for (var key in source) { 6 | if (Object.prototype.hasOwnProperty.call(source, key)) { 7 | target[key] = source[key]; 8 | } 9 | } 10 | } 11 | return target; 12 | }; 13 | -------------------------------------------------------------------------------- /test/index.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import chai, {expect} from 'chai' 3 | import sinon from 'sinon' 4 | import sinonChai from 'sinon-chai' 5 | import CurrencyInput from '../src/index' 6 | import ReactDOM from 'react-dom'; 7 | import ReactTestUtils from 'react-dom/test-utils'; 8 | import setup from './setup' 9 | 10 | chai.use(sinonChai); 11 | 12 | describe('react-currency-input', function(){ 13 | 14 | before('setup dom', function(){ 15 | setup(); // setup the jsdom 16 | }); 17 | 18 | describe('default arguments', function(){ 19 | 20 | before('render and locate element', function() { 21 | this.renderedComponent = ReactTestUtils.renderIntoDocument( 22 | 23 | ); 24 | 25 | this.inputComponent = ReactTestUtils.findRenderedDOMComponentWithTag( 26 | this.renderedComponent, 27 | 'input' 28 | ); 29 | }); 30 | 31 | it(' should have masked value of "0.00"', function() { 32 | expect(this.renderedComponent.getMaskedValue()).to.equal('0.00') 33 | }); 34 | 35 | 36 | it(' should be of type "text"', function() { 37 | expect(this.inputComponent.getAttribute('type')).to.equal('text') 38 | }); 39 | 40 | it('does not auto-focus by default', function() { 41 | expect(this.renderedComponent.props.autoFocus).to.be.false 42 | }); 43 | }); 44 | 45 | describe('custom arguments', function(){ 46 | 47 | before('render and locate element', function() { 48 | this.renderedComponent = ReactTestUtils.renderIntoDocument( 49 | 57 | ); 58 | 59 | this.inputComponent = ReactTestUtils.findRenderedDOMComponentWithTag( 60 | this.renderedComponent, 61 | 'input' 62 | ); 63 | }); 64 | 65 | it(' should have masked value of "123.456,789"', function() { 66 | expect(this.renderedComponent.getMaskedValue()).to.equal('123.456.789,000') 67 | }); 68 | 69 | it(' should be of type "tel"', function() { 70 | expect(this.inputComponent.getAttribute('type')).to.equal('tel') 71 | }); 72 | 73 | it('should be auto focused', function() { 74 | var focusedElement = document.activeElement; 75 | expect(focusedElement.getAttribute('id')).to.equal("currencyInput"); 76 | }); 77 | }); 78 | 79 | 80 | describe('properly convert number value props into display values', function(){ 81 | 82 | it('adds decimals to whole numbers to match precision', function() { 83 | var renderedComponent = ReactTestUtils.renderIntoDocument( 84 | 85 | ); 86 | expect (renderedComponent.getMaskedValue()).to.equal('123,456,789.00') 87 | }); 88 | 89 | it('Does not change value when precision matches', function() { 90 | var renderedComponent = ReactTestUtils.renderIntoDocument( 91 | 92 | ); 93 | expect (renderedComponent.getMaskedValue()).to.equal('1,234,567.89') 94 | }); 95 | 96 | 97 | it('Rounds down properly when an number with extra decimals is passed in', function() { 98 | var renderedComponent = ReactTestUtils.renderIntoDocument( 99 | 100 | ); 101 | expect (renderedComponent.getMaskedValue()).to.equal('1,234,567.89') 102 | }); 103 | 104 | 105 | it('Rounds up properly when an number with extra decimals is passed in', function() { 106 | var renderedComponent = ReactTestUtils.renderIntoDocument( 107 | 108 | ); 109 | expect (renderedComponent.getMaskedValue()).to.equal('1,234,567.90') 110 | }); 111 | 112 | it('Rounds up the whole number when an number with extra decimals is passed in', function() { 113 | var renderedComponent = ReactTestUtils.renderIntoDocument( 114 | 115 | ); 116 | expect (renderedComponent.getMaskedValue()).to.equal('1,234,568') 117 | }); 118 | 119 | it('it handles initial value as the integer 0,', function() { 120 | var renderedComponent = ReactTestUtils.renderIntoDocument( 121 | 122 | ); 123 | expect (renderedComponent.getMaskedValue()).to.equal('0.00') 124 | }); 125 | 126 | it('it handles initial value as the float 0.00,', function() { 127 | var renderedComponent = ReactTestUtils.renderIntoDocument( 128 | 129 | ); 130 | expect (renderedComponent.getMaskedValue()).to.equal('0.00') 131 | }); 132 | 133 | }); 134 | 135 | 136 | describe('properly convert string value props into display values', function(){ 137 | 138 | it('adds decimals to whole numbers to match precision', function() { 139 | var renderedComponent = ReactTestUtils.renderIntoDocument( 140 | 141 | ); 142 | expect (renderedComponent.getMaskedValue()).to.equal('6,300.00') 143 | }); 144 | 145 | 146 | it('Does not change value when precision matches', function() { 147 | var renderedComponent = ReactTestUtils.renderIntoDocument( 148 | 149 | ); 150 | expect (renderedComponent.getMaskedValue()).to.equal('1,234,567.89') 151 | }); 152 | 153 | 154 | it('Rounds down properly when an number with extra decimals is passed in', function() { 155 | var renderedComponent = ReactTestUtils.renderIntoDocument( 156 | 157 | ); 158 | expect (renderedComponent.getMaskedValue()).to.equal('1,234,567.89') 159 | }); 160 | 161 | 162 | it('Rounds up properly when an number with extra decimals is passed in', function() { 163 | var renderedComponent = ReactTestUtils.renderIntoDocument( 164 | 165 | ); 166 | expect (renderedComponent.getMaskedValue()).to.equal('1,234,567.90') 167 | }); 168 | 169 | 170 | it('Rounds up the whole number when an number with extra decimals is passed in', function() { 171 | var renderedComponent = ReactTestUtils.renderIntoDocument( 172 | 173 | ); 174 | expect (renderedComponent.getMaskedValue()).to.equal('1,234,568') 175 | }); 176 | 177 | 178 | it('Handles strings with separators', function() { 179 | var renderedComponent = ReactTestUtils.renderIntoDocument( 180 | 181 | ); 182 | expect (renderedComponent.getMaskedValue()).to.equal('1,000.01') 183 | }); 184 | 185 | 186 | it('Handles strings with prefixes', function() { 187 | var renderedComponent = ReactTestUtils.renderIntoDocument( 188 | 189 | ); 190 | expect (renderedComponent.getMaskedValue()).to.equal('$10.01') 191 | }); 192 | 193 | it('Handles strings with suffixes', function() { 194 | var renderedComponent = ReactTestUtils.renderIntoDocument( 195 | 196 | ); 197 | expect (renderedComponent.getMaskedValue()).to.equal('10.01 kr') 198 | }); 199 | 200 | 201 | it('Handles strings with custom separators', function() { 202 | var renderedComponent = ReactTestUtils.renderIntoDocument( 203 | 204 | ); 205 | expect (renderedComponent.getMaskedValue()).to.equal('123.456.789,12') 206 | }); 207 | 208 | 209 | it("Handles 1,234,567.89 format", function() { 210 | var renderedComponent = ReactTestUtils.renderIntoDocument( 211 | 212 | ); 213 | expect (renderedComponent.getMaskedValue()).to.equal('1,234,567.89') 214 | }); 215 | 216 | 217 | it("Handles 1 234 567.89 format", function() { 218 | var renderedComponent = ReactTestUtils.renderIntoDocument( 219 | 220 | ); 221 | expect (renderedComponent.getMaskedValue()).to.equal('1 234 567.89') 222 | }); 223 | 224 | it("Handles 1 234 567,89 format", function() { 225 | var renderedComponent = ReactTestUtils.renderIntoDocument( 226 | 227 | ); 228 | expect (renderedComponent.getMaskedValue()).to.equal('1 234 567,89') 229 | }); 230 | 231 | it("Handles 1,234,567·89 format", function() { 232 | var renderedComponent = ReactTestUtils.renderIntoDocument( 233 | 234 | ); 235 | expect (renderedComponent.getMaskedValue()).to.equal('1,234,567·89') 236 | }); 237 | 238 | it("Handles 1.234.567,89 format", function() { 239 | var renderedComponent = ReactTestUtils.renderIntoDocument( 240 | 241 | ); 242 | expect (renderedComponent.getMaskedValue()).to.equal('1.234.567,89') 243 | }); 244 | 245 | it("Handles 1˙234˙567,89 format", function() { 246 | var renderedComponent = ReactTestUtils.renderIntoDocument( 247 | 248 | ); 249 | expect (renderedComponent.getMaskedValue()).to.equal('1˙234˙567,89') 250 | }); 251 | 252 | 253 | it("Handles 1'234'567.89 format", function() { 254 | var renderedComponent = ReactTestUtils.renderIntoDocument( 255 | 256 | ); 257 | expect (renderedComponent.getMaskedValue()).to.equal("1'234'567.89") 258 | }); 259 | 260 | 261 | 262 | }); 263 | 264 | describe('change events', function(){ 265 | 266 | before('render and locate element', function() { 267 | this.handleChange = sinon.spy(); 268 | 269 | this.renderedComponent = ReactTestUtils.renderIntoDocument( 270 | 271 | ); 272 | 273 | this.inputComponent = ReactTestUtils.findRenderedDOMComponentWithTag( 274 | this.renderedComponent, 275 | 'input' 276 | ); 277 | }); 278 | 279 | it('should call onChange', function() { 280 | this.inputComponent.value=123456789; 281 | ReactTestUtils.Simulate.change(this.inputComponent); 282 | expect(this.handleChange).to.have.been.calledWith("1,234,567.89", 1234567.89); 283 | }); 284 | 285 | 286 | it('should change the masked value', function() { 287 | this.inputComponent.value=123456789; 288 | ReactTestUtils.Simulate.change(this.inputComponent); 289 | expect(this.renderedComponent.getMaskedValue()).to.equal("1,234,567.89"); 290 | }); 291 | 292 | 293 | it('should change the component value', function() { 294 | this.inputComponent.value=123456789; 295 | ReactTestUtils.Simulate.change(this.inputComponent); 296 | expect(this.inputComponent.value).to.equal("1,234,567.89"); 297 | }); 298 | 299 | 300 | }); 301 | 302 | 303 | describe('negative numbers', function() { 304 | 305 | before('render and locate element', function() { 306 | this.renderedComponent = ReactTestUtils.renderIntoDocument( 307 | 308 | ); 309 | 310 | this.inputComponent = ReactTestUtils.findRenderedDOMComponentWithTag( 311 | this.renderedComponent, 312 | 'input' 313 | ); 314 | }); 315 | 316 | beforeEach('reset value to 0', function() { 317 | this.inputComponent.value = "0"; 318 | ReactTestUtils.Simulate.change(this.inputComponent); 319 | }); 320 | 321 | it('should render 0 without negative sign', function() { 322 | expect(this.renderedComponent.getMaskedValue()).to.equal('0.00'); 323 | this.inputComponent.value = "-0"; ReactTestUtils.Simulate.change(this.inputComponent); 324 | expect(this.renderedComponent.getMaskedValue()).to.equal('0.00'); 325 | }); 326 | 327 | it('should render number with no or even number of "-" as positive', function() { 328 | expect(this.renderedComponent.getMaskedValue()).to.equal('0.00'); 329 | this.inputComponent.value = "123456"; ReactTestUtils.Simulate.change(this.inputComponent); 330 | expect(this.renderedComponent.getMaskedValue()).to.equal('1,234.56'); 331 | this.inputComponent.value = "--123456"; ReactTestUtils.Simulate.change(this.inputComponent); 332 | expect(this.renderedComponent.getMaskedValue()).to.equal('1,234.56'); 333 | this.inputComponent.value = "123--456"; ReactTestUtils.Simulate.change(this.inputComponent); 334 | expect(this.renderedComponent.getMaskedValue()).to.equal('1,234.56'); 335 | this.inputComponent.value = "123456--"; ReactTestUtils.Simulate.change(this.inputComponent); 336 | expect(this.renderedComponent.getMaskedValue()).to.equal('1,234.56'); 337 | this.inputComponent.value = "--123--456--"; ReactTestUtils.Simulate.change(this.inputComponent); 338 | expect(this.renderedComponent.getMaskedValue()).to.equal('1,234.56'); 339 | this.inputComponent.value = "123456----"; ReactTestUtils.Simulate.change(this.inputComponent); 340 | expect(this.renderedComponent.getMaskedValue()).to.equal('1,234.56'); 341 | }); 342 | 343 | it('should render number with odd number of "-" as negative', function() { 344 | expect(this.renderedComponent.getMaskedValue()).to.equal('0.00'); 345 | this.inputComponent.value = "-123456"; ReactTestUtils.Simulate.change(this.inputComponent); 346 | expect(this.renderedComponent.getMaskedValue()).to.equal('-1,234.56'); 347 | this.inputComponent.value = "123-456"; ReactTestUtils.Simulate.change(this.inputComponent); 348 | expect(this.renderedComponent.getMaskedValue()).to.equal('-1,234.56'); 349 | this.inputComponent.value = "123456-"; ReactTestUtils.Simulate.change(this.inputComponent); 350 | expect(this.renderedComponent.getMaskedValue()).to.equal('-1,234.56'); 351 | this.inputComponent.value = "-123-456-"; ReactTestUtils.Simulate.change(this.inputComponent); 352 | expect(this.renderedComponent.getMaskedValue()).to.equal('-1,234.56'); 353 | }); 354 | 355 | it('should correctly change between negative and positive numbers', function() { 356 | expect(this.renderedComponent.getMaskedValue()).to.equal('0.00'); 357 | this.inputComponent.value = "123456"; ReactTestUtils.Simulate.change(this.inputComponent); 358 | expect(this.renderedComponent.getMaskedValue()).to.equal('1,234.56'); 359 | this.inputComponent.value = "1,234.56-"; ReactTestUtils.Simulate.change(this.inputComponent); 360 | expect(this.renderedComponent.getMaskedValue()).to.equal('-1,234.56'); 361 | this.inputComponent.value = "-1,234.56-"; ReactTestUtils.Simulate.change(this.inputComponent); 362 | expect(this.renderedComponent.getMaskedValue()).to.equal('1,234.56'); 363 | this.inputComponent.value = "1-,234.56"; ReactTestUtils.Simulate.change(this.inputComponent); 364 | expect(this.renderedComponent.getMaskedValue()).to.equal('-1,234.56'); 365 | this.inputComponent.value = "-1,234.-56"; ReactTestUtils.Simulate.change(this.inputComponent); 366 | expect(this.renderedComponent.getMaskedValue()).to.equal('1,234.56'); 367 | }); 368 | 369 | }); 370 | 371 | 372 | describe('currency prefix', function() { 373 | 374 | before('render and locate element', function () { 375 | this.renderedComponent = ReactTestUtils.renderIntoDocument( 376 | 377 | ); 378 | 379 | this.inputComponent = ReactTestUtils.findRenderedDOMComponentWithTag( 380 | this.renderedComponent, 381 | 'input' 382 | ); 383 | }); 384 | 385 | it('should render the prefix', function() { 386 | expect(this.renderedComponent.getMaskedValue()).to.equal('$0.00'); 387 | }); 388 | 389 | }); 390 | 391 | describe('currency suffix', function() { 392 | 393 | before('render and locate element', function () { 394 | this.renderedComponent = ReactTestUtils.renderIntoDocument( 395 | 396 | ); 397 | 398 | this.inputComponent = ReactTestUtils.findRenderedDOMComponentWithTag( 399 | this.renderedComponent, 400 | 'input' 401 | ); 402 | }); 403 | 404 | it('should render the suffix', function() { 405 | expect(this.renderedComponent.getMaskedValue()).to.equal('0.00 kr'); 406 | }); 407 | 408 | }); 409 | 410 | describe('input selection', function() { 411 | let defaultProps = { 412 | allowNegative: true, 413 | handleChange: () => {}, 414 | value: '0', 415 | prefix: '$', 416 | suffix: ' s' 417 | }; 418 | let divElem; 419 | let renderComponent = function(props = {}) { 420 | divElem = document.createElement('div'); 421 | document.body.appendChild(divElem); 422 | 423 | const componentProps = Object.assign({}, defaultProps, props); 424 | 425 | const renderedComponent = ReactDOM.render( 426 | , 427 | divElem 428 | ); 429 | const inputComponent = ReactTestUtils.findRenderedDOMComponentWithTag( 430 | renderedComponent, 431 | 'input' 432 | ); 433 | 434 | inputComponent.value = "0"; 435 | ReactTestUtils.Simulate.change(inputComponent); 436 | 437 | return { renderedComponent, inputComponent }; 438 | }; 439 | 440 | after('clean up dom', function() { 441 | document.body.removeChild(divElem); 442 | }); 443 | 444 | it('sanity - renders "$0.00 s"', function() { 445 | const { renderedComponent } = renderComponent(); 446 | expect(renderedComponent.getMaskedValue()).to.equal('$0.00 s'); 447 | }); 448 | 449 | it('should consider precision absence', function() { 450 | const { inputComponent } = renderComponent({ precision: 0 }); 451 | 452 | expect(inputComponent.selectionStart).to.equal(2); 453 | expect(inputComponent.selectionEnd).to.equal(2); 454 | }); 455 | 456 | xit('should highlight number on focus', function() { 457 | const { inputComponent } = renderComponent(); 458 | ReactTestUtils.Simulate.focus(inputComponent); 459 | expect(inputComponent.selectionStart).to.equal(1); 460 | expect(inputComponent.selectionEnd).to.equal(5); 461 | }); 462 | 463 | xit('should consider the negative sign when highlighting', function() { 464 | const { inputComponent } = renderComponent(); 465 | 466 | inputComponent.value = '-4.35'; 467 | ReactTestUtils.Simulate.change(inputComponent); 468 | 469 | ReactTestUtils.Simulate.focus(inputComponent); 470 | expect(inputComponent.selectionStart).to.equal(2); 471 | expect(inputComponent.selectionEnd).to.equal(6); 472 | }); 473 | 474 | xit('should adjust start/end by 1 when entering a number', function() { 475 | const { inputComponent } = renderComponent(); 476 | 477 | inputComponent.value = '134'; 478 | ReactTestUtils.Simulate.change(inputComponent); 479 | ReactTestUtils.Simulate.focus(inputComponent); 480 | 481 | inputComponent.setSelectionRange(1, 1); 482 | inputComponent.value = '1234'; 483 | ReactTestUtils.Simulate.change(inputComponent); 484 | 485 | expect(inputComponent.selectionStart).to.equal(2); 486 | expect(inputComponent.selectionEnd).to.equal(2); 487 | }); 488 | 489 | }); 490 | 491 | }); 492 | -------------------------------------------------------------------------------- /test/mask.spec.js: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai' 2 | import mask from '../src/mask' 3 | 4 | 5 | describe('mask', function(){ 6 | 7 | it('should return empty strings when value is not set"', function(){ 8 | const {maskedValue, value} = mask(); 9 | 10 | expect(maskedValue).to.equal(""); 11 | expect(value).to.equal(0); 12 | }); 13 | 14 | it('should return empty strings when value is empty string"', function(){ 15 | const {maskedValue, value} = mask(""); 16 | 17 | expect(maskedValue).to.equal(""); 18 | expect(value).to.equal(0); 19 | }); 20 | 21 | it('should return empty strings when value is null"', function(){ 22 | const {maskedValue, value} = mask(null); 23 | 24 | expect(maskedValue).to.equal(""); 25 | expect(value).to.equal(0); 26 | }); 27 | 28 | it('should change "0" to "0.00"', function(){ 29 | const {maskedValue, value} = mask("0"); 30 | 31 | expect(maskedValue).to.equal("0.00"); 32 | expect(value).to.equal(0); 33 | }); 34 | 35 | it('should change "00" to "0.00"', function(){ 36 | const {maskedValue, value} = mask("00"); 37 | 38 | expect(maskedValue).to.equal("0.00"); 39 | expect(value).to.equal(0); 40 | }); 41 | 42 | it('should change "000" to "0.00"', function(){ 43 | const {maskedValue, value} = mask("000"); 44 | expect(maskedValue).to.equal("0.00"); 45 | expect(value).to.equal(0); 46 | }); 47 | 48 | it('should change "0000" to "0.00"', function(){ 49 | const {maskedValue, value} = mask("0000"); 50 | expect(maskedValue).to.equal("0.00"); 51 | expect(value).to.equal(0); 52 | }); 53 | 54 | it('should change "0001" to "0.01"', function(){ 55 | const {maskedValue, value} = mask("0001"); 56 | expect(maskedValue).to.equal("0.01"); 57 | expect(value).to.equal(0.01); 58 | }); 59 | 60 | it('should change "1001" to "10.01"', function(){ 61 | const {maskedValue, value} = mask("1001"); 62 | expect(maskedValue).to.equal("10.01"); 63 | expect(value).to.equal(10.01); 64 | }); 65 | 66 | it('should change "123456789" to "1,234,567.89"', function(){ 67 | const {maskedValue, value} = mask("123456789"); 68 | expect(maskedValue).to.equal("1,234,567.89"); 69 | expect(value).to.equal(1234567.89); 70 | }); 71 | 72 | 73 | describe('with separators', function(){ 74 | 75 | it('decimal:"," thousand:"." should change "123456789" to "1.234.567,89"', function(){ 76 | const {maskedValue, value} = mask("123456789", 2, ",", "."); 77 | expect(maskedValue).to.equal("1.234.567,89"); 78 | expect(value).to.equal(1234567.89); 79 | }); 80 | 81 | it('zero length thousand separator should change "123456789" to "1234567.89"', function(){ 82 | const {maskedValue, value} = mask("123456789", 2, ".", ""); 83 | expect(maskedValue).to.equal("1234567.89"); 84 | expect(value).to.equal(1234567.89); 85 | }); 86 | 87 | it('zero length decimal separator should change "123456789" to "1,234,56789"', function(){ 88 | const {maskedValue, value} = mask("123456789", 2, "", ","); 89 | expect(maskedValue).to.equal("1,234,56789"); 90 | expect(value).to.equal(1234567.89); 91 | }); 92 | 93 | }); 94 | 95 | 96 | describe('with precision', function(){ 97 | 98 | it('set to string value "3" should change "123456789" to "123,456.789"', function(){ 99 | const {maskedValue, value} = mask("123456789", "3"); 100 | expect(maskedValue).to.equal("123,456.789"); 101 | expect(value).to.equal(123456.789) 102 | }); 103 | 104 | it('set to 3 should change "123456789" to "123,456.789"', function(){ 105 | const {maskedValue, value} = mask("123456789", 3); 106 | expect(maskedValue).to.equal("123,456.789"); 107 | expect(value).to.equal(123456.789); 108 | }); 109 | 110 | it('set to 0 should change "123456789" to "123,456,789"', function(){ 111 | const {maskedValue, value} = mask("123456789", 0); 112 | expect(maskedValue).to.equal("123,456,789"); 113 | expect(value).to.equal(123456789); 114 | }); 115 | 116 | }); 117 | 118 | 119 | describe('negative numbers', function(){ 120 | 121 | it('all "-" should be stripped out if allowNegative is false', function(){ 122 | expect(mask("123456").maskedValue).to.equal("1,234.56"); 123 | expect(mask("-123456").maskedValue).to.equal("1,234.56"); 124 | expect(mask("--123456").maskedValue).to.equal("1,234.56"); 125 | expect(mask("--123--456").maskedValue).to.equal("1,234.56"); 126 | expect(mask("--123--456--").maskedValue).to.equal("1,234.56"); 127 | }); 128 | 129 | it('single "-" anywhere in the string should result in a negative masked number', function(){ 130 | expect(mask("-123456", "2", ".", ",", true).maskedValue).to.equal("-1,234.56"); 131 | expect(mask("123-456", "2", ".", ",", true).maskedValue).to.equal("-1,234.56"); 132 | expect(mask("123456-", "2", ".", ",", true).maskedValue).to.equal("-1,234.56"); 133 | }); 134 | 135 | it('single "-" anywhere in the string should result in a negative unmasked number', function(){ 136 | expect(mask("-123456", "2", ".", ",", true).value).to.equal(-1234.56); 137 | expect(mask("123-456", "2", ".", ",", true).value).to.equal(-1234.56); 138 | expect(mask("123456-", "2", ".", ",", true).value).to.equal(-1234.56); 139 | }); 140 | 141 | it('no or even number of "-" should result in a positive number', function(){ 142 | expect(mask("123456", "2", ".", ",", true).maskedValue).to.equal("1,234.56"); 143 | expect(mask("--123456", "2", ".", ",", true).maskedValue).to.equal("1,234.56"); 144 | expect(mask("123--456", "2", ".", ",", true).maskedValue).to.equal("1,234.56"); 145 | expect(mask("123456--", "2", ".", ",", true).maskedValue).to.equal("1,234.56"); 146 | expect(mask("--123456--", "2", ".", ",", true).maskedValue).to.equal("1,234.56"); 147 | expect(mask("--123--456--", "2", ".", ",", true).maskedValue).to.equal("1,234.56"); 148 | expect(mask("--1--234--56--", "2", ".", ",", true).maskedValue).to.equal("1,234.56"); 149 | }); 150 | 151 | it('odd number of "-" should result in a negative number', function(){ 152 | expect(mask("-123456", "2", ".", ",", true).maskedValue).to.equal("-1,234.56"); 153 | expect(mask("123-456", "2", ".", ",", true).maskedValue).to.equal("-1,234.56"); 154 | expect(mask("123456-", "2", ".", ",", true).maskedValue).to.equal("-1,234.56"); 155 | expect(mask("-123-456-", "2", ".", ",", true).maskedValue).to.equal("-1,234.56"); 156 | expect(mask("-1-23-45-6-", "2", ".", ",", true).maskedValue).to.equal("-1,234.56"); 157 | expect(mask("-1-2-3-4-5-6-", "2", ".", ",", true).maskedValue).to.equal("-1,234.56"); 158 | }); 159 | 160 | it('0 is never negative', function(){ 161 | expect(mask("0", "2", ".", ",", true).maskedValue).to.equal("0.00"); 162 | expect(mask("-0", "2", ".", ",", true).maskedValue).to.equal("0.00"); 163 | expect(mask("-0-", "2", ".", ",", true).maskedValue).to.equal("0.00"); 164 | expect(mask("--0-", "2", ".", ",", true).maskedValue).to.equal("0.00"); 165 | }); 166 | 167 | it('just "-" should result in 0.00', function(){ 168 | expect(mask("-", "2", ".", ",", true).maskedValue).to.equal("0.00"); 169 | }); 170 | 171 | }); 172 | 173 | 174 | 175 | describe('with currency symbol', function(){ 176 | 177 | it('"$" prefix should change "0" to "$0.00"', function(){ 178 | expect(mask("0","2",".",",",true,"$","").maskedValue).to.equal("$0.00"); 179 | }); 180 | 181 | it('"kr" suffix should change "0" to "0.00kr"', function(){ 182 | expect(mask("0","2",".",",",true,"","kr").maskedValue).to.equal("0.00kr"); 183 | }); 184 | 185 | it('can have both a prefix and a suffix', function(){ 186 | expect(mask("0","2",".",",",true,"$","kr").maskedValue).to.equal("$0.00kr"); 187 | }); 188 | 189 | it('does not strip whitespaces between amount and symbol', function(){ 190 | expect(mask("0","2",".",",",true,"$ ","").maskedValue).to.equal("$ 0.00"); 191 | expect(mask("0","2",".",",",true,""," kr").maskedValue).to.equal("0.00 kr"); 192 | }); 193 | 194 | it('strips whitespaces before and after value', function(){ 195 | expect(mask("0","2",".",",",true," $ ","").maskedValue).to.equal("$ 0.00"); 196 | expect(mask("0","2",".",",",true,""," kr ").maskedValue).to.equal("0.00 kr"); 197 | }); 198 | 199 | 200 | it('"-" should come before the prefix', function(){ 201 | expect(mask("-20.00","2",".",",",true,"$","").maskedValue).to.equal("-$20.00"); 202 | }); 203 | 204 | }); 205 | 206 | 207 | 208 | }); 209 | -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | import jsdom from 'jsdom' 2 | 3 | export default function setup(markup){ 4 | 5 | 6 | global.document = jsdom.jsdom(markup || ''); 7 | global.window = document.defaultView; 8 | 9 | } 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: './examples/index.js', 6 | output: { 7 | path: path.resolve(__dirname, 'examples'), 8 | filename: 'bundle.js' 9 | }, 10 | module: { 11 | loaders: [ 12 | { 13 | test: /\.js$/, 14 | loader: 'babel-loader', 15 | query: { 16 | presets: ["es2015", "react", "stage-3"] 17 | } 18 | } 19 | ] 20 | }, 21 | plugins: [ 22 | new webpack.HotModuleReplacementPlugin() 23 | ], 24 | devServer: { 25 | hot: true, 26 | contentBase: './examples' 27 | }, 28 | stats: { 29 | colors: true 30 | }, 31 | devtool: 'source-map' 32 | }; 33 | --------------------------------------------------------------------------------