From 5f80f2766bd98fbdf435526a6bd257acd75d8908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20D=C4=85browski?= Date: Sat, 17 Oct 2020 20:59:56 +0200 Subject: [PATCH 1/4] fix: handle scientific notation for number added unit tests to prove it does the work chore: added vscode config that won't break anything in this project --- .gitignore | 1 + .vscode/settings.json | 3 +++ src/number_format.js | 1 - src/utils.js | 31 +++++++++++++++++++++++++++-- test/library/format_as_text.spec.js | 15 ++++++++++++++ 5 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index bf7d6639..b55fbeac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules/**/* *.swp +.idea/ .DS_Store *.log .cache diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..7c2feb7e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.formatOnSave": false +} diff --git a/src/number_format.js b/src/number_format.js index ca8695ed..5d9b9b6c 100644 --- a/src/number_format.js +++ b/src/number_format.js @@ -540,7 +540,6 @@ class NumberFormat extends React.Component { // if value is not defined return empty string if (isNonNumericFalsy && !allowEmptyFormatting) return ''; - if (typeof value === 'number') { value = value.toString(); isNumericString = true; diff --git a/src/utils.js b/src/utils.js index 842b4918..c655ae9e 100644 --- a/src/utils.js +++ b/src/utils.js @@ -87,8 +87,9 @@ export function roundToPrecision(numStr: string, scale: number, fixedDecimalScal //if number is empty don't do anything return empty string if (['', '-'].indexOf(numStr) !== -1) return numStr; - const shoudHaveDecimalSeparator = numStr.indexOf('.') !== -1 && scale; - const {beforeDecimal, afterDecimal, hasNagation} = splitDecimal(numStr); + const normalizedNum = getRidOfScientificNotation(numStr); + const shoudHaveDecimalSeparator = normalizedNum.indexOf('.') !== -1 && scale; + const {beforeDecimal, afterDecimal, hasNagation} = splitDecimal(normalizedNum); const roundedDecimalParts = parseFloat(`0.${afterDecimal || '0'}`).toFixed(scale).split('.'); const intPart = beforeDecimal.split('').reverse().reduce((roundedStr, current, idx) => { if (roundedStr.length > idx) { @@ -103,6 +104,32 @@ export function roundToPrecision(numStr: string, scale: number, fixedDecimalScal return `${negation}${intPart}${decimalSeparator}${decimalPart}`; } +const SCIENTIFIC_NUMBER_REGEX = /\d+\.?\d*e[\+\-]?\d+/i; + +export function getRidOfScientificNotation(value: string) { + if (!SCIENTIFIC_NUMBER_REGEX.test(value)) { + return value; + } + + // This looks like a scientific notation. We will use parseFloat this time as this is not gonna be a limitation in this case + let number = parseFloat(value); + if (Math.abs(number) < 1.0) { + let e = parseInt(number.toString().split("e-")[1]); + if (e) { + number *= Math.pow(10, e - 1); + return "0." + new Array(e).join("0") + number.toString().substring(2); + } + } else { + let e = parseInt(number.toString().split("+")[1]); + if (e > 20) { + e -= 20; + number /= Math.pow(10, e); + return number + new Array(e + 1).join("0"); + } + } + return number.toString(); +} + export function omit(obj: Object, keyMaps: Object) { const filteredObj = {}; diff --git a/test/library/format_as_text.spec.js b/test/library/format_as_text.spec.js index dce363fc..325ad21e 100644 --- a/test/library/format_as_text.spec.js +++ b/test/library/format_as_text.spec.js @@ -15,6 +15,21 @@ describe('NumberFormat as text', () => { expect(wrapper.find('span').text()).toEqual('4111 1111 1111 1111'); }); + it('should format small number in a scientific notation properly', () => { + const wrapper = shallow(); + expect(wrapper.find("span").text()).toEqual("0.00000041"); + }); + + it('should format really small number properly', () => { + const wrapper = shallow(); + expect(wrapper.find("span").text()).toEqual("0.00000041"); + }); + + it('should format big number in a scientific notation properly', () => { + const wrapper = shallow(); + expect(wrapper.find("span").text()).toEqual("41 000 000 000"); + }); + it('should format as given format when input is string', () => { const wrapper = shallow(); expect(wrapper.find('span').text()).toEqual('4111 1111 1111 1111'); From 77ea545a529d3821a7d6cd3cccd7128e9d3fc90f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20D=C4=85browski?= Date: Sat, 17 Oct 2020 21:11:30 +0200 Subject: [PATCH 2/4] chore: build package chore: do not include dev files in npm package --- .npmignore | 2 ++ dist/react-number-format.es.js | 33 ++++++++++++++++++++++++++++-- dist/react-number-format.js | 33 ++++++++++++++++++++++++++++-- dist/react-number-format.min.js | 2 +- lib/utils.js | 36 +++++++++++++++++++++++++++++++-- 5 files changed, 99 insertions(+), 7 deletions(-) diff --git a/.npmignore b/.npmignore index 4021a391..27b99381 100644 --- a/.npmignore +++ b/.npmignore @@ -22,3 +22,5 @@ src/ test/ tmp/ flow-typed/ +.idea/ +.vscode/ \ No newline at end of file diff --git a/dist/react-number-format.es.js b/dist/react-number-format.es.js index 45950a96..30eacaa8 100644 --- a/dist/react-number-format.es.js +++ b/dist/react-number-format.es.js @@ -257,9 +257,10 @@ function limitToScale(numStr, scale, fixedDecimalScale) { function roundToPrecision(numStr, scale, fixedDecimalScale) { //if number is empty don't do anything return empty string if (['', '-'].indexOf(numStr) !== -1) return numStr; - var shoudHaveDecimalSeparator = numStr.indexOf('.') !== -1 && scale; + var normalizedNum = getRidOfScientificNotation(numStr); + var shoudHaveDecimalSeparator = normalizedNum.indexOf('.') !== -1 && scale; - var _splitDecimal = splitDecimal(numStr), + var _splitDecimal = splitDecimal(normalizedNum), beforeDecimal = _splitDecimal.beforeDecimal, afterDecimal = _splitDecimal.afterDecimal, hasNagation = _splitDecimal.hasNagation; @@ -277,6 +278,34 @@ function roundToPrecision(numStr, scale, fixedDecimalScale) { var decimalSeparator = shoudHaveDecimalSeparator ? '.' : ''; return "".concat(negation).concat(intPart).concat(decimalSeparator).concat(decimalPart); } +var SCIENTIFIC_NUMBER_REGEX = /\d+\.?\d*e[\+\-]?\d+/i; +function getRidOfScientificNotation(value) { + if (!SCIENTIFIC_NUMBER_REGEX.test(value)) { + return value; + } // This looks like a scientific notation. We will use parseFloat this time as this is not gonna be a limitation in this case + + + var number = parseFloat(value); + + if (Math.abs(number) < 1.0) { + var e = parseInt(number.toString().split("e-")[1]); + + if (e) { + number *= Math.pow(10, e - 1); + return "0." + new Array(e).join("0") + number.toString().substring(2); + } + } else { + var _e = parseInt(number.toString().split("+")[1]); + + if (_e > 20) { + _e -= 20; + number /= Math.pow(10, _e); + return number + new Array(_e + 1).join("0"); + } + } + + return number.toString(); +} function omit(obj, keyMaps) { var filteredObj = {}; Object.keys(obj).forEach(function (key) { diff --git a/dist/react-number-format.js b/dist/react-number-format.js index db3ac5bf..06671819 100644 --- a/dist/react-number-format.js +++ b/dist/react-number-format.js @@ -263,9 +263,10 @@ function roundToPrecision(numStr, scale, fixedDecimalScale) { //if number is empty don't do anything return empty string if (['', '-'].indexOf(numStr) !== -1) return numStr; - var shoudHaveDecimalSeparator = numStr.indexOf('.') !== -1 && scale; + var normalizedNum = getRidOfScientificNotation(numStr); + var shoudHaveDecimalSeparator = normalizedNum.indexOf('.') !== -1 && scale; - var _splitDecimal = splitDecimal(numStr), + var _splitDecimal = splitDecimal(normalizedNum), beforeDecimal = _splitDecimal.beforeDecimal, afterDecimal = _splitDecimal.afterDecimal, hasNagation = _splitDecimal.hasNagation; @@ -283,6 +284,34 @@ var decimalSeparator = shoudHaveDecimalSeparator ? '.' : ''; return "".concat(negation).concat(intPart).concat(decimalSeparator).concat(decimalPart); } + var SCIENTIFIC_NUMBER_REGEX = /\d+\.?\d*e[\+\-]?\d+/i; + function getRidOfScientificNotation(value) { + if (!SCIENTIFIC_NUMBER_REGEX.test(value)) { + return value; + } // This looks like a scientific notation. We will use parseFloat this time as this is not gonna be a limitation in this case + + + var number = parseFloat(value); + + if (Math.abs(number) < 1.0) { + var e = parseInt(number.toString().split("e-")[1]); + + if (e) { + number *= Math.pow(10, e - 1); + return "0." + new Array(e).join("0") + number.toString().substring(2); + } + } else { + var _e = parseInt(number.toString().split("+")[1]); + + if (_e > 20) { + _e -= 20; + number /= Math.pow(10, _e); + return number + new Array(_e + 1).join("0"); + } + } + + return number.toString(); + } function omit(obj, keyMaps) { var filteredObj = {}; Object.keys(obj).forEach(function (key) { diff --git a/dist/react-number-format.min.js b/dist/react-number-format.min.js index a1c912a7..fa0492ce 100644 --- a/dist/react-number-format.min.js +++ b/dist/react-number-format.min.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("react")):"function"==typeof define&&define.amd?define(["react"],t):(e=e||self).NumberFormat=t(e.React)}(this,function(h){"use strict";function n(e,t){for(var r=0;rr?(Number(e[0])+Number(t)).toString()+e.substring(1,e.length):t+e},u[0]),c=d(u[1]||"",Math.min(t,i.length),r),f=n?".":"";return"".concat(s?"-":"").concat(l).concat(f).concat(c)}(s,n,a)),u?this.formatNumString(s):this.formatInput(s))}},{key:"formatNegation",value:function(e){var t=0=t.length-o.length||i&&s&&t[e]===u))}},{key:"checkIfFormatGotDeleted",value:function(e,t,r){for(var n=e;nt.length||!r.length||v===y||0===g&&m===t.length||g===b&&m===x)return r;if(this.checkIfFormatGotDeleted(v,y,t)&&(r=t),!a){var w=this.removeFormatting(r),O=D(w,o),P=O.beforeDecimal,k=O.afterDecimal,F=O.addNegation,N=er?(Number(e[0])+Number(t)).toString()+e.substring(1,e.length):t+e},l[0]),c=d(l[1]||"",Math.min(t,s.length),r),p=a?".":"";return"".concat(u?"-":"").concat(f).concat(p).concat(c)}var v=/\d+\.?\d*e[\+\-]?\d+/i;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)}}function y(e,t,r){return Math.min(Math.max(e,t),r)}function S(e){return Math.max(e.selectionStart,e.selectionEnd)}var b={thousandSeparator:t.oneOfType([t.string,t.oneOf([!0])]),decimalSeparator:t.string,allowedDecimalSeparators:t.arrayOf(t.string),thousandsGroupStyle:t.oneOf(["thousand","lakh","wan"]),decimalScale:t.number,fixedDecimalScale:t.bool,displayType:t.oneOf(["input","text"]),prefix:t.string,suffix:t.string,format:t.oneOfType([t.string,t.func]),removeFormatting:t.func,mask:t.oneOfType([t.string,t.arrayOf(t.string)]),value:t.oneOfType([t.number,t.string]),defaultValue:t.oneOfType([t.number,t.string]),isNumericString:t.bool,customInput:t.elementType,allowNegative:t.bool,allowEmptyFormatting:t.bool,allowLeadingZeros:t.bool,onValueChange:t.func,onKeyDown:t.func,onMouseUp:t.func,onChange:t.func,onFocus:t.func,onBlur:t.func,type:t.oneOf(["text","tel","password"]),isAllowed:t.func,renderText:t.func,getInputRef:t.oneOfType([t.func,t.shape({current:t.any})])},x={displayType:"input",decimalSeparator:".",thousandsGroupStyle:"thousand",fixedDecimalScale:!1,prefix:"",suffix:"",allowNegative:!0,allowEmptyFormatting:!1,allowLeadingZeros:!1,isNumericString:!1,type:"text",onValueChange:u,onChange:u,onKeyDown:u,onMouseUp:u,onFocus:u,onBlur:u,isAllowed:function(){return!0}},w=function(){function a(e){var t;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,a),t=function(e,t){return!t||"object"!=typeof t&&"function"!=typeof t?i(e):t}(this,o(a).call(this,e));var r=e.defaultValue;t.validateProps();var n=t.formatValueProp(r);return t.state={value:n,numAsString:t.removeFormatting(n),mounted:!1},t.selectionBeforeInput={selectionStart:0,selectionEnd:0},t.onChange=t.onChange.bind(i(t)),t.onKeyDown=t.onKeyDown.bind(i(t)),t.onMouseUp=t.onMouseUp.bind(i(t)),t.onFocus=t.onFocus.bind(i(t)),t.onBlur=t.onBlur.bind(i(t)),t}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&r(e,t)}(a,h.Component),function(e,t,r){t&&n(e.prototype,t),r&&n(e,r)}(a,[{key:"componentDidMount",value:function(){this.setState({mounted:!0})}},{key:"componentDidUpdate",value:function(e){this.updateValueIfRequired(e)}},{key:"componentWillUnmount",value:function(){clearTimeout(this.focusTimeout)}},{key:"updateValueIfRequired",value:function(e){var t=this.props,r=this.state,n=this.focusedElm,a=r.value,o=r.numAsString,i=void 0===o?"":o;if(e!==t){this.validateProps();var s=this.formatNumString(i),u=m(t.value)?s:this.formatValueProp(),l=this.removeFormatting(u),f=parseFloat(l),c=parseFloat(i);(isNaN(f)&&isNaN(c)||f===c)&&s===a&&(null!==n||u===a)||this.updateValue({formattedValue:u,numAsString:l,input:n})}}},{key:"getFloatString",value:function(e){var t=0=t.length-o.length||i&&s&&t[e]===u))}},{key:"checkIfFormatGotDeleted",value:function(e,t,r){for(var n=e;nt.length||!r.length||v===y||0===g&&m===t.length||g===b&&m===x)return r;if(this.checkIfFormatGotDeleted(v,y,t)&&(r=t),!a){var w=this.removeFormatting(r),O=D(w,o),P=O.beforeDecimal,k=O.afterDecimal,F=O.addNegation,N=e 20) { + _e -= 20; + number /= Math.pow(10, _e); + return number + new Array(_e + 1).join("0"); + } + } + + return number.toString(); +} + function omit(obj, keyMaps) { var filteredObj = {}; Object.keys(obj).forEach(function (key) { From 64243482368c090269e7319936f6ff19f2a0b954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20D=C4=85browski?= Date: Sat, 13 Feb 2021 17:44:12 +0100 Subject: [PATCH 3/4] chore: ignore dev files from package --- .npmignore | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.npmignore b/.npmignore index 27b99381..e7b3dd09 100644 --- a/.npmignore +++ b/.npmignore @@ -23,4 +23,9 @@ test/ tmp/ flow-typed/ .idea/ -.vscode/ \ No newline at end of file +.vscode/ +.github/ +example/ +_config.yml +*.spec.tsx +typings/tsconfig.json \ No newline at end of file From 74015f447077ee6712b0123516513fc8794b047c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20D=C4=85browski?= Date: Sat, 13 Feb 2021 17:48:38 +0100 Subject: [PATCH 4/4] chore: linted files --- src/utils.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils.js b/src/utils.js index c655ae9e..e455c9b4 100644 --- a/src/utils.js +++ b/src/utils.js @@ -104,7 +104,7 @@ export function roundToPrecision(numStr: string, scale: number, fixedDecimalScal return `${negation}${intPart}${decimalSeparator}${decimalPart}`; } -const SCIENTIFIC_NUMBER_REGEX = /\d+\.?\d*e[\+\-]?\d+/i; +const SCIENTIFIC_NUMBER_REGEX = /\d+\.?\d*e[+-]?\d+/i; export function getRidOfScientificNotation(value: string) { if (!SCIENTIFIC_NUMBER_REGEX.test(value)) { @@ -114,13 +114,13 @@ export function getRidOfScientificNotation(value: string) { // This looks like a scientific notation. We will use parseFloat this time as this is not gonna be a limitation in this case let number = parseFloat(value); if (Math.abs(number) < 1.0) { - let e = parseInt(number.toString().split("e-")[1]); + const e = parseInt(number.toString().split("e-")[1], 10); if (e) { number *= Math.pow(10, e - 1); return "0." + new Array(e).join("0") + number.toString().substring(2); } } else { - let e = parseInt(number.toString().split("+")[1]); + let e = parseInt(number.toString().split("+")[1], 10); if (e > 20) { e -= 20; number /= Math.pow(10, e);