New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

react-number-format

Package Overview
Dependencies
Maintainers
1
Versions
121
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

react-number-format - npm Package Compare versions

Comparing version 2.0.4 to 3.0.0-alpha

_config.yml

4

custom_formatters/card_expiry.js

@@ -25,5 +25,5 @@ /**

const month = limit(val.substring(0, 2), '12');
const date = limit(val.substring(2, 4), '31');
const year = val.substring(2, 4);
return month + (date.length ? '/' + date : '');
return month + (year.length ? '/' + year : '');
}
/*!
* react-number-format - 2.0.4
* react-number-format - 3.0.0-alpha
* Author : Sudhanshu Yadav

@@ -60,3 +60,3 @@ * Copyright (c) 2016,2017 to Sudhanshu Yadav - ignitersworld.com , released under the MIT license.

/* 0 */
/***/ function(module, exports, __webpack_require__) {
/***/ (function(module, exports, __webpack_require__) {

@@ -66,5 +66,5 @@ module.exports = __webpack_require__(1);

/***/ },
/***/ }),
/* 1 */
/***/ function(module, exports, __webpack_require__) {
/***/ (function(module, exports, __webpack_require__) {

@@ -85,2 +85,4 @@ 'use strict';

var _utils = __webpack_require__(8);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

@@ -94,52 +96,2 @@

function noop() {}
function escapeRegExp(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}
function removeLeadingZero(numStr) {
//remove leading zeros
return numStr.replace(/^0+/, '') || '0';
}
/**
* limit decimal numbers to given precision
* Not used .fixedTo because that will break with big numbers
*/
function limitToPrecision(numStr, precision) {
var str = '';
for (var i = 0; i <= precision - 1; i++) {
str += numStr[i] || '0';
}
return str;
}
/**
* This method is required to round prop value to given precision.
* Not used .round or .fixedTo because that will break with big numbers
*/
function roundToPrecision(numStr, precision) {
var numberParts = numStr.split('.');
var roundedDecimalParts = parseFloat('0.' + (numberParts[1] || '0')).toFixed(precision).split('.');
var intPart = numberParts[0].split('').reverse().reduce(function (roundedStr, current, idx) {
if (roundedStr.length > idx) {
return (Number(roundedStr[0]) + Number(current)).toString() + roundedStr.substring(1, roundedStr.length);
}
return current + roundedStr;
}, roundedDecimalParts[0]);
var decimalPart = roundedDecimalParts[1];
return intPart + (decimalPart ? '.' + decimalPart : '');
}
function omit(obj, keyMaps) {
var filteredObj = {};
Object.keys(obj).forEach(function (key) {
if (!keyMaps[key]) filteredObj[key] = obj[key];
});
return filteredObj;
}
var propTypes = {

@@ -153,4 +105,6 @@ thousandSeparator: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.oneOf([true])]),

format: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.func]),
mask: _propTypes2.default.string,
removeFormatting: _propTypes2.default.func,
mask: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.arrayOf(_propTypes2.default.string)]),
value: _propTypes2.default.oneOfType([_propTypes2.default.number, _propTypes2.default.string]),
isNumericString: _propTypes2.default.bool,
customInput: _propTypes2.default.func,

@@ -161,4 +115,6 @@ allowNegative: _propTypes2.default.bool,

onChange: _propTypes2.default.func,
onFocus: _propTypes2.default.func,
type: _propTypes2.default.oneOf(['text', 'tel']),
isAllowed: _propTypes2.default.func
isAllowed: _propTypes2.default.func,
renderText: _propTypes2.default.func
};

@@ -172,9 +128,9 @@

allowNegative: true,
isNumericString: false,
type: 'text',
onChange: noop,
onKeyDown: noop,
onMouseUp: noop,
isAllowed: function isAllowed() {
return true;
}
onChange: _utils.noop,
onKeyDown: _utils.noop,
onMouseUp: _utils.noop,
onFocus: _utils.noop,
isAllowed: _utils.returnTrue
};

@@ -188,11 +144,18 @@

//validate props
var _this = _possibleConstructorReturn(this, (NumberFormat.__proto__ || Object.getPrototypeOf(NumberFormat)).call(this, props));
var value = _this.optimizeValueProp(props);
_this.validateProps();
var formattedValue = _this.formatValueProp();
_this.state = {
value: _this.formatInput(value).formattedValue
value: formattedValue,
numAsString: _this.removeFormatting(formattedValue)
};
_this.onChange = _this.onChange.bind(_this);
_this.onKeyDown = _this.onKeyDown.bind(_this);
_this.onMouseUp = _this.onMouseUp.bind(_this);
_this.onFocus = _this.onFocus.bind(_this);
return _this;

@@ -203,4 +166,4 @@ }

key: 'componentDidUpdate',
value: function componentDidUpdate(prevProps, prevState) {
this.updateValueIfRequired(prevProps, prevState);
value: function componentDidUpdate(prevProps) {
this.updateValueIfRequired(prevProps);
}

@@ -215,13 +178,15 @@ }, {

if (prevProps !== props) {
//validate props
this.validateProps();
var stateValue = state.value;
var value = this.optimizeValueProp(props);
if (value === undefined) value = stateValue;
var lastNumStr = state.numAsString || '';
var _formatInput = this.formatInput(value),
formattedValue = _formatInput.formattedValue;
var formattedValue = props.value === undefined ? this.formatNumString(lastNumStr).formattedValue : this.formatValueProp();
if (formattedValue !== stateValue) {
this.setState({
value: formattedValue
value: formattedValue,
numAsString: this.removeFormatting(formattedValue)
});

@@ -231,62 +196,216 @@ }

}
/** Misc methods **/
}, {
key: 'getFloatString',
value: function getFloatString(num, props) {
props = props || this.props;
value: function getFloatString() {
var num = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
var _getSeparators = this.getSeparators(props),
decimalSeparator = _getSeparators.decimalSeparator,
thousandSeparator = _getSeparators.thousandSeparator;
var _getSeparators = this.getSeparators(),
decimalSeparator = _getSeparators.decimalSeparator;
return (num || '').replace(new RegExp(escapeRegExp(thousandSeparator || ''), 'g'), '').replace(decimalSeparator, '.');
var numRegex = this.getNumberRegex(true);
//remove negation for regex check
var hasNegation = num[0] === '-';
if (hasNegation) num = num.replace('-', '');
num = (num.match(numRegex) || []).join('').replace(decimalSeparator, '.');
//remove extra decimals
var firstDecimalIndex = num.indexOf('.');
if (firstDecimalIndex !== -1) {
num = num.substring(0, firstDecimalIndex) + '.' + num.substring(firstDecimalIndex + 1, num.length).replace(new RegExp((0, _utils.escapeRegExp)(decimalSeparator), 'g'), '');
}
//add negation back
if (hasNegation) num = '-' + num;
return num;
}
//returned regex assumes decimalSeparator is as per prop
}, {
key: 'getFloatValue',
value: function getFloatValue(num, props) {
props = props || this.props;
return parseFloat(this.getFloatString(num, props)) || 0;
key: 'getNumberRegex',
value: function getNumberRegex(g, ignoreDecimalSeparator) {
var _props = this.props,
format = _props.format,
decimalPrecision = _props.decimalPrecision;
var _getSeparators2 = this.getSeparators(),
decimalSeparator = _getSeparators2.decimalSeparator;
return new RegExp('\\d' + (decimalSeparator && decimalPrecision !== 0 && !ignoreDecimalSeparator && !format ? '|' + (0, _utils.escapeRegExp)(decimalSeparator) : ''), g ? 'g' : undefined);
}
}, {
key: 'optimizeValueProp',
value: function optimizeValueProp(props) {
var _getSeparators2 = this.getSeparators(props),
decimalSeparator = _getSeparators2.decimalSeparator;
key: 'getSeparators',
value: function getSeparators() {
var decimalSeparator = this.props.decimalSeparator;
var thousandSeparator = this.props.thousandSeparator;
var decimalPrecision = props.decimalPrecision,
format = props.format;
var value = props.value;
if (thousandSeparator === true) {
thousandSeparator = ',';
}
if (format || !(value || value === 0)) return value;
return {
decimalSeparator: decimalSeparator,
thousandSeparator: thousandSeparator
};
}
}, {
key: 'getMaskAtIndex',
value: function getMaskAtIndex(index) {
var _props$mask = this.props.mask,
mask = _props$mask === undefined ? ' ' : _props$mask;
var isNumber = typeof value === 'number';
if (typeof mask === 'string') {
return mask;
}
if (isNumber) value = value.toString();
return mask[index] || ' ';
}
}, {
key: 'validateProps',
value: function validateProps() {
var mask = this.props.mask;
value = this.removePrefixAndSuffix(isNumber ? value : this.getFloatString(value, props), props);
//validate decimalSeparator and thousandSeparator
//round off value
if (typeof decimalPrecision === 'number') value = roundToPrecision(value, decimalPrecision);
var _getSeparators3 = this.getSeparators(),
decimalSeparator = _getSeparators3.decimalSeparator,
thousandSeparator = _getSeparators3.thousandSeparator;
//correct decimal separator
if (decimalSeparator) {
value = value.replace('.', decimalSeparator);
if (decimalSeparator === thousandSeparator) {
throw new Error('\n Decimal separator can\'t be same as thousand separator.\n\n thousandSeparator: ' + thousandSeparator + ' (thousandSeparator = {true} is same as thousandSeparator = ",")\n decimalSeparator: ' + decimalSeparator + ' (default value for decimalSeparator is .)\n ');
}
//throw error if value has two decimal seperators
if (value.split(decimalSeparator).length > 2) {
throw new Error('\n Wrong input for value props.\n\n More than one decimalSeparator found\n ');
//validate mask
if (mask) {
var maskAsStr = mask === 'string' ? mask : mask.toString();
if (maskAsStr.match(/\d/g)) {
throw new Error('\n Mask ' + mask + ' should not contain numeric character;\n ');
}
}
}
//if decimalPrecision is 0 remove decimalNumbers
if (decimalPrecision === 0) return value.split(decimalSeparator)[0];
/** Misc methods end **/
return value;
/** caret specific methods **/
}, {
key: 'setPatchedCaretPosition',
value: function setPatchedCaretPosition(el, caretPos, currentValue) {
/* setting caret position within timeout of 0ms is required for mobile chrome,
otherwise browser resets the caret position after we set it
We are also setting it without timeout so that in normal browser we don't see the flickering */
(0, _utils.setCaretPosition)(el, caretPos);
setTimeout(function () {
if (el.value === currentValue) (0, _utils.setCaretPosition)(el, caretPos);
}, 0);
}
/* This keeps the caret within typing area so people can't type in between prefix or suffix */
}, {
key: 'correctCaretPosition',
value: function correctCaretPosition(value, caretPos, direction) {
var _props2 = this.props,
prefix = _props2.prefix,
suffix = _props2.suffix,
format = _props2.format;
//in case of format as number limit between prefix and suffix
if (!format) {
var hasNegation = value[0] === '-';
return Math.min(Math.max(caretPos, prefix.length + (hasNegation ? 1 : 0)), value.length - suffix.length);
}
//in case if custom format method don't do anything
if (typeof format === 'function') return caretPos;
/* in case format is string find the closest # position from the caret position */
//in case the caretPos have input value on it don't do anything
if (format[caretPos] === '#' && (0, _utils.charIsNumber)(value[caretPos])) return caretPos;
//if caretPos is just after input value don't do anything
if (format[caretPos - 1] === '#' && (0, _utils.charIsNumber)(value[caretPos - 1])) return caretPos;
//find the nearest caret position
var firstHashPosition = format.indexOf('#');
var lastHashPosition = format.lastIndexOf('#');
//limit the cursor between the first # position and the last # position
caretPos = Math.min(Math.max(caretPos, firstHashPosition), lastHashPosition + 1);
var nextPos = format.substring(caretPos, format.length).indexOf('#');
var caretLeftBound = caretPos;
var caretRightBoud = caretPos + (nextPos === -1 ? 0 : nextPos);
//get the position where the last number is present
while (caretLeftBound > firstHashPosition && (format[caretLeftBound] !== '#' || !(0, _utils.charIsNumber)(value[caretLeftBound]))) {
caretLeftBound -= 1;
}
var goToLeft = !(0, _utils.charIsNumber)(value[caretRightBoud]) || direction === 'left' && caretPos !== firstHashPosition || caretPos - caretLeftBound < caretRightBoud - caretPos;
return goToLeft ? caretLeftBound + 1 : caretRightBoud;
}
}, {
key: 'getCaretPosition',
value: function getCaretPosition(inputValue, formattedValue, caretPos) {
var format = this.props.format;
var stateValue = this.state.value;
var numRegex = this.getNumberRegex(true);
var inputNumber = (inputValue.match(numRegex) || []).join('');
var formattedNumber = (formattedValue.match(numRegex) || []).join('');
var j = void 0,
i = void 0;
j = 0;
for (i = 0; i < caretPos; i++) {
var currentInputChar = inputValue[i];
var currentFormatChar = formattedValue[j] || '';
//no need to increase new cursor position if formatted value does not have those characters
//case inputValue = 1a23 and formattedValue = 123
if (!currentInputChar.match(numRegex) && currentInputChar !== currentFormatChar) continue;
//When we are striping out leading zeros maintain the new cursor position
//Case inputValue = 00023 and formattedValue = 23;
if (currentInputChar === '0' && currentFormatChar.match(numRegex) && currentFormatChar !== '0' && inputNumber.length !== formattedNumber.length) continue;
//we are not using currentFormatChar because j can change here
while (currentInputChar !== formattedValue[j] && j < formattedValue.length) {
j++;
}j++;
}
if (typeof format === 'string' && !stateValue) {
//set it to the maximum value so it goes after the last number
j = formattedValue.length;
}
//correct caret position if its outside of editable area
j = this.correctCaretPosition(formattedValue, j);
return j;
}
/** caret specific methods ends **/
/** methods to remove formattting **/
}, {
key: 'removePrefixAndSuffix',
value: function removePrefixAndSuffix(val, props) {
var format = props.format,
prefix = props.prefix,
suffix = props.suffix;
value: function removePrefixAndSuffix(val) {
var _props3 = this.props,
format = _props3.format,
prefix = _props3.prefix,
suffix = _props3.suffix;

@@ -315,237 +434,281 @@ //remove prefix and suffix

}, {
key: 'getSeparators',
value: function getSeparators(props) {
props = props || this.props;
key: 'removePatternFormatting',
value: function removePatternFormatting(val) {
var format = this.props.format;
var _props = props,
decimalSeparator = _props.decimalSeparator;
var _props2 = props,
thousandSeparator = _props2.thousandSeparator;
var formatArray = format.split('#').filter(function (str) {
return str !== '';
});
var start = 0;
var numStr = '';
for (var i = 0, ln = formatArray.length; i <= ln; i++) {
var part = formatArray[i] || '';
if (thousandSeparator === true) {
thousandSeparator = ',';
}
//if i is the last fragment take the index of end of the value
//For case like +1 (911) 911 91 91 having pattern +1 (###) ### ## ##
var index = i === ln ? val.length : val.indexOf(part, start);
if (decimalSeparator === thousandSeparator) {
throw new Error('\n Decimal separator can\'t be same as thousand separator.\n\n thousandSeparator: ' + thousandSeparator + ' (thousandSeparator = {true} is same as thousandSeparator = ",")\n decimalSeparator: ' + decimalSeparator + ' (default value for decimalSeparator is .)\n ');
/* in any case if we don't find the pattern part in the value assume the val as numeric string
This will be also in case if user has started typing, in any other case it will not be -1
unless wrong prop value is provided */
if (index === -1) {
numStr = val;
break;
} else {
numStr += val.substring(start, index);
start = index + part.length;
}
}
return {
decimalSeparator: decimalSeparator,
thousandSeparator: thousandSeparator
};
return (numStr.match(/\d/g) || []).join('');
}
}, {
key: 'getNumberRegex',
value: function getNumberRegex(g, ignoreDecimalSeparator) {
var _props3 = this.props,
format = _props3.format,
decimalPrecision = _props3.decimalPrecision;
key: 'removeFormatting',
value: function removeFormatting(val) {
var _props4 = this.props,
format = _props4.format,
removeFormatting = _props4.removeFormatting;
var _getSeparators3 = this.getSeparators(),
decimalSeparator = _getSeparators3.decimalSeparator;
if (!val) return val;
return new RegExp('\\d' + (decimalSeparator && decimalPrecision !== 0 && !ignoreDecimalSeparator && !format ? '|' + escapeRegExp(decimalSeparator) : ''), g ? 'g' : undefined);
if (!format) {
val = this.removePrefixAndSuffix(val);
val = this.getFloatString(val);
} else if (typeof format === 'string') {
val = this.removePatternFormatting(val);
} else if (typeof removeFormatting === 'function') {
//condition need to be handled if format method is provide,
val = removeFormatting(val);
} else {
val = (val.match(/\d/g) || []).join('');
}
return val;
}
/** methods to remove formattting end **/
/*** format specific methods start ***/
/**
* Format when # based string is provided
* @param {string} numStr Numeric String
* @return {string} formatted Value
*/
}, {
key: 'setCaretPosition',
value: function setCaretPosition(el, caretPos) {
el.value = el.value;
// ^ this is used to not only get "focus", but
// to make sure we don't have it everything -selected-
// (it causes an issue in chrome, and having it doesn't hurt any other browser)
if (el !== null) {
if (el.createTextRange) {
var range = el.createTextRange();
range.move('character', caretPos);
range.select();
return true;
key: 'formatWithPattern',
value: function formatWithPattern(numStr) {
var format = this.props.format;
var hashCount = 0;
var formattedNumberAry = format.split('');
for (var i = 0, ln = format.length; i < ln; i++) {
if (format[i] === '#') {
formattedNumberAry[i] = numStr[hashCount] || this.getMaskAtIndex(hashCount);
hashCount += 1;
}
// (el.selectionStart === 0 added for Firefox bug)
if (el.selectionStart || el.selectionStart === 0) {
el.focus();
el.setSelectionRange(caretPos, caretPos);
return true;
}
// fail city, fortunately this never happens (as far as I've tested) :)
el.focus();
return false;
}
return formattedNumberAry.join('');
}
/**
* @param {string} numStr Numeric string/floatString] It always have decimalSeparator as .
* @return {string} formatted Value
*/
}, {
key: 'setPatchedCaretPosition',
value: function setPatchedCaretPosition(el, caretPos, currentValue) {
var _this2 = this;
key: 'formatAsNumber',
value: function formatAsNumber(numStr) {
var _props5 = this.props,
decimalPrecision = _props5.decimalPrecision,
allowNegative = _props5.allowNegative,
prefix = _props5.prefix,
suffix = _props5.suffix;
/*
setting caret position within timeout of 0ms is required for mobile chrome,
otherwise browser resets the caret position after we set it
We are also setting it without timeout so that in normal browser we don't see the flickering
*/
this.setCaretPosition(el, caretPos);
setTimeout(function () {
if (el.value === currentValue) _this2.setCaretPosition(el, caretPos);
}, 0);
}
var _getSeparators4 = this.getSeparators(),
thousandSeparator = _getSeparators4.thousandSeparator,
decimalSeparator = _getSeparators4.decimalSeparator;
/* This keeps the caret within typing area so people can't type in between prefix or suffix */
// Check if its negative number and remove negation for futher formatting
var hasNagation = numStr[0] === '-';
numStr = numStr.replace('-', '');
var hasDecimalSeparator = numStr.indexOf('.') !== -1 || decimalPrecision;
var parts = numStr.split('.');
var beforeDecimal = parts[0];
var afterDecimal = parts[1] || '';
//remove leading zeros from number before decimal
beforeDecimal = (0, _utils.removeLeadingZero)(beforeDecimal);
//apply decimal precision if its defined
if (decimalPrecision !== undefined) afterDecimal = (0, _utils.limitToPrecision)(afterDecimal, decimalPrecision);
if (thousandSeparator) {
beforeDecimal = beforeDecimal.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1' + thousandSeparator);
}
//add prefix and suffix
if (prefix) beforeDecimal = prefix + beforeDecimal;
if (suffix) afterDecimal = afterDecimal + suffix;
//restore negation sign
if (hasNagation && allowNegative) beforeDecimal = '-' + beforeDecimal;
numStr = beforeDecimal + (hasDecimalSeparator && decimalSeparator || '') + afterDecimal;
return numStr;
}
}, {
key: 'correctCaretPosition',
value: function correctCaretPosition(value, caretPos) {
var _props4 = this.props,
prefix = _props4.prefix,
suffix = _props4.suffix;
key: 'formatNumString',
value: function formatNumString(value) {
var format = this.props.format;
return Math.min(Math.max(caretPos, prefix.length), value.length - suffix.length);
var formattedValue = value;
if (value === '') {
formattedValue = '';
} else if (value === '-' && !format) {
formattedValue = '-';
value = '';
} else if (typeof format === 'string') {
formattedValue = this.formatWithPattern(formattedValue);
} else if (typeof format === 'function') {
formattedValue = format(formattedValue);
} else {
formattedValue = this.formatAsNumber(formattedValue);
}
return {
value: value,
formattedValue: formattedValue
};
}
}, {
key: 'formatWithPattern',
value: function formatWithPattern(str) {
var _props5 = this.props,
format = _props5.format,
mask = _props5.mask;
key: 'formatValueProp',
value: function formatValueProp() {
var _props6 = this.props,
format = _props6.format,
decimalPrecision = _props6.decimalPrecision;
var _props7 = this.props,
value = _props7.value,
isNumericString = _props7.isNumericString;
if (!format) return str;
var hashCount = format.split('#').length - 1;
var hashIdx = 0;
var frmtdStr = format;
// if value is not defined return empty string
for (var i = 0, ln = str.length; i < ln; i++) {
if (i < hashCount) {
hashIdx = frmtdStr.indexOf('#');
frmtdStr = frmtdStr.replace('#', str[i]);
}
if (value === undefined) return '';
if (typeof value === 'number') {
value = value.toString();
isNumericString = true;
}
var lastIdx = frmtdStr.lastIndexOf('#');
//round the number based on decimalPrecision
//format only if non formatted value is provided
if (isNumericString && !format && typeof decimalPrecision === 'number') {
value = (0, _utils.roundToPrecision)(value, decimalPrecision);
}
if (mask) {
return frmtdStr.replace(/#/g, mask);
}
return frmtdStr.substring(0, hashIdx + 1) + (lastIdx !== -1 ? frmtdStr.substring(lastIdx + 1, frmtdStr.length) : '');
var values = isNumericString ? this.formatNumString(value) : this.formatInput(value);
return values.formattedValue;
}
}, {
key: 'formatInput',
value: function formatInput(val) {
var props = this.props,
removePrefixAndSuffix = this.removePrefixAndSuffix;
var prefix = props.prefix,
suffix = props.suffix,
mask = props.mask,
format = props.format,
allowNegative = props.allowNegative,
decimalPrecision = props.decimalPrecision;
key: 'formatNegation',
value: function formatNegation() {
var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
var allowNegative = this.props.allowNegative;
var _getSeparators4 = this.getSeparators(),
thousandSeparator = _getSeparators4.thousandSeparator,
decimalSeparator = _getSeparators4.decimalSeparator;
var negationRegex = new RegExp('(-)');
var doubleNegationRegex = new RegExp('(-)(.)*(-)');
var maskPattern = format && typeof format == 'string' && !!mask;
var numRegex = this.getNumberRegex(true);
var hasNegative = void 0,
removeNegative = void 0;
// Check number has '-' value
var hasNegation = negationRegex.test(value);
//change val to string if its number
if (typeof val === 'number') val = val + '';
// Check number has 2 or more '-' values
var removeNegation = doubleNegationRegex.test(value);
var negativeRegex = new RegExp('(-)');
var doubleNegativeRegex = new RegExp('(-)(.)*(-)');
//remove negation
value = value.replace(/-/g, '');
//check if it has negative numbers
if (allowNegative && !format) {
// Check number has '-' value
hasNegative = negativeRegex.test(val);
// Check number has 2 or more '-' values
removeNegative = doubleNegativeRegex.test(val);
if (hasNegation && !removeNegation && allowNegative) {
value = '-' + value;
}
//remove prefix and suffix
val = removePrefixAndSuffix(val, props);
return value;
}
}, {
key: 'formatInput',
value: function formatInput() {
var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
var format = this.props.format;
var valMatch = val && val.match(numRegex);
//format negation only if we are formatting as number
if (!valMatch && removeNegative) {
return { value: '', formattedValue: '' };
} else if (!valMatch && hasNegative) {
return { value: '', formattedValue: '-' };
} else if (!valMatch) {
return { value: '', formattedValue: maskPattern ? '' : '' };
if (!format) {
value = this.formatNegation(value);
}
var num = val.match(numRegex).join('');
//remove formatting from number
value = this.removeFormatting(value);
var formattedValue = num;
return this.formatNumString(value);
}
if (format) {
if (typeof format == 'string') {
formattedValue = this.formatWithPattern(formattedValue);
} else if (typeof format == 'function') {
formattedValue = format(formattedValue);
}
} else {
var hasDecimalSeparator = formattedValue.indexOf(decimalSeparator) !== -1 || decimalPrecision;
/*** format specific methods end ***/
var parts = formattedValue.split(decimalSeparator);
var beforeDecimal = parts[0];
var afterDecimal = parts[1] || '';
}, {
key: 'isCharacterAFormat',
value: function isCharacterAFormat(caretPos, value) {
var _props8 = this.props,
format = _props8.format,
prefix = _props8.prefix,
suffix = _props8.suffix;
//remove leading zeros from number before decimal
beforeDecimal = removeLeadingZero(beforeDecimal);
//check within format pattern
//apply decimal precision if its defined
if (decimalPrecision !== undefined) afterDecimal = limitToPrecision(afterDecimal, decimalPrecision);
if (typeof format === 'string' && format[caretPos] !== '#') return true;
if (thousandSeparator) {
beforeDecimal = beforeDecimal.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1' + thousandSeparator);
}
//check in number format
if (!format && (caretPos < prefix.length || caretPos >= value.length - suffix.length)) return true;
return false;
}
}, {
key: 'checkIfFormatGotDeleted',
value: function checkIfFormatGotDeleted(start, end, value) {
//add prefix and suffix
if (prefix) beforeDecimal = prefix + beforeDecimal;
if (suffix) afterDecimal = afterDecimal + suffix;
for (var i = start; i < end; i++) {
if (this.isCharacterAFormat(i, value)) return true;
}
return false;
}
if (hasNegative && !removeNegative) beforeDecimal = '-' + beforeDecimal;
/**
* This will check if any formatting got removed by the delete or backspace and reset the value
* It will also work as fallback if android chome keyDown handler does not work
**/
formattedValue = beforeDecimal + (hasDecimalSeparator && decimalSeparator || '') + afterDecimal;
}
return {
value: (hasNegative && !removeNegative ? '-' : '') + removePrefixAndSuffix(formattedValue, props).match(numRegex).join(''),
formattedValue: formattedValue
};
}
}, {
key: 'getCaretPosition',
value: function getCaretPosition(inputValue, formattedValue, caretPos) {
var numRegex = this.getNumberRegex(true);
var inputNumber = (inputValue.match(numRegex) || []).join('');
var formattedNumber = (formattedValue.match(numRegex) || []).join('');
var j = void 0,
i = void 0;
key: 'correctInputValue',
value: function correctInputValue(caretPos, lastValue, value) {
j = 0;
//don't do anyhting if something got added, or if value is empty string (when whole input is cleared)
if (value.length >= lastValue.length || !value.length) {
return value;
}
for (i = 0; i < caretPos; i++) {
var currentInputChar = inputValue[i];
var currentFormatChar = formattedValue[j] || '';
//no need to increase new cursor position if formatted value does not have those characters
//case inputValue = 1a23 and formattedValue = 123
if (!currentInputChar.match(numRegex) && currentInputChar !== currentFormatChar) continue;
var start = caretPos;
var lastValueParts = (0, _utils.splitString)(lastValue, caretPos);
var newValueParts = (0, _utils.splitString)(value, caretPos);
var deletedIndex = lastValueParts[1].lastIndexOf(newValueParts[1]);
var diff = deletedIndex !== -1 ? lastValueParts[1].substring(0, deletedIndex) : '';
var end = start + diff.length;
//When we are striping out leading zeros maintain the new cursor position
//Case inputValue = 00023 and formattedValue = 23;
if (currentInputChar === '0' && currentFormatChar.match(numRegex) && currentFormatChar !== '0' && inputNumber.length !== formattedNumber.length) continue;
//we are not using currentFormatChar because j can change here
while (currentInputChar !== formattedValue[j] && !(formattedValue[j] || '').match(numRegex) && j < formattedValue.length) {
j++;
}j++;
//if format got deleted reset the value to last value
if (this.checkIfFormatGotDeleted(start, end, lastValue)) {
value = lastValue;
}
//correct caret position if its outsize of editable area
j = this.correctCaretPosition(formattedValue, j);
return j;
return value;
}

@@ -562,12 +725,13 @@ }, {

var lastValue = state.value;
var lastValue = state.value || '';
var _formatInput2 = this.formatInput(inputValue),
formattedValue = _formatInput2.formattedValue,
value = _formatInput2.value; // eslint-disable-line prefer-const
/*Max of selectionStart and selectionEnd is taken for the patch of pixel and other mobile device caret bug*/
var currentCaretPosition = Math.max(el.selectionStart, el.selectionEnd);
inputValue = this.correctInputValue(currentCaretPosition, lastValue, inputValue);
var currentCaretPosition = Math.max(el.selectionStart, el.selectionEnd);
var _formatInput = this.formatInput(inputValue),
_formatInput$formatte = _formatInput.formattedValue,
formattedValue = _formatInput$formatte === undefined ? '' : _formatInput$formatte,
value = _formatInput.value; // eslint-disable-line prefer-const

@@ -577,3 +741,3 @@ var valueObj = {

value: value,
floatValue: this.getFloatValue(value)
floatValue: parseFloat(value)
};

@@ -596,3 +760,3 @@

if (formattedValue !== lastValue) {
this.setState({ value: formattedValue }, function () {
this.setState({ value: formattedValue, numAsString: this.removeFormatting(formattedValue) }, function () {
props.onChange(e, valueObj);

@@ -608,39 +772,69 @@ });

var el = e.target;
var key = e.key;
var selectionEnd = el.selectionEnd,
value = el.value;
var selectionStart = el.selectionStart;
var _props6 = this.props,
decimalPrecision = _props6.decimalPrecision,
prefix = _props6.prefix,
suffix = _props6.suffix;
var key = e.key;
var expectedCaretPosition = void 0;
var _props9 = this.props,
decimalPrecision = _props9.decimalPrecision,
prefix = _props9.prefix,
suffix = _props9.suffix,
format = _props9.format,
onKeyDown = _props9.onKeyDown;
var numRegex = this.getNumberRegex(false, decimalPrecision !== undefined);
var negativeRegex = new RegExp('-');
var isPatternFormat = typeof format === 'string';
//Handle backspace and delete against non numerical/decimal characters
if (selectionStart === selectionEnd) {
var newCaretPosition = selectionStart;
//Handle backspace and delete against non numerical/decimal characters or arrow keys
if (key === 'ArrowLeft' || key === 'Backspace') {
expectedCaretPosition = selectionStart - 1;
} else if (key === 'ArrowRight') {
expectedCaretPosition = selectionStart + 1;
} else if (key === 'Delete') {
expectedCaretPosition = selectionStart;
}
if (key === 'ArrowLeft' || key === 'ArrowRight') {
selectionStart += key === 'ArrowLeft' ? -1 : +1;
newCaretPosition = this.correctCaretPosition(value, selectionStart);
} else if (key === 'Delete' && !numRegex.test(value[selectionStart]) && !negativeRegex.test(value[selectionStart])) {
while (!numRegex.test(value[newCaretPosition]) && newCaretPosition < value.length - suffix.length) {
newCaretPosition++;
}
} else if (key === 'Backspace' && !numRegex.test(value[selectionStart - 1]) && !negativeRegex.test(value[selectionStart - 1])) {
while (!numRegex.test(value[newCaretPosition - 1]) && newCaretPosition > prefix.length) {
newCaretPosition--;
}
}
//if expectedCaretPosition is not set it means we don't want to Handle keyDown
//also if multiple characters are selected don't handle
if (expectedCaretPosition === undefined || selectionStart !== selectionEnd) {
onKeyDown(e);
return;
}
if (newCaretPosition !== selectionStart) {
e.preventDefault();
this.setPatchedCaretPosition(el, newCaretPosition, value);
var newCaretPosition = expectedCaretPosition;
var leftBound = isPatternFormat ? format.indexOf('#') : prefix.length;
var rightBound = isPatternFormat ? format.lastIndexOf('#') + 1 : value.length - suffix.length;
if (key === 'ArrowLeft' || key === 'ArrowRight') {
var direction = key === 'ArrowLeft' ? 'left' : 'right';
newCaretPosition = this.correctCaretPosition(value, expectedCaretPosition, direction);
} else if (key === 'Delete' && !numRegex.test(value[expectedCaretPosition]) && !negativeRegex.test(value[expectedCaretPosition])) {
while (!numRegex.test(value[newCaretPosition]) && newCaretPosition < rightBound) {
newCaretPosition++;
}
} else if (key === 'Backspace' && !numRegex.test(value[expectedCaretPosition]) && !negativeRegex.test(value[expectedCaretPosition])) {
while (!numRegex.test(value[newCaretPosition - 1]) && newCaretPosition > leftBound) {
newCaretPosition--;
}
newCaretPosition = this.correctCaretPosition(value, newCaretPosition, 'left');
}
if (newCaretPosition !== expectedCaretPosition || expectedCaretPosition < leftBound || expectedCaretPosition > rightBound) {
e.preventDefault();
this.setPatchedCaretPosition(el, newCaretPosition, value);
}
/* NOTE: this is just required for unit test as we need to get the newCaretPosition,
Remove this when you find different solution */
if (e.isUnitTestRun) {
this.setPatchedCaretPosition(el, newCaretPosition, value);
}
this.props.onKeyDown(e);
}
/** required to handle the caret position when click anywhere within the input **/
}, {

@@ -665,22 +859,46 @@ key: 'onMouseUp',

}, {
key: 'onFocus',
value: function onFocus(e) {
var el = e.target;
var selectionStart = el.selectionStart,
value = el.value;
var caretPostion = this.correctCaretPosition(value, selectionStart);
if (caretPostion !== selectionStart) {
this.setPatchedCaretPosition(el, caretPostion, value);
}
this.props.onFocus(e);
}
}, {
key: 'render',
value: function render() {
var props = omit(this.props, propTypes);
var _props10 = this.props,
type = _props10.type,
displayType = _props10.displayType,
customInput = _props10.customInput,
renderText = _props10.renderText;
var value = this.state.value;
var inputProps = _extends({}, props, {
type: this.props.type,
value: this.state.value,
var otherProps = (0, _utils.omit)(this.props, propTypes);
var inputProps = _extends({}, otherProps, {
type: type,
value: value,
onChange: this.onChange,
onKeyDown: this.onKeyDown,
onMouseUp: this.onMouseUp
onMouseUp: this.onMouseUp,
onFocus: this.onFocus
});
if (this.props.displayType === 'text') {
return _react2.default.createElement(
if (displayType === 'text') {
return renderText ? renderText(value) || null : _react2.default.createElement(
'span',
props,
this.state.value
otherProps,
value
);
} else if (this.props.customInput) {
var CustomInput = this.props.customInput;
} else if (customInput) {
var CustomInput = customInput;
return _react2.default.createElement(CustomInput, inputProps);

@@ -701,13 +919,11 @@ }

/***/ },
/***/ }),
/* 2 */
/***/ function(module, exports, __webpack_require__) {
/***/ (function(module, exports, __webpack_require__) {
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

@@ -738,13 +954,11 @@

/***/ },
/***/ }),
/* 3 */
/***/ function(module, exports, __webpack_require__) {
/***/ (function(module, exports, __webpack_require__) {
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

@@ -794,3 +1008,4 @@

oneOfType: getShim,
shape: getShim
shape: getShim,
exact: getShim
};

@@ -805,5 +1020,5 @@

/***/ },
/***/ }),
/* 4 */
/***/ function(module, exports) {
/***/ (function(module, exports) {

@@ -814,7 +1029,5 @@ "use strict";

* Copyright (c) 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*

@@ -850,13 +1063,11 @@ *

/***/ },
/***/ }),
/* 5 */
/***/ function(module, exports, __webpack_require__) {
/***/ (function(module, exports, __webpack_require__) {
/**
* Copyright (c) 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*

@@ -911,13 +1122,11 @@ */

/***/ },
/***/ }),
/* 6 */
/***/ function(module, exports) {
/***/ (function(module, exports) {
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

@@ -932,11 +1141,120 @@

/***/ },
/***/ }),
/* 7 */
/***/ function(module, exports) {
/***/ (function(module, exports) {
module.exports = __WEBPACK_EXTERNAL_MODULE_7__;
/***/ }
/***/ }),
/* 8 */
/***/ (function(module, exports) {
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.noop = noop;
exports.returnTrue = returnTrue;
exports.charIsNumber = charIsNumber;
exports.escapeRegExp = escapeRegExp;
exports.removeLeadingZero = removeLeadingZero;
exports.splitString = splitString;
exports.limitToPrecision = limitToPrecision;
exports.roundToPrecision = roundToPrecision;
exports.omit = omit;
exports.setCaretPosition = setCaretPosition;
// basic noop function
function noop() {}
function returnTrue() {
return true;
}
function charIsNumber(char) {
return !!(char || '').match(/\d/);
}
function escapeRegExp(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}
function removeLeadingZero(numStr) {
//remove leading zeros
return numStr.replace(/^0+/, '') || '0';
}
function splitString(str, index) {
return [str.substring(0, index), str.substring(index)];
}
/**
* limit decimal numbers to given precision
* Not used .fixedTo because that will break with big numbers
*/
function limitToPrecision(numStr, precision) {
var str = '';
for (var i = 0; i <= precision - 1; i++) {
str += numStr[i] || '0';
}
return str;
}
/**
* This method is required to round prop value to given precision.
* Not used .round or .fixedTo because that will break with big numbers
*/
function roundToPrecision(numStr, precision) {
var numberParts = numStr.split('.');
var roundedDecimalParts = parseFloat('0.' + (numberParts[1] || '0')).toFixed(precision).split('.');
var intPart = numberParts[0].split('').reverse().reduce(function (roundedStr, current, idx) {
if (roundedStr.length > idx) {
return (Number(roundedStr[0]) + Number(current)).toString() + roundedStr.substring(1, roundedStr.length);
}
return current + roundedStr;
}, roundedDecimalParts[0]);
var decimalPart = roundedDecimalParts[1];
return intPart + (decimalPart ? '.' + decimalPart : '');
}
function omit(obj, keyMaps) {
var filteredObj = {};
Object.keys(obj).forEach(function (key) {
if (!keyMaps[key]) filteredObj[key] = obj[key];
});
return filteredObj;
}
/** set the caret positon in an input field **/
function setCaretPosition(el, caretPos) {
el.value = el.value;
// ^ this is used to not only get "focus", but
// to make sure we don't have it everything -selected-
// (it causes an issue in chrome, and having it doesn't hurt any other browser)
if (el !== null) {
if (el.createTextRange) {
var range = el.createTextRange();
range.move('character', caretPos);
range.select();
return true;
}
// (el.selectionStart === 0 added for Firefox bug)
if (el.selectionStart || el.selectionStart === 0) {
el.focus();
el.setSelectionRange(caretPos, caretPos);
return true;
}
// fail city, fortunately this never happens (as far as I've tested) :)
el.focus();
return false;
}
}
/***/ })
/******/ ])
});
;
/*!
* react-number-format - 2.0.4
* react-number-format - 3.0.0-alpha
* Author : Sudhanshu Yadav
* Copyright (c) 2016,2017 to Sudhanshu Yadav - ignitersworld.com , released under the MIT license.
*/
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react")):"function"==typeof define&&define.amd?define(["react"],t):"object"==typeof exports?exports.NumberFormat=t(require("react")):e.NumberFormat=t(e.React)}(this,function(e){return function(e){function t(n){if(r[n])return r[n].exports;var o=r[n]={exports:{},id:n,loaded:!1};return e[n].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var r={};return t.m=e,t.c=r,t.p="",t(0)}([function(e,t,r){e.exports=r(1)},function(e,t,r){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function u(){}function s(e){return e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}function f(e){return e.replace(/^0+/,"")||"0"}function l(e,t){for(var r="",n=0;n<=t-1;n++)r+=e[n]||"0";return r}function p(e,t){var r=e.split("."),n=parseFloat("0."+(r[1]||"0")).toFixed(t).split("."),o=r[0].split("").reverse().reduce(function(e,t,r){return e.length>r?(Number(e[0])+Number(t)).toString()+e.substring(1,e.length):t+e},n[0]),a=n[1];return o+(a?"."+a:"")}function c(e,t){var r={};return Object.keys(e).forEach(function(n){t[n]||(r[n]=e[n])}),r}var h=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e},d=function(){function e(e,t){for(var r=0;r<t.length;r++){var n=t[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}return function(t,r,n){return r&&e(t.prototype,r),n&&e(t,n),t}}(),v=r(2),m=n(v),g=r(7),y=n(g),x={thousandSeparator:m.default.oneOfType([m.default.string,m.default.oneOf([!0])]),decimalSeparator:m.default.string,decimalPrecision:m.default.number,displayType:m.default.oneOf(["input","text"]),prefix:m.default.string,suffix:m.default.string,format:m.default.oneOfType([m.default.string,m.default.func]),mask:m.default.string,value:m.default.oneOfType([m.default.number,m.default.string]),customInput:m.default.func,allowNegative:m.default.bool,onKeyDown:m.default.func,onMouseUp:m.default.func,onChange:m.default.func,type:m.default.oneOf(["text","tel"]),isAllowed:m.default.func},b={displayType:"input",decimalSeparator:".",prefix:"",suffix:"",allowNegative:!0,type:"text",onChange:u,onKeyDown:u,onMouseUp:u,isAllowed:function(){return!0}},S=function(e){function t(e){o(this,t);var r=a(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e)),n=r.optimizeValueProp(e);return r.state={value:r.formatInput(n).formattedValue},r.onChange=r.onChange.bind(r),r.onKeyDown=r.onKeyDown.bind(r),r.onMouseUp=r.onMouseUp.bind(r),r}return i(t,e),d(t,[{key:"componentDidUpdate",value:function(e,t){this.updateValueIfRequired(e,t)}},{key:"updateValueIfRequired",value:function(e){var t=this.props,r=this.state;if(e!==t){var n=r.value,o=this.optimizeValueProp(t);void 0===o&&(o=n);var a=this.formatInput(o),i=a.formattedValue;i!==n&&this.setState({value:i})}}},{key:"getFloatString",value:function(e,t){t=t||this.props;var r=this.getSeparators(t),n=r.decimalSeparator,o=r.thousandSeparator;return(e||"").replace(new RegExp(s(o||""),"g"),"").replace(n,".")}},{key:"getFloatValue",value:function(e,t){return t=t||this.props,parseFloat(this.getFloatString(e,t))||0}},{key:"optimizeValueProp",value:function(e){var t=this.getSeparators(e),r=t.decimalSeparator,n=e.decimalPrecision,o=e.format,a=e.value;if(o||!a&&0!==a)return a;var i="number"==typeof a;if(i&&(a=a.toString()),a=this.removePrefixAndSuffix(i?a:this.getFloatString(a,e),e),"number"==typeof n&&(a=p(a,n)),r&&(a=a.replace(".",r)),a.split(r).length>2)throw new Error("\n Wrong input for value props.\n\n More than one decimalSeparator found\n ");return 0===n?a.split(r)[0]:a}},{key:"removePrefixAndSuffix",value:function(e,t){var r=t.format,n=t.prefix,o=t.suffix;if(!r&&e){var a="-"===e[0];a&&(e=e.substring(1,e.length)),e=n&&0===e.indexOf(n)?e.substring(n.length,e.length):e;var i=e.lastIndexOf(o);e=o&&i!==-1&&i===e.length-o.length?e.substring(0,i):e,a&&(e="-"+e)}return e}},{key:"getSeparators",value:function(e){e=e||this.props;var t=e,r=t.decimalSeparator,n=e,o=n.thousandSeparator;if(o===!0&&(o=","),r===o)throw new Error("\n Decimal separator can't be same as thousand separator.\n\n thousandSeparator: "+o+' (thousandSeparator = {true} is same as thousandSeparator = ",")\n decimalSeparator: '+r+" (default value for decimalSeparator is .)\n ");return{decimalSeparator:r,thousandSeparator:o}}},{key:"getNumberRegex",value:function(e,t){var r=this.props,n=r.format,o=r.decimalPrecision,a=this.getSeparators(),i=a.decimalSeparator;return new RegExp("\\d"+(!i||0===o||t||n?"":"|"+s(i)),e?"g":void 0)}},{key:"setCaretPosition",value:function(e,t){if(e.value=e.value,null!==e){if(e.createTextRange){var r=e.createTextRange();return r.move("character",t),r.select(),!0}return e.selectionStart||0===e.selectionStart?(e.focus(),e.setSelectionRange(t,t),!0):(e.focus(),!1)}}},{key:"setPatchedCaretPosition",value:function(e,t,r){var n=this;this.setCaretPosition(e,t),setTimeout(function(){e.value===r&&n.setCaretPosition(e,t)},0)}},{key:"correctCaretPosition",value:function(e,t){var r=this.props,n=r.prefix,o=r.suffix;return Math.min(Math.max(t,n.length),e.length-o.length)}},{key:"formatWithPattern",value:function(e){var t=this.props,r=t.format,n=t.mask;if(!r)return e;for(var o=r.split("#").length-1,a=0,i=r,u=0,s=e.length;u<s;u++)u<o&&(a=i.indexOf("#"),i=i.replace("#",e[u]));var f=i.lastIndexOf("#");return n?i.replace(/#/g,n):i.substring(0,a+1)+(f!==-1?i.substring(f+1,i.length):"")}},{key:"formatInput",value:function(e){var t=this.props,r=this.removePrefixAndSuffix,n=t.prefix,o=t.suffix,a=(t.mask,t.format),i=t.allowNegative,u=t.decimalPrecision,s=this.getSeparators(),p=s.thousandSeparator,c=s.decimalSeparator,h=this.getNumberRegex(!0),d=void 0,v=void 0;"number"==typeof e&&(e+="");var m=new RegExp("(-)"),g=new RegExp("(-)(.)*(-)");i&&!a&&(d=m.test(e),v=g.test(e)),e=r(e,t);var y=e&&e.match(h);if(!y&&v)return{value:"",formattedValue:""};if(!y&&d)return{value:"",formattedValue:"-"};if(!y)return{value:"",formattedValue:""};var x=e.match(h).join(""),b=x;if(a)"string"==typeof a?b=this.formatWithPattern(b):"function"==typeof a&&(b=a(b));else{var S=b.indexOf(c)!==-1||u,P=b.split(c),w=P[0],O=P[1]||"";w=f(w),void 0!==u&&(O=l(O,u)),p&&(w=w.replace(/(\d)(?=(\d{3})+(?!\d))/g,"$1"+p)),n&&(w=n+w),o&&(O+=o),d&&!v&&(w="-"+w),b=w+(S&&c||"")+O}return{value:(d&&!v?"-":"")+r(b,t).match(h).join(""),formattedValue:b}}},{key:"getCaretPosition",value:function(e,t,r){var n=this.getNumberRegex(!0),o=(e.match(n)||[]).join(""),a=(t.match(n)||[]).join(""),i=void 0,u=void 0;for(i=0,u=0;u<r;u++){var s=e[u],f=t[i]||"";if((s.match(n)||s===f)&&("0"!==s||!f.match(n)||"0"===f||o.length===a.length)){for(;s!==t[i]&&!(t[i]||"").match(n)&&i<t.length;)i++;i++}}return i=this.correctCaretPosition(t,i)}},{key:"onChange",value:function(e){e.persist();var t=e.target,r=t.value,n=this.state,o=this.props,a=o.isAllowed,i=n.value,u=this.formatInput(r),s=u.formattedValue,f=u.value,l=Math.max(t.selectionStart,t.selectionEnd),p={formattedValue:s,value:f,floatValue:this.getFloatValue(f)};a(p)||(s=i),t.value=s;var c=this.getCaretPosition(r,s,l);return this.setPatchedCaretPosition(t,c,s),s!==i&&this.setState({value:s},function(){o.onChange(e,p)}),f}},{key:"onKeyDown",value:function(e){var t=e.target,r=t.selectionEnd,n=t.value,o=t.selectionStart,a=this.props,i=a.decimalPrecision,u=a.prefix,s=a.suffix,f=e.key,l=this.getNumberRegex(!1,void 0!==i),p=new RegExp("-");if(o===r){var c=o;if("ArrowLeft"===f||"ArrowRight"===f)o+="ArrowLeft"===f?-1:1,c=this.correctCaretPosition(n,o);else if("Delete"!==f||l.test(n[o])||p.test(n[o])){if("Backspace"===f&&!l.test(n[o-1])&&!p.test(n[o-1]))for(;!l.test(n[c-1])&&c>u.length;)c--}else for(;!l.test(n[c])&&c<n.length-s.length;)c++;c!==o&&(e.preventDefault(),this.setPatchedCaretPosition(t,c,n))}this.props.onKeyDown(e)}},{key:"onMouseUp",value:function(e){var t=e.target,r=t.selectionStart,n=t.selectionEnd,o=t.value;if(r===n){var a=this.correctCaretPosition(o,r);a!==r&&this.setPatchedCaretPosition(t,a,o)}this.props.onMouseUp(e)}},{key:"render",value:function(){var e=c(this.props,x),t=h({},e,{type:this.props.type,value:this.state.value,onChange:this.onChange,onKeyDown:this.onKeyDown,onMouseUp:this.onMouseUp});if("text"===this.props.displayType)return y.default.createElement("span",e,this.state.value);if(this.props.customInput){var r=this.props.customInput;return y.default.createElement(r,t)}return y.default.createElement("input",t)}}]),t}(y.default.Component);S.propTypes=x,S.defaultProps=b,e.exports=S},function(e,t,r){e.exports=r(3)()},function(e,t,r){"use strict";var n=r(4),o=r(5),a=r(6);e.exports=function(){function e(e,t,r,n,i,u){u!==a&&o(!1,"Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types")}function t(){return e}e.isRequired=e;var r={array:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t};return r.checkPropTypes=n,r.PropTypes=r,r}},function(e,t){"use strict";function r(e){return function(){return e}}var n=function(){};n.thatReturns=r,n.thatReturnsFalse=r(!1),n.thatReturnsTrue=r(!0),n.thatReturnsNull=r(null),n.thatReturnsThis=function(){return this},n.thatReturnsArgument=function(e){return e},e.exports=n},function(e,t,r){"use strict";function n(e,t,r,n,a,i,u,s){if(o(t),!e){var f;if(void 0===t)f=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var l=[r,n,a,i,u,s],p=0;f=new Error(t.replace(/%s/g,function(){return l[p++]})),f.name="Invariant Violation"}throw f.framesToPop=1,f}}var o=function(e){};e.exports=n},function(e,t){"use strict";var r="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED";e.exports=r},function(t,r){t.exports=e}])});
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react")):"function"==typeof define&&define.amd?define(["react"],t):"object"==typeof exports?exports.NumberFormat=t(require("react")):e.NumberFormat=t(e.React)}(this,function(e){return function(e){function t(n){if(r[n])return r[n].exports;var o=r[n]={exports:{},id:n,loaded:!1};return e[n].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var r={};return t.m=e,t.c=r,t.p="",t(0)}([function(e,t,r){e.exports=r(1)},function(e,t,r){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e},s=function(){function e(e,t){for(var r=0;r<t.length;r++){var n=t[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}return function(t,r,n){return r&&e(t.prototype,r),n&&e(t,n),t}}(),f=r(2),l=n(f),c=r(7),p=n(c),h=r(8),d={thousandSeparator:l.default.oneOfType([l.default.string,l.default.oneOf([!0])]),decimalSeparator:l.default.string,decimalPrecision:l.default.number,displayType:l.default.oneOf(["input","text"]),prefix:l.default.string,suffix:l.default.string,format:l.default.oneOfType([l.default.string,l.default.func]),removeFormatting:l.default.func,mask:l.default.oneOfType([l.default.string,l.default.arrayOf(l.default.string)]),value:l.default.oneOfType([l.default.number,l.default.string]),isNumericString:l.default.bool,customInput:l.default.func,allowNegative:l.default.bool,onKeyDown:l.default.func,onMouseUp:l.default.func,onChange:l.default.func,onFocus:l.default.func,type:l.default.oneOf(["text","tel"]),isAllowed:l.default.func,renderText:l.default.func},g={displayType:"input",decimalSeparator:".",prefix:"",suffix:"",allowNegative:!0,isNumericString:!1,type:"text",onChange:h.noop,onKeyDown:h.noop,onMouseUp:h.noop,onFocus:h.noop,isAllowed:h.returnTrue},m=function(e){function t(e){o(this,t);var r=a(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e));r.validateProps();var n=r.formatValueProp();return r.state={value:n,numAsString:r.removeFormatting(n)},r.onChange=r.onChange.bind(r),r.onKeyDown=r.onKeyDown.bind(r),r.onMouseUp=r.onMouseUp.bind(r),r.onFocus=r.onFocus.bind(r),r}return i(t,e),s(t,[{key:"componentDidUpdate",value:function(e){this.updateValueIfRequired(e)}},{key:"updateValueIfRequired",value:function(e){var t=this.props,r=this.state;if(e!==t){this.validateProps();var n=r.value,o=r.numAsString||"",a=void 0===t.value?this.formatNumString(o).formattedValue:this.formatValueProp();a!==n&&this.setState({value:a,numAsString:this.removeFormatting(a)})}}},{key:"getFloatString",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=this.getSeparators(),r=t.decimalSeparator,n=this.getNumberRegex(!0),o="-"===e[0];o&&(e=e.replace("-","")),e=(e.match(n)||[]).join("").replace(r,".");var a=e.indexOf(".");return a!==-1&&(e=e.substring(0,a)+"."+e.substring(a+1,e.length).replace(new RegExp((0,h.escapeRegExp)(r),"g"),"")),o&&(e="-"+e),e}},{key:"getNumberRegex",value:function(e,t){var r=this.props,n=r.format,o=r.decimalPrecision,a=this.getSeparators(),i=a.decimalSeparator;return new RegExp("\\d"+(!i||0===o||t||n?"":"|"+(0,h.escapeRegExp)(i)),e?"g":void 0)}},{key:"getSeparators",value:function(){var e=this.props.decimalSeparator,t=this.props.thousandSeparator;return t===!0&&(t=","),{decimalSeparator:e,thousandSeparator:t}}},{key:"getMaskAtIndex",value:function(e){var t=this.props.mask,r=void 0===t?" ":t;return"string"==typeof r?r:r[e]||" "}},{key:"validateProps",value:function(){var e=this.props.mask,t=this.getSeparators(),r=t.decimalSeparator,n=t.thousandSeparator;if(r===n)throw new Error("\n Decimal separator can't be same as thousand separator.\n\n thousandSeparator: "+n+' (thousandSeparator = {true} is same as thousandSeparator = ",")\n decimalSeparator: '+r+" (default value for decimalSeparator is .)\n ");if(e){var o="string"===e?e:e.toString();if(o.match(/\d/g))throw new Error("\n Mask "+e+" should not contain numeric character;\n ")}}},{key:"setPatchedCaretPosition",value:function(e,t,r){(0,h.setCaretPosition)(e,t),setTimeout(function(){e.value===r&&(0,h.setCaretPosition)(e,t)},0)}},{key:"correctCaretPosition",value:function(e,t,r){var n=this.props,o=n.prefix,a=n.suffix,i=n.format;if(!i){var u="-"===e[0];return Math.min(Math.max(t,o.length+(u?1:0)),e.length-a.length)}if("function"==typeof i)return t;if("#"===i[t]&&(0,h.charIsNumber)(e[t]))return t;if("#"===i[t-1]&&(0,h.charIsNumber)(e[t-1]))return t;var s=i.indexOf("#"),f=i.lastIndexOf("#");t=Math.min(Math.max(t,s),f+1);for(var l=i.substring(t,i.length).indexOf("#"),c=t,p=t+(l===-1?0:l);c>s&&("#"!==i[c]||!(0,h.charIsNumber)(e[c]));)c-=1;var d=!(0,h.charIsNumber)(e[p])||"left"===r&&t!==s||t-c<p-t;return d?c+1:p}},{key:"getCaretPosition",value:function(e,t,r){var n=this.props.format,o=this.state.value,a=this.getNumberRegex(!0),i=(e.match(a)||[]).join(""),u=(t.match(a)||[]).join(""),s=void 0,f=void 0;for(s=0,f=0;f<r;f++){var l=e[f],c=t[s]||"";if((l.match(a)||l===c)&&("0"!==l||!c.match(a)||"0"===c||i.length===u.length)){for(;l!==t[s]&&s<t.length;)s++;s++}}return"string"!=typeof n||o||(s=t.length),s=this.correctCaretPosition(t,s)}},{key:"removePrefixAndSuffix",value:function(e){var t=this.props,r=t.format,n=t.prefix,o=t.suffix;if(!r&&e){var a="-"===e[0];a&&(e=e.substring(1,e.length)),e=n&&0===e.indexOf(n)?e.substring(n.length,e.length):e;var i=e.lastIndexOf(o);e=o&&i!==-1&&i===e.length-o.length?e.substring(0,i):e,a&&(e="-"+e)}return e}},{key:"removePatternFormatting",value:function(e){for(var t=this.props.format,r=t.split("#").filter(function(e){return""!==e}),n=0,o="",a=0,i=r.length;a<=i;a++){var u=r[a]||"",s=a===i?e.length:e.indexOf(u,n);if(s===-1){o=e;break}o+=e.substring(n,s),n=s+u.length}return(o.match(/\d/g)||[]).join("")}},{key:"removeFormatting",value:function e(t){var r=this.props,n=r.format,e=r.removeFormatting;return t?(n?t="string"==typeof n?this.removePatternFormatting(t):"function"==typeof e?e(t):(t.match(/\d/g)||[]).join(""):(t=this.removePrefixAndSuffix(t),t=this.getFloatString(t)),t):t}},{key:"formatWithPattern",value:function(e){for(var t=this.props.format,r=0,n=t.split(""),o=0,a=t.length;o<a;o++)"#"===t[o]&&(n[o]=e[r]||this.getMaskAtIndex(r),r+=1);return n.join("")}},{key:"formatAsNumber",value:function(e){var t=this.props,r=t.decimalPrecision,n=t.allowNegative,o=t.prefix,a=t.suffix,i=this.getSeparators(),u=i.thousandSeparator,s=i.decimalSeparator,f="-"===e[0];e=e.replace("-","");var l=e.indexOf(".")!==-1||r,c=e.split("."),p=c[0],d=c[1]||"";return p=(0,h.removeLeadingZero)(p),void 0!==r&&(d=(0,h.limitToPrecision)(d,r)),u&&(p=p.replace(/(\d)(?=(\d{3})+(?!\d))/g,"$1"+u)),o&&(p=o+p),a&&(d+=a),f&&n&&(p="-"+p),e=p+(l&&s||"")+d}},{key:"formatNumString",value:function(e){var t=this.props.format,r=e;return""===e?r="":"-"!==e||t?r="string"==typeof t?this.formatWithPattern(r):"function"==typeof t?t(r):this.formatAsNumber(r):(r="-",e=""),{value:e,formattedValue:r}}},{key:"formatValueProp",value:function(){var e=this.props,t=e.format,r=e.decimalPrecision,n=this.props,o=n.value,a=n.isNumericString;if(void 0===o)return"";"number"==typeof o&&(o=o.toString(),a=!0),a&&!t&&"number"==typeof r&&(o=(0,h.roundToPrecision)(o,r));var i=a?this.formatNumString(o):this.formatInput(o);return i.formattedValue}},{key:"formatNegation",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=this.props.allowNegative,r=new RegExp("(-)"),n=new RegExp("(-)(.)*(-)"),o=r.test(e),a=n.test(e);return e=e.replace(/-/g,""),o&&!a&&t&&(e="-"+e),e}},{key:"formatInput",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=this.props.format;return t||(e=this.formatNegation(e)),e=this.removeFormatting(e),this.formatNumString(e)}},{key:"isCharacterAFormat",value:function(e,t){var r=this.props,n=r.format,o=r.prefix,a=r.suffix;return"string"==typeof n&&"#"!==n[e]||!n&&(e<o.length||e>=t.length-a.length)}},{key:"checkIfFormatGotDeleted",value:function(e,t,r){for(var n=e;n<t;n++)if(this.isCharacterAFormat(n,r))return!0;return!1}},{key:"correctInputValue",value:function(e,t,r){if(r.length>=t.length||!r.length)return r;var n=e,o=(0,h.splitString)(t,e),a=(0,h.splitString)(r,e),i=o[1].lastIndexOf(a[1]),u=i!==-1?o[1].substring(0,i):"",s=n+u.length;return this.checkIfFormatGotDeleted(n,s,t)&&(r=t),r}},{key:"onChange",value:function(e){e.persist();var t=e.target,r=t.value,n=this.state,o=this.props,a=o.isAllowed,i=n.value||"",u=Math.max(t.selectionStart,t.selectionEnd);r=this.correctInputValue(u,i,r);var s=this.formatInput(r),f=s.formattedValue,l=void 0===f?"":f,c=s.value,p={formattedValue:l,value:c,floatValue:parseFloat(c)};a(p)||(l=i),t.value=l;var h=this.getCaretPosition(r,l,u);return this.setPatchedCaretPosition(t,h,l),l!==i&&this.setState({value:l,numAsString:this.removeFormatting(l)},function(){o.onChange(e,p)}),c}},{key:"onKeyDown",value:function e(t){var r=t.target,n=t.key,o=r.selectionEnd,a=r.value,i=r.selectionStart,u=void 0,s=this.props,f=s.decimalPrecision,l=s.prefix,c=s.suffix,p=s.format,e=s.onKeyDown,h=this.getNumberRegex(!1,void 0!==f),d=new RegExp("-"),g="string"==typeof p;if("ArrowLeft"===n||"Backspace"===n?u=i-1:"ArrowRight"===n?u=i+1:"Delete"===n&&(u=i),void 0===u||i!==o)return void e(t);var m=u,v=g?p.indexOf("#"):l.length,y=g?p.lastIndexOf("#")+1:a.length-c.length;if("ArrowLeft"===n||"ArrowRight"===n){var x="ArrowLeft"===n?"left":"right";m=this.correctCaretPosition(a,u,x)}else if("Delete"!==n||h.test(a[u])||d.test(a[u])){if("Backspace"===n&&!h.test(a[u])&&!d.test(a[u])){for(;!h.test(a[m-1])&&m>v;)m--;m=this.correctCaretPosition(a,m,"left")}}else for(;!h.test(a[m])&&m<y;)m++;(m!==u||u<v||u>y)&&(t.preventDefault(),this.setPatchedCaretPosition(r,m,a)),t.isUnitTestRun&&this.setPatchedCaretPosition(r,m,a),this.props.onKeyDown(t)}},{key:"onMouseUp",value:function(e){var t=e.target,r=t.selectionStart,n=t.selectionEnd,o=t.value;if(r===n){var a=this.correctCaretPosition(o,r);a!==r&&this.setPatchedCaretPosition(t,a,o)}this.props.onMouseUp(e)}},{key:"onFocus",value:function(e){var t=e.target,r=t.selectionStart,n=t.value,o=this.correctCaretPosition(n,r);o!==r&&this.setPatchedCaretPosition(t,o,n),this.props.onFocus(e)}},{key:"render",value:function(){var e=this.props,t=e.type,r=e.displayType,n=e.customInput,o=e.renderText,a=this.state.value,i=(0,h.omit)(this.props,d),s=u({},i,{type:t,value:a,onChange:this.onChange,onKeyDown:this.onKeyDown,onMouseUp:this.onMouseUp,onFocus:this.onFocus});if("text"===r)return o?o(a)||null:p.default.createElement("span",i,a);if(n){var f=n;return p.default.createElement(f,s)}return p.default.createElement("input",s)}}]),t}(p.default.Component);m.propTypes=d,m.defaultProps=g,e.exports=m},function(e,t,r){e.exports=r(3)()},function(e,t,r){"use strict";var n=r(4),o=r(5),a=r(6);e.exports=function(){function e(e,t,r,n,i,u){u!==a&&o(!1,"Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types")}function t(){return e}e.isRequired=e;var r={array:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t};return r.checkPropTypes=n,r.PropTypes=r,r}},function(e,t){"use strict";function r(e){return function(){return e}}var n=function(){};n.thatReturns=r,n.thatReturnsFalse=r(!1),n.thatReturnsTrue=r(!0),n.thatReturnsNull=r(null),n.thatReturnsThis=function(){return this},n.thatReturnsArgument=function(e){return e},e.exports=n},function(e,t,r){"use strict";function n(e,t,r,n,a,i,u,s){if(o(t),!e){var f;if(void 0===t)f=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var l=[r,n,a,i,u,s],c=0;f=new Error(t.replace(/%s/g,function(){return l[c++]})),f.name="Invariant Violation"}throw f.framesToPop=1,f}}var o=function(e){};e.exports=n},function(e,t){"use strict";var r="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED";e.exports=r},function(t,r){t.exports=e},function(e,t){"use strict";function r(){}function n(){return!0}function o(e){return!!(e||"").match(/\d/)}function a(e){return e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}function i(e){return e.replace(/^0+/,"")||"0"}function u(e,t){return[e.substring(0,t),e.substring(t)]}function s(e,t){for(var r="",n=0;n<=t-1;n++)r+=e[n]||"0";return r}function f(e,t){var r=e.split("."),n=parseFloat("0."+(r[1]||"0")).toFixed(t).split("."),o=r[0].split("").reverse().reduce(function(e,t,r){return e.length>r?(Number(e[0])+Number(t)).toString()+e.substring(1,e.length):t+e},n[0]),a=n[1];return o+(a?"."+a:"")}function l(e,t){var r={};return Object.keys(e).forEach(function(n){t[n]||(r[n]=e[n])}),r}function c(e,t){if(e.value=e.value,null!==e){if(e.createTextRange){var r=e.createTextRange();return r.move("character",t),r.select(),!0}return e.selectionStart||0===e.selectionStart?(e.focus(),e.setSelectionRange(t,t),!0):(e.focus(),!1)}}Object.defineProperty(t,"__esModule",{value:!0}),t.noop=r,t.returnTrue=n,t.charIsNumber=o,t.escapeRegExp=a,t.removeLeadingZero=i,t.splitString=u,t.limitToPrecision=s,t.roundToPrecision=f,t.omit=l,t.setCaretPosition=c}])});

@@ -35,2 +35,9 @@ import React from 'react';

<h3>
Custom renderText method
</h3>
<NumberFormat value={4111111111111111} displayType="text" format="#### #### #### ####" renderText={value => <i>{value}</i>}/>
</div>
<div className="example">
<h3>
Prefix and thousand separator : Format currency in input

@@ -41,3 +48,2 @@ </h3>

decimalSeparator=","
isAllowed={(values) => values.floatValue > 5}
value={this.state.test}

@@ -100,3 +106,3 @@ prefix="$"

</h3>
<NumberFormat format="#### #### #### ####" />
<NumberFormat format="#### #### #### ####" mask="_"/>
</div>

@@ -106,5 +112,12 @@

<h3>
Format with mask as array
</h3>
<NumberFormat format="##/##" placeholder="MM/YY" mask={['M', 'M', 'Y', 'Y']}/>
</div>
<div className="example">
<h3>
Format with mask : Format credit card in an input
</h3>
<NumberFormat format="#### #### #### ####" mask="_" />
<NumberFormat format="#### #### #### ####" mask="_"/>
</div>

@@ -121,2 +134,9 @@

<h3>
Format phone number
</h3>
<NumberFormat format="+1 (###) ###-####" mask="_"/>
</div>
<div className="example">
<h3>
Custom input : Format credit card number

@@ -123,0 +143,0 @@ </h3>

@@ -22,2 +22,4 @@ var webpack = require('webpack');

},
externals: {
},
watch: true

@@ -24,0 +26,0 @@ },

@@ -15,2 +15,4 @@ 'use strict';

var _utils = require('./utils');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

@@ -24,52 +26,2 @@

function noop() {}
function escapeRegExp(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}
function removeLeadingZero(numStr) {
//remove leading zeros
return numStr.replace(/^0+/, '') || '0';
}
/**
* limit decimal numbers to given precision
* Not used .fixedTo because that will break with big numbers
*/
function limitToPrecision(numStr, precision) {
var str = '';
for (var i = 0; i <= precision - 1; i++) {
str += numStr[i] || '0';
}
return str;
}
/**
* This method is required to round prop value to given precision.
* Not used .round or .fixedTo because that will break with big numbers
*/
function roundToPrecision(numStr, precision) {
var numberParts = numStr.split('.');
var roundedDecimalParts = parseFloat('0.' + (numberParts[1] || '0')).toFixed(precision).split('.');
var intPart = numberParts[0].split('').reverse().reduce(function (roundedStr, current, idx) {
if (roundedStr.length > idx) {
return (Number(roundedStr[0]) + Number(current)).toString() + roundedStr.substring(1, roundedStr.length);
}
return current + roundedStr;
}, roundedDecimalParts[0]);
var decimalPart = roundedDecimalParts[1];
return intPart + (decimalPart ? '.' + decimalPart : '');
}
function omit(obj, keyMaps) {
var filteredObj = {};
Object.keys(obj).forEach(function (key) {
if (!keyMaps[key]) filteredObj[key] = obj[key];
});
return filteredObj;
}
var propTypes = {

@@ -83,4 +35,6 @@ thousandSeparator: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.oneOf([true])]),

format: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.func]),
mask: _propTypes2.default.string,
removeFormatting: _propTypes2.default.func,
mask: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.arrayOf(_propTypes2.default.string)]),
value: _propTypes2.default.oneOfType([_propTypes2.default.number, _propTypes2.default.string]),
isNumericString: _propTypes2.default.bool,
customInput: _propTypes2.default.func,

@@ -91,4 +45,6 @@ allowNegative: _propTypes2.default.bool,

onChange: _propTypes2.default.func,
onFocus: _propTypes2.default.func,
type: _propTypes2.default.oneOf(['text', 'tel']),
isAllowed: _propTypes2.default.func
isAllowed: _propTypes2.default.func,
renderText: _propTypes2.default.func
};

@@ -102,9 +58,9 @@

allowNegative: true,
isNumericString: false,
type: 'text',
onChange: noop,
onKeyDown: noop,
onMouseUp: noop,
isAllowed: function isAllowed() {
return true;
}
onChange: _utils.noop,
onKeyDown: _utils.noop,
onMouseUp: _utils.noop,
onFocus: _utils.noop,
isAllowed: _utils.returnTrue
};

@@ -118,11 +74,18 @@

//validate props
var _this = _possibleConstructorReturn(this, (NumberFormat.__proto__ || Object.getPrototypeOf(NumberFormat)).call(this, props));
var value = _this.optimizeValueProp(props);
_this.validateProps();
var formattedValue = _this.formatValueProp();
_this.state = {
value: _this.formatInput(value).formattedValue
value: formattedValue,
numAsString: _this.removeFormatting(formattedValue)
};
_this.onChange = _this.onChange.bind(_this);
_this.onKeyDown = _this.onKeyDown.bind(_this);
_this.onMouseUp = _this.onMouseUp.bind(_this);
_this.onFocus = _this.onFocus.bind(_this);
return _this;

@@ -133,4 +96,4 @@ }

key: 'componentDidUpdate',
value: function componentDidUpdate(prevProps, prevState) {
this.updateValueIfRequired(prevProps, prevState);
value: function componentDidUpdate(prevProps) {
this.updateValueIfRequired(prevProps);
}

@@ -145,13 +108,15 @@ }, {

if (prevProps !== props) {
//validate props
this.validateProps();
var stateValue = state.value;
var value = this.optimizeValueProp(props);
if (value === undefined) value = stateValue;
var lastNumStr = state.numAsString || '';
var _formatInput = this.formatInput(value),
formattedValue = _formatInput.formattedValue;
var formattedValue = props.value === undefined ? this.formatNumString(lastNumStr).formattedValue : this.formatValueProp();
if (formattedValue !== stateValue) {
this.setState({
value: formattedValue
value: formattedValue,
numAsString: this.removeFormatting(formattedValue)
});

@@ -161,62 +126,216 @@ }

}
/** Misc methods **/
}, {
key: 'getFloatString',
value: function getFloatString(num, props) {
props = props || this.props;
value: function getFloatString() {
var num = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
var _getSeparators = this.getSeparators(props),
decimalSeparator = _getSeparators.decimalSeparator,
thousandSeparator = _getSeparators.thousandSeparator;
var _getSeparators = this.getSeparators(),
decimalSeparator = _getSeparators.decimalSeparator;
return (num || '').replace(new RegExp(escapeRegExp(thousandSeparator || ''), 'g'), '').replace(decimalSeparator, '.');
var numRegex = this.getNumberRegex(true);
//remove negation for regex check
var hasNegation = num[0] === '-';
if (hasNegation) num = num.replace('-', '');
num = (num.match(numRegex) || []).join('').replace(decimalSeparator, '.');
//remove extra decimals
var firstDecimalIndex = num.indexOf('.');
if (firstDecimalIndex !== -1) {
num = num.substring(0, firstDecimalIndex) + '.' + num.substring(firstDecimalIndex + 1, num.length).replace(new RegExp((0, _utils.escapeRegExp)(decimalSeparator), 'g'), '');
}
//add negation back
if (hasNegation) num = '-' + num;
return num;
}
//returned regex assumes decimalSeparator is as per prop
}, {
key: 'getFloatValue',
value: function getFloatValue(num, props) {
props = props || this.props;
return parseFloat(this.getFloatString(num, props)) || 0;
key: 'getNumberRegex',
value: function getNumberRegex(g, ignoreDecimalSeparator) {
var _props = this.props,
format = _props.format,
decimalPrecision = _props.decimalPrecision;
var _getSeparators2 = this.getSeparators(),
decimalSeparator = _getSeparators2.decimalSeparator;
return new RegExp('\\d' + (decimalSeparator && decimalPrecision !== 0 && !ignoreDecimalSeparator && !format ? '|' + (0, _utils.escapeRegExp)(decimalSeparator) : ''), g ? 'g' : undefined);
}
}, {
key: 'optimizeValueProp',
value: function optimizeValueProp(props) {
var _getSeparators2 = this.getSeparators(props),
decimalSeparator = _getSeparators2.decimalSeparator;
key: 'getSeparators',
value: function getSeparators() {
var decimalSeparator = this.props.decimalSeparator;
var thousandSeparator = this.props.thousandSeparator;
var decimalPrecision = props.decimalPrecision,
format = props.format;
var value = props.value;
if (thousandSeparator === true) {
thousandSeparator = ',';
}
if (format || !(value || value === 0)) return value;
return {
decimalSeparator: decimalSeparator,
thousandSeparator: thousandSeparator
};
}
}, {
key: 'getMaskAtIndex',
value: function getMaskAtIndex(index) {
var _props$mask = this.props.mask,
mask = _props$mask === undefined ? ' ' : _props$mask;
var isNumber = typeof value === 'number';
if (typeof mask === 'string') {
return mask;
}
if (isNumber) value = value.toString();
return mask[index] || ' ';
}
}, {
key: 'validateProps',
value: function validateProps() {
var mask = this.props.mask;
value = this.removePrefixAndSuffix(isNumber ? value : this.getFloatString(value, props), props);
//validate decimalSeparator and thousandSeparator
//round off value
if (typeof decimalPrecision === 'number') value = roundToPrecision(value, decimalPrecision);
var _getSeparators3 = this.getSeparators(),
decimalSeparator = _getSeparators3.decimalSeparator,
thousandSeparator = _getSeparators3.thousandSeparator;
//correct decimal separator
if (decimalSeparator) {
value = value.replace('.', decimalSeparator);
if (decimalSeparator === thousandSeparator) {
throw new Error('\n Decimal separator can\'t be same as thousand separator.\n\n thousandSeparator: ' + thousandSeparator + ' (thousandSeparator = {true} is same as thousandSeparator = ",")\n decimalSeparator: ' + decimalSeparator + ' (default value for decimalSeparator is .)\n ');
}
//throw error if value has two decimal seperators
if (value.split(decimalSeparator).length > 2) {
throw new Error('\n Wrong input for value props.\n\n More than one decimalSeparator found\n ');
//validate mask
if (mask) {
var maskAsStr = mask === 'string' ? mask : mask.toString();
if (maskAsStr.match(/\d/g)) {
throw new Error('\n Mask ' + mask + ' should not contain numeric character;\n ');
}
}
}
//if decimalPrecision is 0 remove decimalNumbers
if (decimalPrecision === 0) return value.split(decimalSeparator)[0];
/** Misc methods end **/
return value;
/** caret specific methods **/
}, {
key: 'setPatchedCaretPosition',
value: function setPatchedCaretPosition(el, caretPos, currentValue) {
/* setting caret position within timeout of 0ms is required for mobile chrome,
otherwise browser resets the caret position after we set it
We are also setting it without timeout so that in normal browser we don't see the flickering */
(0, _utils.setCaretPosition)(el, caretPos);
setTimeout(function () {
if (el.value === currentValue) (0, _utils.setCaretPosition)(el, caretPos);
}, 0);
}
/* This keeps the caret within typing area so people can't type in between prefix or suffix */
}, {
key: 'correctCaretPosition',
value: function correctCaretPosition(value, caretPos, direction) {
var _props2 = this.props,
prefix = _props2.prefix,
suffix = _props2.suffix,
format = _props2.format;
//in case of format as number limit between prefix and suffix
if (!format) {
var hasNegation = value[0] === '-';
return Math.min(Math.max(caretPos, prefix.length + (hasNegation ? 1 : 0)), value.length - suffix.length);
}
//in case if custom format method don't do anything
if (typeof format === 'function') return caretPos;
/* in case format is string find the closest # position from the caret position */
//in case the caretPos have input value on it don't do anything
if (format[caretPos] === '#' && (0, _utils.charIsNumber)(value[caretPos])) return caretPos;
//if caretPos is just after input value don't do anything
if (format[caretPos - 1] === '#' && (0, _utils.charIsNumber)(value[caretPos - 1])) return caretPos;
//find the nearest caret position
var firstHashPosition = format.indexOf('#');
var lastHashPosition = format.lastIndexOf('#');
//limit the cursor between the first # position and the last # position
caretPos = Math.min(Math.max(caretPos, firstHashPosition), lastHashPosition + 1);
var nextPos = format.substring(caretPos, format.length).indexOf('#');
var caretLeftBound = caretPos;
var caretRightBoud = caretPos + (nextPos === -1 ? 0 : nextPos);
//get the position where the last number is present
while (caretLeftBound > firstHashPosition && (format[caretLeftBound] !== '#' || !(0, _utils.charIsNumber)(value[caretLeftBound]))) {
caretLeftBound -= 1;
}
var goToLeft = !(0, _utils.charIsNumber)(value[caretRightBoud]) || direction === 'left' && caretPos !== firstHashPosition || caretPos - caretLeftBound < caretRightBoud - caretPos;
return goToLeft ? caretLeftBound + 1 : caretRightBoud;
}
}, {
key: 'getCaretPosition',
value: function getCaretPosition(inputValue, formattedValue, caretPos) {
var format = this.props.format;
var stateValue = this.state.value;
var numRegex = this.getNumberRegex(true);
var inputNumber = (inputValue.match(numRegex) || []).join('');
var formattedNumber = (formattedValue.match(numRegex) || []).join('');
var j = void 0,
i = void 0;
j = 0;
for (i = 0; i < caretPos; i++) {
var currentInputChar = inputValue[i];
var currentFormatChar = formattedValue[j] || '';
//no need to increase new cursor position if formatted value does not have those characters
//case inputValue = 1a23 and formattedValue = 123
if (!currentInputChar.match(numRegex) && currentInputChar !== currentFormatChar) continue;
//When we are striping out leading zeros maintain the new cursor position
//Case inputValue = 00023 and formattedValue = 23;
if (currentInputChar === '0' && currentFormatChar.match(numRegex) && currentFormatChar !== '0' && inputNumber.length !== formattedNumber.length) continue;
//we are not using currentFormatChar because j can change here
while (currentInputChar !== formattedValue[j] && j < formattedValue.length) {
j++;
}j++;
}
if (typeof format === 'string' && !stateValue) {
//set it to the maximum value so it goes after the last number
j = formattedValue.length;
}
//correct caret position if its outside of editable area
j = this.correctCaretPosition(formattedValue, j);
return j;
}
/** caret specific methods ends **/
/** methods to remove formattting **/
}, {
key: 'removePrefixAndSuffix',
value: function removePrefixAndSuffix(val, props) {
var format = props.format,
prefix = props.prefix,
suffix = props.suffix;
value: function removePrefixAndSuffix(val) {
var _props3 = this.props,
format = _props3.format,
prefix = _props3.prefix,
suffix = _props3.suffix;

@@ -245,237 +364,281 @@ //remove prefix and suffix

}, {
key: 'getSeparators',
value: function getSeparators(props) {
props = props || this.props;
key: 'removePatternFormatting',
value: function removePatternFormatting(val) {
var format = this.props.format;
var _props = props,
decimalSeparator = _props.decimalSeparator;
var _props2 = props,
thousandSeparator = _props2.thousandSeparator;
var formatArray = format.split('#').filter(function (str) {
return str !== '';
});
var start = 0;
var numStr = '';
for (var i = 0, ln = formatArray.length; i <= ln; i++) {
var part = formatArray[i] || '';
if (thousandSeparator === true) {
thousandSeparator = ',';
}
//if i is the last fragment take the index of end of the value
//For case like +1 (911) 911 91 91 having pattern +1 (###) ### ## ##
var index = i === ln ? val.length : val.indexOf(part, start);
if (decimalSeparator === thousandSeparator) {
throw new Error('\n Decimal separator can\'t be same as thousand separator.\n\n thousandSeparator: ' + thousandSeparator + ' (thousandSeparator = {true} is same as thousandSeparator = ",")\n decimalSeparator: ' + decimalSeparator + ' (default value for decimalSeparator is .)\n ');
/* in any case if we don't find the pattern part in the value assume the val as numeric string
This will be also in case if user has started typing, in any other case it will not be -1
unless wrong prop value is provided */
if (index === -1) {
numStr = val;
break;
} else {
numStr += val.substring(start, index);
start = index + part.length;
}
}
return {
decimalSeparator: decimalSeparator,
thousandSeparator: thousandSeparator
};
return (numStr.match(/\d/g) || []).join('');
}
}, {
key: 'getNumberRegex',
value: function getNumberRegex(g, ignoreDecimalSeparator) {
var _props3 = this.props,
format = _props3.format,
decimalPrecision = _props3.decimalPrecision;
key: 'removeFormatting',
value: function removeFormatting(val) {
var _props4 = this.props,
format = _props4.format,
removeFormatting = _props4.removeFormatting;
var _getSeparators3 = this.getSeparators(),
decimalSeparator = _getSeparators3.decimalSeparator;
if (!val) return val;
return new RegExp('\\d' + (decimalSeparator && decimalPrecision !== 0 && !ignoreDecimalSeparator && !format ? '|' + escapeRegExp(decimalSeparator) : ''), g ? 'g' : undefined);
if (!format) {
val = this.removePrefixAndSuffix(val);
val = this.getFloatString(val);
} else if (typeof format === 'string') {
val = this.removePatternFormatting(val);
} else if (typeof removeFormatting === 'function') {
//condition need to be handled if format method is provide,
val = removeFormatting(val);
} else {
val = (val.match(/\d/g) || []).join('');
}
return val;
}
/** methods to remove formattting end **/
/*** format specific methods start ***/
/**
* Format when # based string is provided
* @param {string} numStr Numeric String
* @return {string} formatted Value
*/
}, {
key: 'setCaretPosition',
value: function setCaretPosition(el, caretPos) {
el.value = el.value;
// ^ this is used to not only get "focus", but
// to make sure we don't have it everything -selected-
// (it causes an issue in chrome, and having it doesn't hurt any other browser)
if (el !== null) {
if (el.createTextRange) {
var range = el.createTextRange();
range.move('character', caretPos);
range.select();
return true;
key: 'formatWithPattern',
value: function formatWithPattern(numStr) {
var format = this.props.format;
var hashCount = 0;
var formattedNumberAry = format.split('');
for (var i = 0, ln = format.length; i < ln; i++) {
if (format[i] === '#') {
formattedNumberAry[i] = numStr[hashCount] || this.getMaskAtIndex(hashCount);
hashCount += 1;
}
// (el.selectionStart === 0 added for Firefox bug)
if (el.selectionStart || el.selectionStart === 0) {
el.focus();
el.setSelectionRange(caretPos, caretPos);
return true;
}
// fail city, fortunately this never happens (as far as I've tested) :)
el.focus();
return false;
}
return formattedNumberAry.join('');
}
/**
* @param {string} numStr Numeric string/floatString] It always have decimalSeparator as .
* @return {string} formatted Value
*/
}, {
key: 'setPatchedCaretPosition',
value: function setPatchedCaretPosition(el, caretPos, currentValue) {
var _this2 = this;
key: 'formatAsNumber',
value: function formatAsNumber(numStr) {
var _props5 = this.props,
decimalPrecision = _props5.decimalPrecision,
allowNegative = _props5.allowNegative,
prefix = _props5.prefix,
suffix = _props5.suffix;
/*
setting caret position within timeout of 0ms is required for mobile chrome,
otherwise browser resets the caret position after we set it
We are also setting it without timeout so that in normal browser we don't see the flickering
*/
this.setCaretPosition(el, caretPos);
setTimeout(function () {
if (el.value === currentValue) _this2.setCaretPosition(el, caretPos);
}, 0);
}
var _getSeparators4 = this.getSeparators(),
thousandSeparator = _getSeparators4.thousandSeparator,
decimalSeparator = _getSeparators4.decimalSeparator;
/* This keeps the caret within typing area so people can't type in between prefix or suffix */
// Check if its negative number and remove negation for futher formatting
var hasNagation = numStr[0] === '-';
numStr = numStr.replace('-', '');
var hasDecimalSeparator = numStr.indexOf('.') !== -1 || decimalPrecision;
var parts = numStr.split('.');
var beforeDecimal = parts[0];
var afterDecimal = parts[1] || '';
//remove leading zeros from number before decimal
beforeDecimal = (0, _utils.removeLeadingZero)(beforeDecimal);
//apply decimal precision if its defined
if (decimalPrecision !== undefined) afterDecimal = (0, _utils.limitToPrecision)(afterDecimal, decimalPrecision);
if (thousandSeparator) {
beforeDecimal = beforeDecimal.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1' + thousandSeparator);
}
//add prefix and suffix
if (prefix) beforeDecimal = prefix + beforeDecimal;
if (suffix) afterDecimal = afterDecimal + suffix;
//restore negation sign
if (hasNagation && allowNegative) beforeDecimal = '-' + beforeDecimal;
numStr = beforeDecimal + (hasDecimalSeparator && decimalSeparator || '') + afterDecimal;
return numStr;
}
}, {
key: 'correctCaretPosition',
value: function correctCaretPosition(value, caretPos) {
var _props4 = this.props,
prefix = _props4.prefix,
suffix = _props4.suffix;
key: 'formatNumString',
value: function formatNumString(value) {
var format = this.props.format;
return Math.min(Math.max(caretPos, prefix.length), value.length - suffix.length);
var formattedValue = value;
if (value === '') {
formattedValue = '';
} else if (value === '-' && !format) {
formattedValue = '-';
value = '';
} else if (typeof format === 'string') {
formattedValue = this.formatWithPattern(formattedValue);
} else if (typeof format === 'function') {
formattedValue = format(formattedValue);
} else {
formattedValue = this.formatAsNumber(formattedValue);
}
return {
value: value,
formattedValue: formattedValue
};
}
}, {
key: 'formatWithPattern',
value: function formatWithPattern(str) {
var _props5 = this.props,
format = _props5.format,
mask = _props5.mask;
key: 'formatValueProp',
value: function formatValueProp() {
var _props6 = this.props,
format = _props6.format,
decimalPrecision = _props6.decimalPrecision;
var _props7 = this.props,
value = _props7.value,
isNumericString = _props7.isNumericString;
if (!format) return str;
var hashCount = format.split('#').length - 1;
var hashIdx = 0;
var frmtdStr = format;
// if value is not defined return empty string
for (var i = 0, ln = str.length; i < ln; i++) {
if (i < hashCount) {
hashIdx = frmtdStr.indexOf('#');
frmtdStr = frmtdStr.replace('#', str[i]);
}
if (value === undefined) return '';
if (typeof value === 'number') {
value = value.toString();
isNumericString = true;
}
var lastIdx = frmtdStr.lastIndexOf('#');
//round the number based on decimalPrecision
//format only if non formatted value is provided
if (isNumericString && !format && typeof decimalPrecision === 'number') {
value = (0, _utils.roundToPrecision)(value, decimalPrecision);
}
if (mask) {
return frmtdStr.replace(/#/g, mask);
}
return frmtdStr.substring(0, hashIdx + 1) + (lastIdx !== -1 ? frmtdStr.substring(lastIdx + 1, frmtdStr.length) : '');
var values = isNumericString ? this.formatNumString(value) : this.formatInput(value);
return values.formattedValue;
}
}, {
key: 'formatInput',
value: function formatInput(val) {
var props = this.props,
removePrefixAndSuffix = this.removePrefixAndSuffix;
var prefix = props.prefix,
suffix = props.suffix,
mask = props.mask,
format = props.format,
allowNegative = props.allowNegative,
decimalPrecision = props.decimalPrecision;
key: 'formatNegation',
value: function formatNegation() {
var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
var allowNegative = this.props.allowNegative;
var _getSeparators4 = this.getSeparators(),
thousandSeparator = _getSeparators4.thousandSeparator,
decimalSeparator = _getSeparators4.decimalSeparator;
var negationRegex = new RegExp('(-)');
var doubleNegationRegex = new RegExp('(-)(.)*(-)');
var maskPattern = format && typeof format == 'string' && !!mask;
var numRegex = this.getNumberRegex(true);
var hasNegative = void 0,
removeNegative = void 0;
// Check number has '-' value
var hasNegation = negationRegex.test(value);
//change val to string if its number
if (typeof val === 'number') val = val + '';
// Check number has 2 or more '-' values
var removeNegation = doubleNegationRegex.test(value);
var negativeRegex = new RegExp('(-)');
var doubleNegativeRegex = new RegExp('(-)(.)*(-)');
//remove negation
value = value.replace(/-/g, '');
//check if it has negative numbers
if (allowNegative && !format) {
// Check number has '-' value
hasNegative = negativeRegex.test(val);
// Check number has 2 or more '-' values
removeNegative = doubleNegativeRegex.test(val);
if (hasNegation && !removeNegation && allowNegative) {
value = '-' + value;
}
//remove prefix and suffix
val = removePrefixAndSuffix(val, props);
return value;
}
}, {
key: 'formatInput',
value: function formatInput() {
var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
var format = this.props.format;
var valMatch = val && val.match(numRegex);
//format negation only if we are formatting as number
if (!valMatch && removeNegative) {
return { value: '', formattedValue: '' };
} else if (!valMatch && hasNegative) {
return { value: '', formattedValue: '-' };
} else if (!valMatch) {
return { value: '', formattedValue: maskPattern ? '' : '' };
if (!format) {
value = this.formatNegation(value);
}
var num = val.match(numRegex).join('');
//remove formatting from number
value = this.removeFormatting(value);
var formattedValue = num;
return this.formatNumString(value);
}
if (format) {
if (typeof format == 'string') {
formattedValue = this.formatWithPattern(formattedValue);
} else if (typeof format == 'function') {
formattedValue = format(formattedValue);
}
} else {
var hasDecimalSeparator = formattedValue.indexOf(decimalSeparator) !== -1 || decimalPrecision;
/*** format specific methods end ***/
var parts = formattedValue.split(decimalSeparator);
var beforeDecimal = parts[0];
var afterDecimal = parts[1] || '';
}, {
key: 'isCharacterAFormat',
value: function isCharacterAFormat(caretPos, value) {
var _props8 = this.props,
format = _props8.format,
prefix = _props8.prefix,
suffix = _props8.suffix;
//remove leading zeros from number before decimal
beforeDecimal = removeLeadingZero(beforeDecimal);
//check within format pattern
//apply decimal precision if its defined
if (decimalPrecision !== undefined) afterDecimal = limitToPrecision(afterDecimal, decimalPrecision);
if (typeof format === 'string' && format[caretPos] !== '#') return true;
if (thousandSeparator) {
beforeDecimal = beforeDecimal.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1' + thousandSeparator);
}
//check in number format
if (!format && (caretPos < prefix.length || caretPos >= value.length - suffix.length)) return true;
return false;
}
}, {
key: 'checkIfFormatGotDeleted',
value: function checkIfFormatGotDeleted(start, end, value) {
//add prefix and suffix
if (prefix) beforeDecimal = prefix + beforeDecimal;
if (suffix) afterDecimal = afterDecimal + suffix;
for (var i = start; i < end; i++) {
if (this.isCharacterAFormat(i, value)) return true;
}
return false;
}
if (hasNegative && !removeNegative) beforeDecimal = '-' + beforeDecimal;
/**
* This will check if any formatting got removed by the delete or backspace and reset the value
* It will also work as fallback if android chome keyDown handler does not work
**/
formattedValue = beforeDecimal + (hasDecimalSeparator && decimalSeparator || '') + afterDecimal;
}
return {
value: (hasNegative && !removeNegative ? '-' : '') + removePrefixAndSuffix(formattedValue, props).match(numRegex).join(''),
formattedValue: formattedValue
};
}
}, {
key: 'getCaretPosition',
value: function getCaretPosition(inputValue, formattedValue, caretPos) {
var numRegex = this.getNumberRegex(true);
var inputNumber = (inputValue.match(numRegex) || []).join('');
var formattedNumber = (formattedValue.match(numRegex) || []).join('');
var j = void 0,
i = void 0;
key: 'correctInputValue',
value: function correctInputValue(caretPos, lastValue, value) {
j = 0;
//don't do anyhting if something got added, or if value is empty string (when whole input is cleared)
if (value.length >= lastValue.length || !value.length) {
return value;
}
for (i = 0; i < caretPos; i++) {
var currentInputChar = inputValue[i];
var currentFormatChar = formattedValue[j] || '';
//no need to increase new cursor position if formatted value does not have those characters
//case inputValue = 1a23 and formattedValue = 123
if (!currentInputChar.match(numRegex) && currentInputChar !== currentFormatChar) continue;
var start = caretPos;
var lastValueParts = (0, _utils.splitString)(lastValue, caretPos);
var newValueParts = (0, _utils.splitString)(value, caretPos);
var deletedIndex = lastValueParts[1].lastIndexOf(newValueParts[1]);
var diff = deletedIndex !== -1 ? lastValueParts[1].substring(0, deletedIndex) : '';
var end = start + diff.length;
//When we are striping out leading zeros maintain the new cursor position
//Case inputValue = 00023 and formattedValue = 23;
if (currentInputChar === '0' && currentFormatChar.match(numRegex) && currentFormatChar !== '0' && inputNumber.length !== formattedNumber.length) continue;
//we are not using currentFormatChar because j can change here
while (currentInputChar !== formattedValue[j] && !(formattedValue[j] || '').match(numRegex) && j < formattedValue.length) {
j++;
}j++;
//if format got deleted reset the value to last value
if (this.checkIfFormatGotDeleted(start, end, lastValue)) {
value = lastValue;
}
//correct caret position if its outsize of editable area
j = this.correctCaretPosition(formattedValue, j);
return j;
return value;
}

@@ -492,12 +655,13 @@ }, {

var lastValue = state.value;
var lastValue = state.value || '';
var _formatInput2 = this.formatInput(inputValue),
formattedValue = _formatInput2.formattedValue,
value = _formatInput2.value; // eslint-disable-line prefer-const
/*Max of selectionStart and selectionEnd is taken for the patch of pixel and other mobile device caret bug*/
var currentCaretPosition = Math.max(el.selectionStart, el.selectionEnd);
inputValue = this.correctInputValue(currentCaretPosition, lastValue, inputValue);
var currentCaretPosition = Math.max(el.selectionStart, el.selectionEnd);
var _formatInput = this.formatInput(inputValue),
_formatInput$formatte = _formatInput.formattedValue,
formattedValue = _formatInput$formatte === undefined ? '' : _formatInput$formatte,
value = _formatInput.value; // eslint-disable-line prefer-const

@@ -507,3 +671,3 @@ var valueObj = {

value: value,
floatValue: this.getFloatValue(value)
floatValue: parseFloat(value)
};

@@ -526,3 +690,3 @@

if (formattedValue !== lastValue) {
this.setState({ value: formattedValue }, function () {
this.setState({ value: formattedValue, numAsString: this.removeFormatting(formattedValue) }, function () {
props.onChange(e, valueObj);

@@ -538,39 +702,69 @@ });

var el = e.target;
var key = e.key;
var selectionEnd = el.selectionEnd,
value = el.value;
var selectionStart = el.selectionStart;
var _props6 = this.props,
decimalPrecision = _props6.decimalPrecision,
prefix = _props6.prefix,
suffix = _props6.suffix;
var key = e.key;
var expectedCaretPosition = void 0;
var _props9 = this.props,
decimalPrecision = _props9.decimalPrecision,
prefix = _props9.prefix,
suffix = _props9.suffix,
format = _props9.format,
onKeyDown = _props9.onKeyDown;
var numRegex = this.getNumberRegex(false, decimalPrecision !== undefined);
var negativeRegex = new RegExp('-');
var isPatternFormat = typeof format === 'string';
//Handle backspace and delete against non numerical/decimal characters
if (selectionStart === selectionEnd) {
var newCaretPosition = selectionStart;
//Handle backspace and delete against non numerical/decimal characters or arrow keys
if (key === 'ArrowLeft' || key === 'Backspace') {
expectedCaretPosition = selectionStart - 1;
} else if (key === 'ArrowRight') {
expectedCaretPosition = selectionStart + 1;
} else if (key === 'Delete') {
expectedCaretPosition = selectionStart;
}
if (key === 'ArrowLeft' || key === 'ArrowRight') {
selectionStart += key === 'ArrowLeft' ? -1 : +1;
newCaretPosition = this.correctCaretPosition(value, selectionStart);
} else if (key === 'Delete' && !numRegex.test(value[selectionStart]) && !negativeRegex.test(value[selectionStart])) {
while (!numRegex.test(value[newCaretPosition]) && newCaretPosition < value.length - suffix.length) {
newCaretPosition++;
}
} else if (key === 'Backspace' && !numRegex.test(value[selectionStart - 1]) && !negativeRegex.test(value[selectionStart - 1])) {
while (!numRegex.test(value[newCaretPosition - 1]) && newCaretPosition > prefix.length) {
newCaretPosition--;
}
}
//if expectedCaretPosition is not set it means we don't want to Handle keyDown
//also if multiple characters are selected don't handle
if (expectedCaretPosition === undefined || selectionStart !== selectionEnd) {
onKeyDown(e);
return;
}
if (newCaretPosition !== selectionStart) {
e.preventDefault();
this.setPatchedCaretPosition(el, newCaretPosition, value);
var newCaretPosition = expectedCaretPosition;
var leftBound = isPatternFormat ? format.indexOf('#') : prefix.length;
var rightBound = isPatternFormat ? format.lastIndexOf('#') + 1 : value.length - suffix.length;
if (key === 'ArrowLeft' || key === 'ArrowRight') {
var direction = key === 'ArrowLeft' ? 'left' : 'right';
newCaretPosition = this.correctCaretPosition(value, expectedCaretPosition, direction);
} else if (key === 'Delete' && !numRegex.test(value[expectedCaretPosition]) && !negativeRegex.test(value[expectedCaretPosition])) {
while (!numRegex.test(value[newCaretPosition]) && newCaretPosition < rightBound) {
newCaretPosition++;
}
} else if (key === 'Backspace' && !numRegex.test(value[expectedCaretPosition]) && !negativeRegex.test(value[expectedCaretPosition])) {
while (!numRegex.test(value[newCaretPosition - 1]) && newCaretPosition > leftBound) {
newCaretPosition--;
}
newCaretPosition = this.correctCaretPosition(value, newCaretPosition, 'left');
}
if (newCaretPosition !== expectedCaretPosition || expectedCaretPosition < leftBound || expectedCaretPosition > rightBound) {
e.preventDefault();
this.setPatchedCaretPosition(el, newCaretPosition, value);
}
/* NOTE: this is just required for unit test as we need to get the newCaretPosition,
Remove this when you find different solution */
if (e.isUnitTestRun) {
this.setPatchedCaretPosition(el, newCaretPosition, value);
}
this.props.onKeyDown(e);
}
/** required to handle the caret position when click anywhere within the input **/
}, {

@@ -595,22 +789,46 @@ key: 'onMouseUp',

}, {
key: 'onFocus',
value: function onFocus(e) {
var el = e.target;
var selectionStart = el.selectionStart,
value = el.value;
var caretPostion = this.correctCaretPosition(value, selectionStart);
if (caretPostion !== selectionStart) {
this.setPatchedCaretPosition(el, caretPostion, value);
}
this.props.onFocus(e);
}
}, {
key: 'render',
value: function render() {
var props = omit(this.props, propTypes);
var _props10 = this.props,
type = _props10.type,
displayType = _props10.displayType,
customInput = _props10.customInput,
renderText = _props10.renderText;
var value = this.state.value;
var inputProps = _extends({}, props, {
type: this.props.type,
value: this.state.value,
var otherProps = (0, _utils.omit)(this.props, propTypes);
var inputProps = _extends({}, otherProps, {
type: type,
value: value,
onChange: this.onChange,
onKeyDown: this.onKeyDown,
onMouseUp: this.onMouseUp
onMouseUp: this.onMouseUp,
onFocus: this.onFocus
});
if (this.props.displayType === 'text') {
return _react2.default.createElement(
if (displayType === 'text') {
return renderText ? renderText(value) || null : _react2.default.createElement(
'span',
props,
this.state.value
otherProps,
value
);
} else if (this.props.customInput) {
var CustomInput = this.props.customInput;
} else if (customInput) {
var CustomInput = customInput;
return _react2.default.createElement(CustomInput, inputProps);

@@ -617,0 +835,0 @@ }

{
"name": "react-number-format",
"description": "React component to format number in an input or as a text.",
"version": "2.0.4",
"version": "3.0.0-alpha",
"main": "lib/number_format.js",

@@ -37,2 +37,3 @@ "author": "Sudhanshu Yadav",

"babel-plugin-react-transform": "^2.0.2",
"babel-plugin-transform-flow-strip-types": "^6.22.0",
"babel-plugin-transform-object-assign": "^6.8.0",

@@ -44,3 +45,4 @@ "babel-preset-es2015": "^6.6.0",

"cross-env": "^3.1.4",
"enzyme": "^2.7.1",
"enzyme": "^3.0.0",
"enzyme-adapter-react-16": "^1.0.0",
"eslint": "^2.5.3",

@@ -54,2 +56,3 @@ "eslint-config-standard": "^5.1.0",

"eslint-plugin-standard": "^1.3.2",
"flow-bin": "^0.54.1",
"jasmine": "^2.4.1",

@@ -65,5 +68,6 @@ "jasmine-core": "^2.4.1",

"karma-webpack": "^1.7.0",
"material-ui": "^0.16.7",
"react": "^15.0.1",
"react-dom": "^15.0.1",
"material-ui": "^0.19.2",
"react": "^16.0.0",
"react-dom": "^16.0.0",
"react-test-renderer": "^16.0.0",
"react-transform-hmr": "^1.0.4",

@@ -74,8 +78,8 @@ "webpack": "^1.12.14",

"peerDependencies": {
"react": "^0.14 || ^15.0.0-rc || ^15.0.0",
"react-dom": "^0.14 || ^15.0.0-rc || ^15.0.0"
"react": "^0.14 || ^15.0.0-rc || ^15.0.0 || ^16.0.0-rc || ^16.0.0",
"react-dom": "^0.14 || ^15.0.0-rc || ^15.0.0 || ^16.0.0-rc || ^16.0.0"
},
"dependencies": {
"prop-types": "^15.5.8"
"prop-types": "^15.6.0"
}
}

@@ -28,11 +28,13 @@ # react-number-format

| suffix | String (ex : /-) | none | Add a prefix after the number |
| value | Number or String | null | Value to the number format. If passed as string it should have same decimal separator as the decimalSeparator props|
| value | Number or String | null | Value to the number format. It can be a float number, or formatted string. If value is string representation of number (unformatted), isNumericString props should be passed as true. |
| isNumericString | boolean | false | If value is passed as string representation of numbers (unformatted) then this should be passed as true |
| displayType | String: text / input | input | If input it renders a input element where formatting happens as you input characters. If text it renders it as a normal text in a span formatting the given value |
| type | One of ['text', 'tel'] | text | Input type attribute
| format | String : Hash based ex (#### #### #### ####) <br/> Or Function| none | If format given as hash string allow number input inplace of hash. If format given as function, component calls the function with unformatted number and expects formatted number.
| mask | String (ex : _) | none | If mask defined, component will show non entered placed with masked value.
| customInput | Component Reference | input | This allow supporting custom inputs with number format.
| onChange | (e, values) => {} | none | onChange handler accepts event object and [values object](#values-object)
| isAllowed | ([values](#values-object)) => true or false | none | A checker function to check if input value is valid or not
| type | One of ['text', 'tel'] | text | Input type attribute |
| format | String : Hash based ex (#### #### #### ####) <br/> Or Function| none | If format given as hash string allow number input inplace of hash. If format given as function, component calls the function with unformatted number and expects formatted number. |
| removeFormatting | (formattedValue) => numericString | none | If you are providing custom format method and it add numbers as format you will need to add custom removeFormatting logic |
| mask | String (ex : _) | none | If mask defined, component will show non entered placed with masked value. |
| customInput | Component Reference | input | This allow supporting custom inputs with number format. |
| onChange | (e, values) => {} | none | onChange handler accepts event object and [values object](#values-object) |
| isAllowed | ([values](#values-object)) => true or false | none | A checker function to check if input value is valid or not |
| renderText | (formattedValue) => React Element | null | A renderText method useful if you want to render formattedValue in different element other than span. |
**Other than this it accepts all the props which can be given to a input or span based on displayType you selected.**

@@ -51,3 +53,3 @@

### Notes and quirks
1. Value can be passed as string or number, but if it is passed as string you should maintain the same decimal separator on the string what you provided as decimalSeparator prop.
1. Value can be passed as string or number, but if it is passed as string it should be either formatted value or if it is a numeric string, you have to set isNumericString props to true.

@@ -69,2 +71,10 @@ 2. Value as prop will be rounded to given precision if format option is not provided.

#### Custom renderText method
```jsx
var NumberFormat = require('react-number-format');
<NumberFormat value={2456981} displayType={'text'} thousandSeparator={true} prefix={'$'} renderText={value => <div>{value}</div>} />
```
Output : `<div> $2,456,981 </div>`
#### Format with pattern : Format credit card as text

@@ -104,2 +114,10 @@ ```jsx

#### Format with mask as array
Mask can also be a array of string. Each item corresponds to the same index #.
```jsx
<NumberFormat format="##/##" placeholder="MM/YY" mask={['M', 'M', 'Y', 'Y']}/>
```
#### Custom format method : Format credit card expiry time

@@ -127,5 +145,5 @@ ```jsx

let month = limit(val.substring(0, 2), '12');
let date = limit(val.substring(2, 4), '31');
let year = val.substring(2, 4);
return month + (date.length ? '/' + date : '');
return month + (year.length ? '/' + year : '');
}

@@ -135,4 +153,9 @@

```
![Screencast example](https://media.giphy.com/media/3ohryizPIpkv2Qs3p6/giphy.gif)
![Screencast example](https://i.imgur.com/9wwdyFF.gif)
### Format phone number
```jsx
<NumberFormat format="+1 (###) ###-####" mask="_"/>
```
### Custom Inputs

@@ -163,2 +186,11 @@ You can easily extend your custom input with number format. But custom input should have all input props.

### Major Updates
### v3.0.0-alpha
- Added renderText prop to render formatted value differently.
- onChange api been changed. Now it receives [values object](#values-object) as second parameter.
- mask can be now array of string in which case mask at specific index will be mapped with the # of the pattern.
- Value can be passed as string or number, but if it is passed as string it should be either formatted value or if it is a numeric string, you have to set isNumericString props to true.
- Added support for numbers in prefix / suffix / pattern.
- Fixed caret position issues.
- Lot of bugs and stability fixes ([See release notes](https://github.com/s-yadav/react-number-format/releases))
### v2.0.0

@@ -165,0 +197,0 @@ - Added isAllowed prop to validate custom input and reject input based on it.

@@ -0,55 +1,19 @@

//@flow
import PropTypes from 'prop-types';
import React from 'react';
function noop(){}
import {
noop,
returnTrue,
charIsNumber,
escapeRegExp,
removeLeadingZero,
splitString,
limitToPrecision,
roundToPrecision,
omit,
setCaretPosition
} from './utils';
function escapeRegExp(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}
function removeLeadingZero(numStr) {
//remove leading zeros
return numStr.replace(/^0+/,'') || '0';
}
/**
* limit decimal numbers to given precision
* Not used .fixedTo because that will break with big numbers
*/
function limitToPrecision(numStr, precision) {
let str = ''
for (let i=0; i<=precision - 1; i++) {
str += numStr[i] || '0'
}
return str;
}
/**
* This method is required to round prop value to given precision.
* Not used .round or .fixedTo because that will break with big numbers
*/
function roundToPrecision(numStr, precision) {
const numberParts = numStr.split('.');
const roundedDecimalParts = parseFloat(`0.${numberParts[1] || '0'}`).toFixed(precision).split('.');
const intPart = numberParts[0].split('').reverse().reduce((roundedStr, current, idx) => {
if (roundedStr.length > idx) {
return (Number(roundedStr[0]) + Number(current)).toString() + roundedStr.substring(1, roundedStr.length);
}
return current + roundedStr;
}, roundedDecimalParts[0])
const decimalPart = roundedDecimalParts[1];
return intPart + (decimalPart ? '.' + decimalPart : '');
}
function omit(obj, keyMaps) {
const filteredObj = {};
Object.keys(obj).forEach((key) => {
if (!keyMaps[key]) filteredObj[key] = obj[key]
});
return filteredObj;
}
const propTypes = {

@@ -66,3 +30,4 @@ thousandSeparator: PropTypes.oneOfType([PropTypes.string, PropTypes.oneOf([true])]),

]),
mask: PropTypes.string,
removeFormatting: PropTypes.func,
mask: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
value: PropTypes.oneOfType([

@@ -72,2 +37,3 @@ PropTypes.number,

]),
isNumericString: PropTypes.bool,
customInput: PropTypes.func,

@@ -78,4 +44,6 @@ allowNegative: PropTypes.bool,

onChange: PropTypes.func,
onFocus: PropTypes.func,
type: PropTypes.oneOf(['text', 'tel']),
isAllowed: PropTypes.func
isAllowed: PropTypes.func,
renderText: PropTypes.func
};

@@ -89,2 +57,3 @@

allowNegative: true,
isNumericString: false,
type: 'text',

@@ -94,35 +63,56 @@ onChange: noop,

onMouseUp: noop,
isAllowed: function() {return true;}
onFocus: noop,
isAllowed: returnTrue,
};
class NumberFormat extends React.Component {
constructor(props) {
state: {
value?: string,
numAsString?: string
}
onChange: Function
onKeyDown: Function
onMouseUp: Function
onFocus: Function
static defaultProps: Object
constructor(props: Object) {
super(props);
const value = this.optimizeValueProp(props);
//validate props
this.validateProps();
const formattedValue = this.formatValueProp();
this.state = {
value: this.formatInput(value).formattedValue
value: formattedValue,
numAsString: this.removeFormatting(formattedValue)
}
this.onChange = this.onChange.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
this.onMouseUp = this.onMouseUp.bind(this);
this.onFocus = this.onFocus.bind(this);
}
componentDidUpdate(prevProps, prevState) {
this.updateValueIfRequired(prevProps, prevState);
componentDidUpdate(prevProps: Object) {
this.updateValueIfRequired(prevProps);
}
updateValueIfRequired(prevProps) {
updateValueIfRequired(prevProps: Object) {
const {props, state} = this;
if(prevProps !== props) {
//validate props
this.validateProps();
const stateValue = state.value;
let value = this.optimizeValueProp(props);
if (value === undefined) value = stateValue;
const lastNumStr = state.numAsString || '';
const {formattedValue} = this.formatInput(value);
const formattedValue = props.value === undefined ? this.formatNumString(lastNumStr).formattedValue : this.formatValueProp();
if (formattedValue !== stateValue) {
this.setState({
value : formattedValue
value : formattedValue,
numAsString: this.removeFormatting(formattedValue)
})

@@ -133,52 +123,184 @@ }

getFloatString(num, props) {
props = props || this.props;
const {decimalSeparator, thousandSeparator} = this.getSeparators(props);
return (num || '').replace(new RegExp(escapeRegExp(thousandSeparator || ''), 'g'), '').replace(decimalSeparator, '.');
/** Misc methods **/
getFloatString(num: string = '') {
const {decimalSeparator} = this.getSeparators();
const numRegex = this.getNumberRegex(true);
//remove negation for regex check
const hasNegation = num[0] === '-';
if(hasNegation) num = num.replace('-', '');
num = (num.match(numRegex) || []).join('').replace(decimalSeparator, '.');
//remove extra decimals
const firstDecimalIndex = num.indexOf('.');
if (firstDecimalIndex !== -1) {
num = `${num.substring(0, firstDecimalIndex)}.${num.substring(firstDecimalIndex + 1, num.length).replace(new RegExp(escapeRegExp(decimalSeparator), 'g'), '')}`
}
//add negation back
if(hasNegation) num = '-' + num;
return num;
}
getFloatValue(num, props) {
props = props || this.props;
return parseFloat(this.getFloatString(num, props)) || 0;
//returned regex assumes decimalSeparator is as per prop
getNumberRegex(g: boolean, ignoreDecimalSeparator?: boolean) {
const {format, decimalPrecision} = this.props;
const {decimalSeparator} = this.getSeparators();
return new RegExp('\\d' + (decimalSeparator && decimalPrecision !== 0 && !ignoreDecimalSeparator && !format ? '|' + escapeRegExp(decimalSeparator) : ''), g ? 'g' : undefined);
}
optimizeValueProp(props) {
const {decimalSeparator} = this.getSeparators(props);
const {decimalPrecision, format} = props;
getSeparators() {
const {decimalSeparator} = this.props;
let {thousandSeparator} = this.props;
let {value} = props;
if (thousandSeparator === true) {
thousandSeparator = ','
}
if (format || !(value || value === 0)) return value;
return {
decimalSeparator,
thousandSeparator
}
}
const isNumber = typeof value === 'number';
getMaskAtIndex (index: number) {
const {mask = ' '} = this.props;
if (typeof mask === 'string') {
return mask;
}
if (isNumber) value = value.toString();
return mask[index] || ' ';
}
value = this.removePrefixAndSuffix(isNumber ? value: this.getFloatString(value, props), props);
validateProps() {
const {mask} = this.props;
//round off value
if(typeof decimalPrecision === 'number') value = roundToPrecision(value, decimalPrecision);
//validate decimalSeparator and thousandSeparator
const {decimalSeparator, thousandSeparator} = this.getSeparators();
//correct decimal separator
if (decimalSeparator) {
value = value.replace('.', decimalSeparator);
}
//throw error if value has two decimal seperators
if (value.split(decimalSeparator).length > 2) {
if (decimalSeparator === thousandSeparator) {
throw new Error(`
Wrong input for value props.\n
More than one decimalSeparator found
Decimal separator can\'t be same as thousand separator.\n
thousandSeparator: ${thousandSeparator} (thousandSeparator = {true} is same as thousandSeparator = ",")
decimalSeparator: ${decimalSeparator} (default value for decimalSeparator is .)
`);
}
//if decimalPrecision is 0 remove decimalNumbers
if (decimalPrecision === 0) return value.split(decimalSeparator)[0]
//validate mask
if (mask) {
const maskAsStr = mask === 'string' ? mask : mask.toString();
if (maskAsStr.match(/\d/g)) {
throw new Error(`
Mask ${mask} should not contain numeric character;
`)
}
}
return value;
}
removePrefixAndSuffix(val, props) {
const {format, prefix, suffix} = props;
/** Misc methods end **/
/** caret specific methods **/
setPatchedCaretPosition(el: HTMLInputElement, caretPos: number, currentValue: string) {
/* setting caret position within timeout of 0ms is required for mobile chrome,
otherwise browser resets the caret position after we set it
We are also setting it without timeout so that in normal browser we don't see the flickering */
setCaretPosition(el, caretPos);
setTimeout(() => {
if(el.value === currentValue) setCaretPosition(el, caretPos);
}, 0);
}
/* This keeps the caret within typing area so people can't type in between prefix or suffix */
correctCaretPosition(value: string, caretPos: number, direction?: string) {
const {prefix, suffix, format} = this.props;
//in case of format as number limit between prefix and suffix
if (!format) {
const hasNegation = value[0] === '-';
return Math.min(Math.max(caretPos, prefix.length + (hasNegation ? 1 : 0)), (value.length - suffix.length));
}
//in case if custom format method don't do anything
if (typeof format === 'function') return caretPos;
/* in case format is string find the closest # position from the caret position */
//in case the caretPos have input value on it don't do anything
if (format[caretPos] === '#' && charIsNumber(value[caretPos])) return caretPos;
//if caretPos is just after input value don't do anything
if (format[caretPos - 1] === '#' && charIsNumber(value[caretPos - 1])) return caretPos;
//find the nearest caret position
const firstHashPosition = format.indexOf('#');
const lastHashPosition = format.lastIndexOf('#');
//limit the cursor between the first # position and the last # position
caretPos = Math.min(Math.max(caretPos, firstHashPosition), lastHashPosition + 1);
const nextPos = format.substring(caretPos, format.length).indexOf('#');
let caretLeftBound = caretPos;
const caretRightBoud = caretPos + (nextPos === -1 ? 0 : nextPos)
//get the position where the last number is present
while (caretLeftBound > firstHashPosition && (format[caretLeftBound] !== '#' || !charIsNumber(value[caretLeftBound]))) {
caretLeftBound -= 1;
}
const goToLeft = !charIsNumber(value[caretRightBoud])
|| (direction === 'left' && caretPos !== firstHashPosition)
|| (caretPos - caretLeftBound < caretRightBoud - caretPos);
return goToLeft ? caretLeftBound + 1 : caretRightBoud;
}
getCaretPosition(inputValue: string, formattedValue: string, caretPos: number) {
const {format} = this.props;
const stateValue = this.state.value;
const numRegex = this.getNumberRegex(true);
const inputNumber = (inputValue.match(numRegex) || []).join('');
const formattedNumber = (formattedValue.match(numRegex) || []).join('');
let j, i;
j = 0;
for(i=0; i<caretPos; i++){
const currentInputChar = inputValue[i];
const currentFormatChar = formattedValue[j]||'';
//no need to increase new cursor position if formatted value does not have those characters
//case inputValue = 1a23 and formattedValue = 123
if(!currentInputChar.match(numRegex) && currentInputChar !== currentFormatChar) continue;
//When we are striping out leading zeros maintain the new cursor position
//Case inputValue = 00023 and formattedValue = 23;
if (currentInputChar === '0' && currentFormatChar.match(numRegex) && currentFormatChar !== '0' && inputNumber.length !== formattedNumber.length) continue;
//we are not using currentFormatChar because j can change here
while(currentInputChar !== formattedValue[j] && j < formattedValue.length) j++;
j++;
}
if (typeof format === 'string' && !stateValue) {
//set it to the maximum value so it goes after the last number
j = formattedValue.length
}
//correct caret position if its outside of editable area
j = this.correctCaretPosition(formattedValue, j);
return j;
}
/** caret specific methods ends **/
/** methods to remove formattting **/
removePrefixAndSuffix(val: string) {
const {format, prefix, suffix} = this.props;
//remove prefix and suffix

@@ -205,214 +327,241 @@ if (!format && val) {

getSeparators(props) {
props = props || this.props;
removePatternFormatting(val: string) {
const {format} = this.props;
const formatArray = format.split('#').filter(str => str !== '');
let start = 0;
let numStr = '';
const {decimalSeparator} = props;
for (let i=0, ln=formatArray.length; i <= ln; i++) {
const part = formatArray[i] || '';
let {thousandSeparator} = props;
//if i is the last fragment take the index of end of the value
//For case like +1 (911) 911 91 91 having pattern +1 (###) ### ## ##
const index = i === ln ? val.length : val.indexOf(part, start);
if (thousandSeparator === true) {
thousandSeparator = ','
/* in any case if we don't find the pattern part in the value assume the val as numeric string
This will be also in case if user has started typing, in any other case it will not be -1
unless wrong prop value is provided */
if (index === -1) {
numStr = val;
break;
} else {
numStr += val.substring(start, index);
start = index + part.length;
}
}
if (decimalSeparator === thousandSeparator) {
throw new Error(`
Decimal separator can\'t be same as thousand separator.\n
thousandSeparator: ${thousandSeparator} (thousandSeparator = {true} is same as thousandSeparator = ",")
decimalSeparator: ${decimalSeparator} (default value for decimalSeparator is .)
`);
}
return (numStr.match(/\d/g) || []).join('');
}
return {
decimalSeparator,
thousandSeparator
removeFormatting(val: string) {
const {format, removeFormatting} = this.props;
if (!val) return val;
if (!format) {
val = this.removePrefixAndSuffix(val);
val = this.getFloatString(val);
} else if (typeof format === 'string') {
val = this.removePatternFormatting(val);
} else if (typeof removeFormatting === 'function') { //condition need to be handled if format method is provide,
val = removeFormatting(val);
} else {
val = (val.match(/\d/g) || []).join('')
}
return val;
}
/** methods to remove formattting end **/
getNumberRegex(g, ignoreDecimalSeparator) {
const {format, decimalPrecision} = this.props;
const {decimalSeparator} = this.getSeparators();
return new RegExp('\\d' + (decimalSeparator && decimalPrecision !== 0 && !ignoreDecimalSeparator && !format ? '|' + escapeRegExp(decimalSeparator) : ''), g ? 'g' : undefined);
}
setCaretPosition(el, caretPos) {
el.value = el.value;
// ^ this is used to not only get "focus", but
// to make sure we don't have it everything -selected-
// (it causes an issue in chrome, and having it doesn't hurt any other browser)
if (el !== null) {
if (el.createTextRange) {
const range = el.createTextRange();
range.move('character', caretPos);
range.select();
return true;
/*** format specific methods start ***/
/**
* Format when # based string is provided
* @param {string} numStr Numeric String
* @return {string} formatted Value
*/
formatWithPattern(numStr: string) {
const {format} = this.props;
let hashCount = 0;
const formattedNumberAry = format.split('');
for (let i = 0, ln = format.length; i < ln; i++) {
if (format[i] === '#') {
formattedNumberAry[i] = numStr[hashCount] || this.getMaskAtIndex(hashCount);
hashCount += 1;
}
// (el.selectionStart === 0 added for Firefox bug)
if (el.selectionStart || el.selectionStart === 0) {
el.focus();
el.setSelectionRange(caretPos, caretPos);
return true;
}
// fail city, fortunately this never happens (as far as I've tested) :)
el.focus();
return false;
}
return formattedNumberAry.join('');
}
/**
* @param {string} numStr Numeric string/floatString] It always have decimalSeparator as .
* @return {string} formatted Value
*/
formatAsNumber(numStr: string) {
const {decimalPrecision, allowNegative, prefix, suffix} = this.props;
const {thousandSeparator, decimalSeparator} = this.getSeparators();
setPatchedCaretPosition(el, caretPos, currentValue) {
/*
setting caret position within timeout of 0ms is required for mobile chrome,
otherwise browser resets the caret position after we set it
We are also setting it without timeout so that in normal browser we don't see the flickering
*/
this.setCaretPosition(el, caretPos);
setTimeout(() => {
if(el.value === currentValue) this.setCaretPosition(el, caretPos);
}, 0);
}
// Check if its negative number and remove negation for futher formatting
const hasNagation = numStr[0] === '-';
numStr = numStr.replace('-', '');
/* This keeps the caret within typing area so people can't type in between prefix or suffix */
correctCaretPosition(value, caretPos) {
const {prefix, suffix} = this.props;
return Math.min(Math.max(caretPos, prefix.length), (value.length - suffix.length));
const hasDecimalSeparator = numStr.indexOf('.') !== -1 || decimalPrecision;
const parts = numStr.split('.');
let beforeDecimal = parts[0];
let afterDecimal = parts[1] || '';
//remove leading zeros from number before decimal
beforeDecimal = removeLeadingZero(beforeDecimal);
//apply decimal precision if its defined
if (decimalPrecision !== undefined) afterDecimal = limitToPrecision(afterDecimal, decimalPrecision);
if(thousandSeparator) {
beforeDecimal = beforeDecimal.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1' + thousandSeparator);
}
//add prefix and suffix
if(prefix) beforeDecimal = prefix + beforeDecimal;
if(suffix) afterDecimal = afterDecimal + suffix;
//restore negation sign
if (hasNagation && allowNegative) beforeDecimal = '-' + beforeDecimal;
numStr = beforeDecimal + (hasDecimalSeparator && decimalSeparator || '') + afterDecimal;
return numStr;
}
formatWithPattern(str) {
const {format,mask} = this.props;
if (!format) return str;
const hashCount = format.split('#').length - 1;
let hashIdx = 0;
let frmtdStr = format;
formatNumString(value: string) {
const {format} = this.props;
let formattedValue = value;
for(let i=0, ln=str.length; i<ln; i++ ){
if(i < hashCount){
hashIdx = frmtdStr.indexOf('#');
frmtdStr = frmtdStr.replace('#',str[i]);
}
if (value === '') {
formattedValue = ''
} else if (value === '-' && !format) {
formattedValue = '-';
value = '';
} else if (typeof format === 'string') {
formattedValue = this.formatWithPattern(formattedValue);
} else if (typeof format === 'function') {
formattedValue = format(formattedValue);
} else {
formattedValue = this.formatAsNumber(formattedValue)
}
const lastIdx = frmtdStr.lastIndexOf('#');
if(mask){
return frmtdStr.replace(/#/g,mask);
return {
value,
formattedValue
}
return frmtdStr.substring(0,hashIdx + 1) + (lastIdx!==-1 ? frmtdStr.substring(lastIdx + 1, frmtdStr.length) :'');
}
formatInput(val) {
const {props, removePrefixAndSuffix} = this;
const {prefix, suffix, mask, format, allowNegative, decimalPrecision} = props;
const {thousandSeparator, decimalSeparator} = this.getSeparators();
const maskPattern = format && typeof format == 'string' && !!mask;
const numRegex = this.getNumberRegex(true);
let hasNegative, removeNegative;
formatValueProp() {
const {format, decimalPrecision} = this.props;
let {value, isNumericString} = this.props;
//change val to string if its number
if(typeof val === 'number') val = val + '';
// if value is not defined return empty string
if (value === undefined) return '';
const negativeRegex = new RegExp('(-)');
const doubleNegativeRegex = new RegExp('(-)(.)*(-)');
if (typeof value === 'number') {
value = value.toString();
isNumericString = true;
}
//check if it has negative numbers
if (allowNegative && !format) {
// Check number has '-' value
hasNegative = negativeRegex.test(val);
// Check number has 2 or more '-' values
removeNegative = doubleNegativeRegex.test(val);
//round the number based on decimalPrecision
//format only if non formatted value is provided
if (isNumericString && !format && typeof decimalPrecision === 'number') {
value = roundToPrecision(value, decimalPrecision)
}
//remove prefix and suffix
val = removePrefixAndSuffix(val, props);
const values = isNumericString ? this.formatNumString(value) : this.formatInput(value);
const valMatch = val && val.match(numRegex);
return values.formattedValue;
}
if (!valMatch && removeNegative) {
return {value :'', formattedValue: ''}
} else if (!valMatch && hasNegative) {
return {value :'', formattedValue: '-'}
} else if (!valMatch) {
return {value :'', formattedValue: (maskPattern ? '' : '')}
}
formatNegation(value: string = '') {
const {allowNegative} = this.props;
const negationRegex = new RegExp('(-)');
const doubleNegationRegex = new RegExp('(-)(.)*(-)');
const num = val.match(numRegex).join('');
// Check number has '-' value
const hasNegation = negationRegex.test(value);
let formattedValue = num;
// Check number has 2 or more '-' values
const removeNegation = doubleNegationRegex.test(value);
if(format){
if(typeof format == 'string'){
formattedValue = this.formatWithPattern(formattedValue);
}
else if(typeof format == 'function'){
formattedValue = format(formattedValue);
}
//remove negation
value = value.replace(/-/g, '');
if (hasNegation && !removeNegation && allowNegative) {
value = '-' + value;
}
else{
const hasDecimalSeparator = formattedValue.indexOf(decimalSeparator) !== -1 || decimalPrecision;
const parts = formattedValue.split(decimalSeparator);
let beforeDecimal = parts[0];
let afterDecimal = parts[1] || '';
return value;
}
//remove leading zeros from number before decimal
beforeDecimal = removeLeadingZero(beforeDecimal);
formatInput(value: string = '') {
const {format} = this.props;
//apply decimal precision if its defined
if (decimalPrecision !== undefined) afterDecimal = limitToPrecision(afterDecimal, decimalPrecision);
//format negation only if we are formatting as number
if (!format) {
value = this.formatNegation(value);
}
if(thousandSeparator) {
beforeDecimal = beforeDecimal.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1' + thousandSeparator);
}
//remove formatting from number
value = this.removeFormatting(value);
//add prefix and suffix
if(prefix) beforeDecimal = prefix + beforeDecimal;
if(suffix) afterDecimal = afterDecimal + suffix;
return this.formatNumString(value);
}
if (hasNegative && !removeNegative) beforeDecimal = '-' + beforeDecimal;
/*** format specific methods end ***/
isCharacterAFormat(caretPos: number, value: string) {
const {format, prefix, suffix} = this.props;
formattedValue = beforeDecimal + (hasDecimalSeparator && decimalSeparator || '') + afterDecimal;
}
//check within format pattern
if (typeof format === 'string' && format[caretPos] !== '#') return true;
return {
value : (hasNegative && !removeNegative ? '-' : '') + removePrefixAndSuffix(formattedValue, props).match(numRegex).join(''),
formattedValue : formattedValue
//check in number format
if (!format && (caretPos < prefix.length || caretPos >= value.length - suffix.length)) return true;
return false;
}
checkIfFormatGotDeleted(start: number, end: number, value: string) {
for (let i = start; i < end; i++) {
if (this.isCharacterAFormat(i, value)) return true;
}
return false;
}
getCaretPosition(inputValue, formattedValue, caretPos) {
const numRegex = this.getNumberRegex(true);
const inputNumber = (inputValue.match(numRegex) || []).join('');
const formattedNumber = (formattedValue.match(numRegex) || []).join('');
let j, i;
j=0;
/**
* This will check if any formatting got removed by the delete or backspace and reset the value
* It will also work as fallback if android chome keyDown handler does not work
**/
correctInputValue(caretPos: number, lastValue: string, value: string) {
for(i=0; i<caretPos; i++){
const currentInputChar = inputValue[i];
const currentFormatChar = formattedValue[j]||'';
//no need to increase new cursor position if formatted value does not have those characters
//case inputValue = 1a23 and formattedValue = 123
if(!currentInputChar.match(numRegex) && currentInputChar !== currentFormatChar) continue;
//don't do anyhting if something got added, or if value is empty string (when whole input is cleared)
if (value.length >= lastValue.length || !value.length) {
return value;
}
//When we are striping out leading zeros maintain the new cursor position
//Case inputValue = 00023 and formattedValue = 23;
if (currentInputChar === '0' && currentFormatChar.match(numRegex) && currentFormatChar !== '0' && inputNumber.length !== formattedNumber.length) continue;
const start = caretPos;
const lastValueParts = splitString(lastValue, caretPos);
const newValueParts = splitString(value, caretPos);
const deletedIndex = lastValueParts[1].lastIndexOf(newValueParts[1]);
const diff = deletedIndex !== -1 ? lastValueParts[1].substring(0, deletedIndex) : '';
const end = start + diff.length;
//we are not using currentFormatChar because j can change here
while(currentInputChar !== formattedValue[j] && !(formattedValue[j]||'').match(numRegex) && j<formattedValue.length) j++;
j++;
//if format got deleted reset the value to last value
if (this.checkIfFormatGotDeleted(start, end, lastValue)) {
value = lastValue;
}
//correct caret position if its outsize of editable area
j = this.correctCaretPosition(formattedValue, j);
return j;
return value;
}
onChange(e) {
onChange(e: SyntheticInputEvent) {
e.persist();
const el = e.target;
const inputValue = el.value;
let inputValue = el.value;
const {state, props} = this;
const {isAllowed} = props;
const lastValue = state.value;
let {formattedValue, value} = this.formatInput(inputValue); // eslint-disable-line prefer-const
const lastValue = state.value || '';

@@ -422,6 +571,10 @@ /*Max of selectionStart and selectionEnd is taken for the patch of pixel and other mobile device caret bug*/

inputValue = this.correctInputValue(currentCaretPosition, lastValue, inputValue);
let {formattedValue = '', value} = this.formatInput(inputValue); // eslint-disable-line prefer-const
const valueObj = {
formattedValue,
value,
floatValue: this.getFloatValue(value)
floatValue: parseFloat(value)
};

@@ -444,3 +597,3 @@

if (formattedValue !== lastValue) {
this.setState({value : formattedValue},()=>{
this.setState({value : formattedValue, numAsString: this.removeFormatting(formattedValue)}, () => {
props.onChange(e, valueObj);

@@ -453,34 +606,62 @@ });

onKeyDown(e) {
onKeyDown(e: SyntheticKeyboardInputEvent) {
const el = e.target;
const {key} = e;
const {selectionEnd, value} = el;
let {selectionStart} = el;
const {decimalPrecision, prefix, suffix} = this.props;
const {key} = e;
const {selectionStart} = el;
let expectedCaretPosition;
const {decimalPrecision, prefix, suffix, format, onKeyDown} = this.props;
const numRegex = this.getNumberRegex(false, decimalPrecision !== undefined);
const negativeRegex = new RegExp('-');
const isPatternFormat = typeof format === 'string';
//Handle backspace and delete against non numerical/decimal characters
if(selectionStart === selectionEnd) {
let newCaretPosition = selectionStart;
//Handle backspace and delete against non numerical/decimal characters or arrow keys
if (key === 'ArrowLeft' || key === 'Backspace') {
expectedCaretPosition = selectionStart - 1;
} else if (key === 'ArrowRight') {
expectedCaretPosition = selectionStart + 1;
} else if (key === 'Delete') {
expectedCaretPosition = selectionStart;
}
if (key === 'ArrowLeft' || key === 'ArrowRight') {
selectionStart += key === 'ArrowLeft' ? -1 : +1;
newCaretPosition = this.correctCaretPosition(value, selectionStart);
} else if (key === 'Delete' && !numRegex.test(value[selectionStart]) && !negativeRegex.test(value[selectionStart])) {
while (!numRegex.test(value[newCaretPosition]) && newCaretPosition < (value.length - suffix.length)) newCaretPosition++;
} else if (key === 'Backspace' && !numRegex.test(value[selectionStart - 1]) && !negativeRegex.test(value[selectionStart-1])) {
while (!numRegex.test(value[newCaretPosition - 1]) && newCaretPosition > prefix.length) newCaretPosition--;
}
//if expectedCaretPosition is not set it means we don't want to Handle keyDown
//also if multiple characters are selected don't handle
if (expectedCaretPosition === undefined || selectionStart !== selectionEnd) {
onKeyDown(e);
return;
}
if (newCaretPosition !== selectionStart) {
e.preventDefault();
this.setPatchedCaretPosition(el, newCaretPosition, value);
}
let newCaretPosition = expectedCaretPosition;
const leftBound = isPatternFormat ? format.indexOf('#') : prefix.length;
const rightBound = isPatternFormat ? format.lastIndexOf('#') + 1 : value.length - suffix.length;
if (key === 'ArrowLeft' || key === 'ArrowRight') {
const direction = key === 'ArrowLeft' ? 'left' : 'right';
newCaretPosition = this.correctCaretPosition(value, expectedCaretPosition, direction);
} else if (key === 'Delete' && !numRegex.test(value[expectedCaretPosition]) && !negativeRegex.test(value[expectedCaretPosition])) {
while (!numRegex.test(value[newCaretPosition]) && newCaretPosition < rightBound) newCaretPosition++;
} else if (key === 'Backspace' && !numRegex.test(value[expectedCaretPosition]) && !negativeRegex.test(value[expectedCaretPosition])) {
while (!numRegex.test(value[newCaretPosition - 1]) && newCaretPosition > leftBound){ newCaretPosition--; }
newCaretPosition = this.correctCaretPosition(value, newCaretPosition, 'left');
}
if (newCaretPosition !== expectedCaretPosition || expectedCaretPosition < leftBound || expectedCaretPosition > rightBound) {
e.preventDefault();
this.setPatchedCaretPosition(el, newCaretPosition, value);
}
/* NOTE: this is just required for unit test as we need to get the newCaretPosition,
Remove this when you find different solution */
if (e.isUnitTestRun) {
this.setPatchedCaretPosition(el, newCaretPosition, value);
}
this.props.onKeyDown(e);
}
onMouseUp(e) {
/** required to handle the caret position when click anywhere within the input **/
onMouseUp(e: SyntheticMouseInputEvent) {
const el = e.target;

@@ -499,19 +680,35 @@ const {selectionStart, selectionEnd, value} = el;

onFocus(e: SyntheticInputEvent) {
const el = e.target;
const {selectionStart, value} = el;
const caretPostion = this.correctCaretPosition(value, selectionStart);
if (caretPostion !== selectionStart) {
this.setPatchedCaretPosition(el, caretPostion, value);
}
this.props.onFocus(e);
}
render() {
const props = omit(this.props, propTypes);
const {type, displayType, customInput, renderText} = this.props;
const {value} = this.state;
const inputProps = Object.assign({}, props, {
type:this.props.type,
value:this.state.value,
onChange:this.onChange,
onKeyDown:this.onKeyDown,
onMouseUp: this.onMouseUp
const otherProps = omit(this.props, propTypes);
const inputProps = Object.assign({}, otherProps, {
type,
value,
onChange: this.onChange,
onKeyDown: this.onKeyDown,
onMouseUp: this.onMouseUp,
onFocus: this.onFocus
})
if( this.props.displayType === 'text'){
return (<span {...props}>{this.state.value}</span>);
if( displayType === 'text'){
return renderText ? (renderText(value) || null) : <span {...otherProps}>{value}</span>;
}
else if (this.props.customInput) {
const CustomInput = this.props.customInput;
else if (customInput) {
const CustomInput = customInput;
return (

@@ -518,0 +715,0 @@ <CustomInput

@@ -14,7 +14,7 @@ import {cardExpiry} from '../../custom_formatters/card_expiry';

expect(cardExpiry('00')).toEqual('01');
expect(cardExpiry('0200')).toEqual('02/01');
expect(cardExpiry('024')).toEqual('02/04');
expect(cardExpiry('0200')).toEqual('02/00');
expect(cardExpiry('024')).toEqual('02/4');
expect(cardExpiry('1410')).toEqual('12/10');
expect(cardExpiry('1235')).toEqual('12/31');
expect(cardExpiry('1235')).toEqual('12/35');
});
})
import React from 'react';
import { shallow } from 'enzyme';
import { shallow } from '../test_util';
import NumberFormat from '../../src/number_format';

@@ -24,3 +25,3 @@

const wrapper = shallow(<NumberFormat value="41111111111111" displayType={'text'} format="#### #### #### ####" />);
expect(wrapper.find('span').text()).toEqual('4111 1111 1111 11');
expect(wrapper.find('span').text()).toEqual('4111 1111 1111 11 ');
});

@@ -47,2 +48,8 @@

});
it('should accept custom renderText method', () => {
const wrapper = shallow(<NumberFormat value="4111.11" thousandSeparator="," renderText={value => <div>{value}</div>} displayType={'text'} decimalPrecision={4} />);
expect(wrapper.find('div').text()).toEqual('4,111.1100');
})
});
import React from 'react';
import { shallow, mount } from 'enzyme';
import NumberFormat from '../../src/number_format';
import {getCustomEvent, simulateKeyInput, shallow, mount} from '../test_util';
import {getCustomEvent} from '../test_util';
/**

@@ -24,3 +23,2 @@ * This suit is to test NumberFormat when normal numeric values are provided without any formatting options

expect(wrapper.state().value).toEqual('');
expect(wrapper.find('input').get(0).value).toEqual('');
});

@@ -31,3 +29,2 @@

expect(wrapper.state().value).toEqual('');
expect(wrapper.find('input').get(0).value).toEqual('');
});

@@ -38,3 +35,3 @@

wrapper.find('input').simulate('change', getCustomEvent('2456981.89'));
simulateKeyInput(wrapper.find('input'), '2456981.89', 0);

@@ -47,8 +44,9 @@ expect(wrapper.state().value).toEqual('$2,456,981.89');

wrapper.find('input').simulate('change', getCustomEvent('-2456981.89'));
simulateKeyInput(wrapper.find('input'), '-', 0);
expect(wrapper.state().value).toEqual('-$2,456,981.89');
expect(wrapper.state().value).toEqual('-');
wrapper.find('input').simulate('change', getCustomEvent('-'));
expect(wrapper.state().value).toEqual('-');
simulateKeyInput(wrapper.find('input'), '123.55', 1);
expect(wrapper.state().value).toEqual('-$123.55');
});

@@ -58,9 +56,13 @@

it('removes negation when double negation is done', () => {
const wrapper = shallow(<NumberFormat thousandSeparator={true} prefix={'$'} />);
const wrapper = shallow(<NumberFormat thousandSeparator={true} prefix={'$'} value={-2456981.89} />);
wrapper.find('input').simulate('change', getCustomEvent('--2456981.89'));
expect(wrapper.state().value).toEqual('-$2,456,981.89');
simulateKeyInput(wrapper.find('input'), '-', 1);
expect(wrapper.state().value).toEqual('$2,456,981.89');
wrapper.find('input').simulate('change', getCustomEvent('--'));
wrapper.setProps({value: ''});
wrapper.update();
simulateKeyInput(wrapper.find('input'), '--', 0);
expect(wrapper.state().value).toEqual('');

@@ -70,9 +72,10 @@ });

it('allows negation and double negation any cursor position in the input', () => {
const wrapper = shallow(<NumberFormat thousandSeparator={true} prefix={'$'}/>);
const wrapper = shallow(<NumberFormat thousandSeparator={true} prefix={'$'} value={2456981.89}/>);
wrapper.find('input').simulate('change', getCustomEvent('24569-81.89'));
simulateKeyInput(wrapper.find('input'), '-', 5);
expect(wrapper.state().value).toEqual('-$2,456,981.89');
wrapper.find('input').simulate('change', getCustomEvent('24569--81.89'));
//restore negation back
simulateKeyInput(wrapper.find('input'), '-', 7);

@@ -85,23 +88,21 @@ expect(wrapper.state().value).toEqual('$2,456,981.89');

const wrapper = shallow(<NumberFormat thousandSeparator={'.'} decimalSeparator={','} prefix={'$'} />);
const input = wrapper.find('input');
simulateKeyInput(wrapper.find('input'), '2456981,89', 0);
input.simulate('change', getCustomEvent('2456981,89'));
expect(wrapper.state().value).toEqual('$2.456.981,89');
wrapper.setProps({thousandSeparator: "'"});
wrapper.update();
/** TODO : Failing testcases, changing thousand seperator, decimal seperator on the fly fails **/
//expect(wrapper.state().value).toEqual("$2'456'981,89");
input.simulate('change', getCustomEvent('2456981,89'));
expect(wrapper.state().value).toEqual("$2'456'981,89");
wrapper.setProps({thousandSeparator: " ", decimalSeparator:"'" });
input.simulate('change', getCustomEvent("2456981'89"));
//changing decimal separator in the fly should work
wrapper.setProps({decimalSeparator: '.'});
wrapper.update();
expect(wrapper.state().value).toEqual("$2'456'981.89");
wrapper.setProps({thousandSeparator: " ", decimalSeparator:"'", value:'' });
wrapper.update();
simulateKeyInput(wrapper.find('input'), "2456981'89", 0);
expect(wrapper.state().value).toEqual("$2 456 981'89");
wrapper.setProps({thousandSeparator: ",", decimalSeparator: "."});
input.simulate('change', getCustomEvent("2456981.89"));
expect(wrapper.state().value).toEqual("$2,456,981.89");
});

@@ -227,3 +228,3 @@

it('should round the initial value to given decimalPrecision', () => {
const wrapper = shallow(<NumberFormat value={123213.7536} decimalPrecision={1}/>, {lifecycleExperimental: true});
const wrapper = shallow(<NumberFormat value={123213.7536} isNumericString={true} decimalPrecision={1}/>, {lifecycleExperimental: true});
expect(wrapper.state().value).toEqual('123213.8');

@@ -259,3 +260,3 @@

wrapper.setProps({
value: '56.790,876'
value: '56790.876'
})

@@ -265,3 +266,3 @@ expect(wrapper.state().value).toEqual('56.790,88');

wrapper.setProps({
value: '981273724234817383478127,678'
value: '981273724234817383478127.678'
});

@@ -268,0 +269,0 @@

import React from 'react';
import { shallow, mount } from 'enzyme';
import TextField from 'material-ui/TextField';
import NumberFormat from '../../src/number_format';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import TextField from 'material-ui/TextField';
import {simulateKeyInput, shallow, mount} from '../test_util';
import {getCustomEvent} from '../test_util';
import {cardExpiry} from '../../custom_formatters/card_expiry';
/*** format_number input as input ****/

@@ -14,3 +13,3 @@ describe('NumberFormat as input', () => {

const wrapper = mount(<NumberFormat />);
expect(wrapper.find('input').get(0).getAttribute('type')).toEqual('text');
expect(wrapper.find('input').instance().getAttribute('type')).toEqual('text');
});

@@ -20,3 +19,3 @@

const wrapper = mount(<NumberFormat type="tel" />);
expect(wrapper.find('input').get(0).getAttribute('type')).toEqual('tel');
expect(wrapper.find('input').instance().getAttribute('type')).toEqual('tel');
});

@@ -27,5 +26,7 @@

expect(wrapper.state().value).toEqual('$2,456,981');
expect(wrapper.find('input').get(0).value).toEqual('$2,456,981');
expect(wrapper.find('input').instance().value).toEqual('$2,456,981');
});
it('should not reset number inputs value if number input renders again with same props', () => {

@@ -46,5 +47,5 @@ class WrapperComponent extends React.Component {

const input = wrapper.find('input');
const domInput = input.get(0);
const domInput = input.instance();
input.simulate('change', getCustomEvent('2456981'));
simulateKeyInput(input, '2456981', 0);

@@ -58,80 +59,16 @@ expect(domInput.value).toEqual('$2,456,981');

it('should listen on change event and formmat properly', () => {
const wrapper = shallow(<NumberFormat thousandSeparator={true} prefix={'$'} />);
wrapper.find('input').simulate('change', getCustomEvent('2456981'));
expect(wrapper.state().value).toEqual('$2,456,981');
});
it('removes negation when format props is provided', () => {
const wrapper = shallow(<NumberFormat format="#### #### #### ####" />);
wrapper.find('input').simulate('change', getCustomEvent('-2456981'));
expect(wrapper.state().value).toEqual('2456 981');
});
it('should have proper intermediate formatting', () => {
const wrapper = shallow(<NumberFormat format="#### #### #### ####" />);
const wrapper = shallow(<NumberFormat format="#### #### #### ####" value="2342 2345 2342 2345" />);
const input = wrapper.find('input');
//case 1st - if entered a number where formatting should happen
input.simulate('change', getCustomEvent('41111'));
expect(wrapper.state().value).toEqual('4111 1');
//by default space is mask
simulateKeyInput(input, '-', 0);
expect(wrapper.state().value).toEqual('2342 2345 2342 2345');
//case 2st - if pressed backspace at formatting point
input.simulate('change', getCustomEvent('411 1'));
expect(wrapper.state().value).toEqual('4111');
//case 3rd - if something entered before formatting point
input.simulate('change', getCustomEvent('41111 1'));
expect(wrapper.state().value).toEqual('4111 11');
//case 4th - if something entered when maximum numbers are input
input.simulate('change', getCustomEvent('41112 1111 1111 1111'));
expect(wrapper.state().value).toEqual('4111 2111 1111 1111');
//case 5th - if something removed after empty space
input.simulate('change', getCustomEvent('4111 2111 211 1111'));
expect(wrapper.state().value).toEqual('4111 2111 2111 111');
//case 6th - if space is removed
input.simulate('change', getCustomEvent('41112111 1211 1111'));
expect(wrapper.state().value).toEqual('4111 2111 1211 1111');
simulateKeyInput(input, '-', 4);
expect(wrapper.state().value).toEqual('2342 2345 2342 2345');
});
it('should have proper intermediate masking', () => {
const wrapper = shallow(<NumberFormat format="#### #### #### ####" mask="_"/>);
const input = wrapper.find('input');
//case 1st - if entered a number where formatting should happen
input.simulate('change', getCustomEvent('41111'));
expect(wrapper.state().value).toEqual('4111 1___ ____ ____');
//case 2st - if pressed backspace at formatting point
input.simulate('change', getCustomEvent('411 1'));
expect(wrapper.state().value).toEqual('4111 ____ ____ ____');
//case 3rd - if something entered before formatting point
input.simulate('change', getCustomEvent('41111 1'));
expect(wrapper.state().value).toEqual('4111 11__ ____ ____');
//case 4th - if something entered when maximum numbers are input
input.simulate('change', getCustomEvent('41112 1111 1111 1111'));
expect(wrapper.state().value).toEqual('4111 2111 1111 1111');
//case 5th - if something removed after empty space
input.simulate('change', getCustomEvent('4111 2111 211 1111'));
expect(wrapper.state().value).toEqual('4111 2111 2111 111_');
//case 6th - if space is removed
input.simulate('change', getCustomEvent('41112111 1211 1111'));
expect(wrapper.state().value).toEqual('4111 2111 1211 1111');
});
it('should block inputs based on isAllowed callback', () => {

@@ -141,10 +78,10 @@ const wrapper = shallow(<NumberFormat isAllowed={(values) => {

return floatValue <= 10000;
}}/>, {lifecycleExperimental: true});
}} value={9999}/>, {lifecycleExperimental: true});
const input = wrapper.find('input');
input.simulate('change', getCustomEvent('9999'));
expect(wrapper.state().value).toEqual('9999');
input.simulate('change', getCustomEvent('99992'));
simulateKeyInput(input, '9', 4);
expect(wrapper.state().value).toEqual('9999');

@@ -166,52 +103,42 @@ })

input.simulate('change', getCustomEvent('2456981,89'));
expect(input.get(0).value).toEqual('2.456.981,89');
simulateKeyInput(input, '2456981,89', 0);
expect(input.instance().value).toEqual('2.456.981,89');
wrapper.setProps({format: '#### #### #### ####', mask: '_'});
input.simulate('change', getCustomEvent('41111 1'));
expect(input.get(0).value).toEqual('4111 11__ ____ ____');
wrapper.setProps({format: '#### #### #### ####', mask: '_', value: ''});
simulateKeyInput(input, '411111', 0);
expect(input.instance().value).toEqual('4111 11__ ____ ____');
});
});
describe('Test caret position manipulation', () => {
it('caret should move to typing area if user click between prefix or suffix', () => {
const wrapper = shallow(<NumberFormat thousandSeparator={true} prefix={'$'} suffix={' per sqr. mt.'} />);
const instance = wrapper.instance();
expect(instance.getCaretPosition('$123,124,234.56 per sqr. mt.', '$123,124,234.56 per sqr. mt.', 0)).toEqual(1);
describe('Test masking', () => {
it('should allow mask as string', () => {
const wrapper = shallow(<NumberFormat format="#### #### ####" mask="_"/>);
expect(instance.getCaretPosition('$123 per sqr. mt.', '$123 per sqr. mt.', 7)).toEqual(4);
});
simulateKeyInput(wrapper.find('input'), '111', 0);
expect(wrapper.state().value).toEqual('111_ ____ ____');
it('should update cursor position if formmatted value is different than input value', () => {
const wrapper = shallow(<NumberFormat thousandSeparator={true} prefix={'$'} />);
const instance = wrapper.instance();
simulateKeyInput(wrapper.find('input'), '1', 3);
expect(wrapper.state().value).toEqual('1111 ____ ____');
});
//case when nonNumeric characters are removed
expect(instance.getCaretPosition('$123,a124,234.56', '$123,124,234.56', 6)).toEqual(5);
it('should allow mask as array of strings', () => {
const wrapper = shallow(<NumberFormat format="##/##/####" mask={['D', 'D', 'M', 'M', 'Y', 'Y', 'Y', 'Y']}/>);
//case when formatting is changed
expect(instance.getCaretPosition('$12312', '$12,312', 5)).toEqual(6);
simulateKeyInput(wrapper.find('input'), '1', 0);
expect(wrapper.state().value).toEqual('1D/MM/YYYY');
//case if isAllowed is blocking new input say minimum value is 9
expect(instance.getCaretPosition('$1.234', '$10.456', 2)).toEqual(2);
simulateKeyInput(wrapper.find('input'), '3', 1);
expect(wrapper.state().value).toEqual('13/MM/YYYY');
});
//case when rounding off
expect(instance.getCaretPosition('$1.235600', '$1.246', 9)).toEqual(6);
});
it('should throw an error if mask has numeric character', () => {
expect(() => {
shallow(<NumberFormat format="#### #### ####" mask="1"/>)
}).toThrow()
it('maintains the cursor position when removing leading zero', () => {
const wrapper = shallow(<NumberFormat thousandSeparator={true} prefix={'$'}/>);
const instance = wrapper.instance();
expect(instance.getCaretPosition('$000123', '$123', 4)).toEqual(1);
expect(instance.getCaretPosition('$00.123', '$0.123', 3)).toEqual(2);
});
// Failing testcase not a critical one
it('corrects the cursor position if custom format is applied', () => {
const wrapper = shallow(<NumberFormat format={cardExpiry} />);
const instance = wrapper.instance();
expect(instance.getCaretPosition('00/12', '01/12', 2)).toEqual(2);
expect(instance.getCaretPosition('001/1', '01/11', 2)).toEqual(2);
expect(() => {
shallow(<NumberFormat format="#### #### ####" mask={['D', 'D', 'M', '1', '2', 'Y', 'Y', 'Y']}/>)
}).toThrow()
})
})
});

@@ -0,1 +1,6 @@

import Enzyme, {shallow, mount} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });
const noop = function(){};

@@ -12,2 +17,15 @@

function getEvent(eventProps, targetProps) {
let event = new Event('custom');
const el = document.createElement('input');
Object.keys(targetProps).forEach((key) => {
el[key] = targetProps[key];
})
event = Object.assign({}, event, eventProps, {target: el});
return event;
}
export function setCaretPosition(event, caretPos) {

@@ -18,1 +36,100 @@ const {target} = event;

}
export function simulateKeyInput(input, key, selectionStart, selectionEnd, setSelectionRange) {
if (selectionEnd === undefined) {
selectionEnd = selectionStart;
}
const currentValue = input.prop('value');
let defaultPrevented = false;
const keydownEvent = getEvent({
preventDefault: function() {
defaultPrevented = true;
},
key,
isUnitTestRun: true
}, {
value: currentValue,
selectionStart,
selectionEnd,
setSelectionRange: setSelectionRange || noop,
focus: noop
})
//fire key down event
input.simulate('keydown', keydownEvent);
//fire change event
if (!defaultPrevented && key !== 'ArrowLeft' && key !== 'ArrowRight') {
//get changed caret positon
let newCaretPosition, newValue;
if (key === 'Backspace') {
newCaretPosition = selectionStart !== selectionEnd ? selectionStart : selectionStart - 1;
newValue = selectionStart !== selectionEnd ?
currentValue.substring(0, selectionStart) + currentValue.substring(selectionEnd, currentValue.length) :
currentValue.substring(0, newCaretPosition) + currentValue.substring(selectionStart, currentValue.length);
} else if (key === 'Delete') {
newCaretPosition = selectionStart;
newValue = selectionStart !== selectionEnd ?
currentValue.substring(0, selectionStart) + currentValue.substring(selectionEnd, currentValue.length) :
currentValue.substring(0, selectionStart) + currentValue.substring(selectionStart + 1, currentValue.length);
} else {
newCaretPosition = selectionStart + key.length;
newValue = selectionStart !== selectionEnd ?
currentValue.substring(0, selectionStart) + key + currentValue.substring(selectionEnd, currentValue.length) :
currentValue.substring(0, selectionStart) + key + currentValue.substring(selectionStart, currentValue.length);
}
const changeEvent = getEvent({
persist: noop,
key
}, {
value: newValue,
selectionStart: newCaretPosition,
selectionEnd: newCaretPosition,
setSelectionRange: setSelectionRange || noop,
focus: noop
})
input.simulate('change', changeEvent);
}
}
export function simulateMousUpEvent(input, selectionStart, setSelectionRange) {
const selectionEnd = selectionStart;
const currentValue = input.prop('value');
const mouseUpEvent = getEvent({}, {
value: currentValue,
selectionStart,
selectionEnd,
setSelectionRange: setSelectionRange || noop,
focus: noop
});
input.simulate('mouseup', mouseUpEvent);
}
export function simulateFocusEvent(input, selectionStart, setSelectionRange) {
const selectionEnd = selectionStart;
const currentValue = input.prop('value');
const focusEvent = getEvent({}, {
value: currentValue,
selectionStart,
selectionEnd,
setSelectionRange: setSelectionRange || noop,
focus: noop
});
input.simulate('focus', focusEvent);
}
export {Enzyme, shallow, mount};

@@ -7,3 +7,3 @@ module.exports = {

},
devtool: "eval-source-map",
devtool: "cheap-eval-source-map",
debug: true,

@@ -10,0 +10,0 @@ output: {

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc