Skip to content

Commit

Permalink
Add Autofill logic
Browse files Browse the repository at this point in the history
  • Loading branch information
Caleb Doucet committed Jan 11, 2021
1 parent 565475b commit c09ee24
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 26 deletions.
24 changes: 12 additions & 12 deletions .size-snapshot.json
Original file line number Diff line number Diff line change
@@ -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
}
}
20 changes: 10 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>",
Expand All @@ -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",
Expand Down
70 changes: 67 additions & 3 deletions src/utils/mask.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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) {
Expand Down
85 changes: 84 additions & 1 deletion tests/input/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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(
<Input mask="9999-9999" defaultValue="123" maskPlaceholder={null} />
);
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(
<Input mask="9999-9999" defaultValue="123" />
);
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(
<Input mask="9999-9999" defaultValue="123" maskPlaceholder="####-####" />
);
await simulateFocus(input);
setCursorPosition(input, 9);
TestUtils.Simulate.change(input);

input.value = "12345678";
setCursorPosition(input, 8);
Expand All @@ -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(
<Input mask="9999-9999" defaultValue="____-____" />
);
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(
<Input mask="9999-9999" defaultValue="1234-5678" />
);
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(
<Input mask="(9999-9999)" defaultValue="(" maskPlaceholder={null} />
);
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(<Input />);
setProps({
Expand Down

0 comments on commit c09ee24

Please sign in to comment.