Skip to content

Add Autofill logic #247

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 <sanniassin@gmail.com>",
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