From c09ee24a606fb39bcbaf58dacf332f876b204256 Mon Sep 17 00:00:00 2001 From: Caleb Doucet Date: Thu, 7 Jan 2021 15:03:10 -0400 Subject: [PATCH] Add Autofill logic --- .size-snapshot.json | 24 ++++++------- package.json | 20 +++++------ src/utils/mask.js | 70 ++++++++++++++++++++++++++++++++++-- tests/input/input.js | 85 +++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 173 insertions(+), 26 deletions(-) diff --git a/.size-snapshot.json b/.size-snapshot.json index 001712b..fa2bde4 100644 --- a/.size-snapshot.json +++ b/.size-snapshot.json @@ -1,22 +1,22 @@ { "dist/react-input-mask.js": { - "bundled": 79669, - "minified": 26008, - "gzipped": 8342 + "bundled": 81871, + "minified": 26472, + "gzipped": 8464 }, "lib/react-input-mask.development.js": { - "bundled": 30278, - "minified": 12895, - "gzipped": 4267 + "bundled": 32398, + "minified": 13359, + "gzipped": 4396 }, "dist/react-input-mask.min.js": { - "bundled": 44160, - "minified": 15279, - "gzipped": 5298 + "bundled": 46362, + "minified": 15743, + "gzipped": 5422 }, "lib/react-input-mask.production.min.js": { - "bundled": 28947, - "minified": 11818, - "gzipped": 3931 + "bundled": 31067, + "minified": 12282, + "gzipped": 4057 } } diff --git a/package.json b/package.json index 38d463b..f9c3a58 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "react-input-mask", "description": "Masked input component for React", - "version": "3.0.0-alpha.2", + "version": "3.0.0-alpha.3", "homepage": "https://github.com/sanniassin/react-input-mask", "license": "MIT", "author": "Nikita Lobachev ", @@ -17,15 +17,15 @@ "react-dom": ">=16.8" }, "devDependencies": { - "@babel/cli": "^7.8.3", - "@babel/core": "^7.8.3", - "@babel/plugin-proposal-class-properties": "^7.8.3", - "@babel/plugin-transform-modules-commonjs": "^7.8.3", - "@babel/plugin-transform-proto-to-assign": "^7.8.3", - "@babel/polyfill": "^7.8.3", - "@babel/preset-env": "^7.8.3", - "@babel/preset-react": "^7.8.3", - "@babel/register": "^7.8.3", + "@babel/cli": "^7.8.7", + "@babel/core": "^7.8.7", + "@babel/plugin-proposal-class-properties": "^7.8.7", + "@babel/plugin-transform-modules-commonjs": "^7.8.7", + "@babel/plugin-transform-proto-to-assign": "^7.8.7", + "@babel/polyfill": "^7.8.7", + "@babel/preset-env": "^7.8.7", + "@babel/preset-react": "^7.8.7", + "@babel/register": "^7.8.7", "babel-eslint": "^10.0.3", "babel-loader": "^8.0.6", "babel-plugin-dev-expression": "^0.2.2", diff --git a/src/utils/mask.js b/src/utils/mask.js index f0a1fa5..24db4f3 100644 --- a/src/utils/mask.js +++ b/src/utils/mask.js @@ -222,17 +222,74 @@ export default class MaskUtils { return value; }; + isAutoFilled = ( + { value, selection }, + { value: previousValue, selection: previousSelection } + ) => { + const { maskPlaceholder } = this.maskOptions; + if ( + // Autocomplete will set the previous selection to the length of the autocompleted value + previousSelection.end < previousValue.length && + selection.end === value.length + ) { + return true; + } + + if ( + selection.length === 0 && + previousSelection.length === 0 && + selection.start < previousSelection.start && + selection.start === value.length + ) { + // When both previous and current state have no selection length, the cursor index is less than it was before + // and the cursor is at the end of the new value + // Check each character to see if there are any changes which is only possible if the value was autocompleted. + return value.split("").some((char, index) => { + return char !== previousValue[index]; + }); + } + + if ( + !maskPlaceholder && + previousSelection.length === 0 && + previousValue.length < value.length + ) { + // If there is no mask placeholder, the selection is 0 and the new value is longer than the previous value + // (characters have been added) + return value.split("").some((char, index) => { + // Check each character before the selection to see if they have changed + if (index < previousSelection.start) { + // Any character before the previous selection that changes will be changed because of autofill + return char !== previousValue[index]; + } + return false; + }); + } + + return false; + }; + processChange = (currentState, previousState) => { const { mask, prefix, lastEditablePosition } = this.maskOptions; const { value, selection } = currentState; - const previousValue = previousState.value; - const previousSelection = previousState.selection; + let previousValue = previousState.value; + let previousSelection = previousState.selection; let newValue = value; let enteredString = ""; let formattedEnteredStringLength = 0; let removedLength = 0; let cursorPosition = Math.min(previousSelection.start, selection.start); + if (this.isAutoFilled(currentState, previousState)) { + // If the value is autocompleted treat it as if the input started empty. + previousValue = prefix; + previousSelection = { + start: 0, + end: 0, + length: 0 + }; + } + if (selection.end > previousSelection.start) { enteredString = newValue.slice(previousSelection.start, selection.end); formattedEnteredStringLength = this.getStringFillingLengthAtPosition( @@ -248,7 +305,14 @@ export default class MaskUtils { removedLength = previousValue.length - newValue.length; } - newValue = previousValue; + if ( + !( + newValue.length === previousValue.length && + selection.end === previousSelection.start + ) + ) { + newValue = previousValue; + } if (removedLength) { if (removedLength === 1 && !previousSelection.length) { diff --git a/tests/input/input.js b/tests/input/input.js index 9b85f0f..de28e74 100644 --- a/tests/input/input.js +++ b/tests/input/input.js @@ -563,6 +563,8 @@ describe("react-input-mask", () => { await simulateBackspacePress(input); expect(input.value).to.equal("+7 (495) 156 45 4"); + await setCursorPosition(input, 17); + input.value = "+7 ("; setCursorPosition(input, 4); TestUtils.Simulate.change(input); @@ -1162,11 +1164,43 @@ describe("react-input-mask", () => { expect(input.value).to.equal("1234"); }); - it("should handle autofill", async () => { + it("should handle autofill with no maskPlaceholder", async () => { const { input } = createInput( ); await simulateFocus(input); + setCursorPosition(input, 3); + TestUtils.Simulate.change(input); + + input.value = "12345678"; + setCursorPosition(input, 8); + TestUtils.Simulate.change(input); + + expect(input.value).to.equal("1234-5678"); + }); + + it("should handle autofill with default maskPlaceholder", async () => { + const { input } = createInput( + + ); + await simulateFocus(input); + setCursorPosition(input, 9); + TestUtils.Simulate.change(input); + + input.value = "12345678"; + setCursorPosition(input, 8); + TestUtils.Simulate.change(input); + + expect(input.value).to.equal("1234-5678"); + }); + + it("should handle autofill with full length maskPlaceholder", async () => { + const { input } = createInput( + + ); + await simulateFocus(input); + setCursorPosition(input, 9); + TestUtils.Simulate.change(input); input.value = "12345678"; setCursorPosition(input, 8); @@ -1175,6 +1209,55 @@ describe("react-input-mask", () => { expect(input.value).to.equal("1234-5678"); }); + it("should handle autofill a fully masked value", async () => { + // This handles a case where chrome will autofill this field then move to another auto fill field and then come back + // and fill this field again. + const { input } = createInput( + + ); + await simulateFocus(input); + setCursorPosition(input, 9); + TestUtils.Simulate.change(input); + + input.value = "12345678"; + setCursorPosition(input, 8); + TestUtils.Simulate.change(input); + + expect(input.value).to.equal("1234-5678"); + }); + + it("should handle autofill an existing value", async () => { + // This handles a case where chrome will autofill this field then move to another auto fill field and then come back + // and fill this field again. + const { input } = createInput( + + ); + await simulateFocus(input); + + input.value = "12345678"; + setCursorPosition(input, 8); + TestUtils.Simulate.change(input); + + expect(input.value).to.equal("1234-5678"); + }); + + it("should handle autofill when there is a prefix and no mask placeholder", async () => { + const { input } = createInput( + + ); + await simulateFocus(input); + + setCursorPosition(input, 1); + TestUtils.Simulate.change(input); + expect(input.value).to.equal("("); + + input.value = "12345678"; + setCursorPosition(input, 8); + TestUtils.Simulate.change(input); + + expect(input.value).to.equal("(1234-5678)"); + }); + it("should handle transition between masked and non-masked state", async () => { const { input, setProps } = createInput(); setProps({