react-number-format
Advanced tools
Comparing version 2.0.4 to 3.0.0-alpha
@@ -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
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
335907
30
3655
212
40
3
+ Addedreact@16.14.0(transitive)
+ Addedreact-dom@16.14.0(transitive)
+ Addedscheduler@0.19.1(transitive)
- Removedasap@2.0.6(transitive)
- Removedcore-js@1.2.7(transitive)
- Removedcreate-react-class@15.7.0(transitive)
- Removedencoding@0.1.13(transitive)
- Removedfbjs@0.8.18(transitive)
- Removediconv-lite@0.6.3(transitive)
- Removedis-stream@1.1.0(transitive)
- Removedisomorphic-fetch@2.2.1(transitive)
- Removednode-fetch@1.7.3(transitive)
- Removedpromise@7.3.1(transitive)
- Removedreact@15.7.0(transitive)
- Removedreact-dom@15.7.0(transitive)
- Removedsafer-buffer@2.1.2(transitive)
- Removedsetimmediate@1.0.5(transitive)
- Removedua-parser-js@0.7.40(transitive)
- Removedwhatwg-fetch@3.6.20(transitive)
Updatedprop-types@^15.6.0