Skip to content

Commit c09ee24

Browse files
author
Caleb Doucet
committed
Add Autofill logic
1 parent 565475b commit c09ee24

File tree

4 files changed

+173
-26
lines changed

4 files changed

+173
-26
lines changed

.size-snapshot.json

+12-12
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
{
22
"dist/react-input-mask.js": {
3-
"bundled": 79669,
4-
"minified": 26008,
5-
"gzipped": 8342
3+
"bundled": 81871,
4+
"minified": 26472,
5+
"gzipped": 8464
66
},
77
"lib/react-input-mask.development.js": {
8-
"bundled": 30278,
9-
"minified": 12895,
10-
"gzipped": 4267
8+
"bundled": 32398,
9+
"minified": 13359,
10+
"gzipped": 4396
1111
},
1212
"dist/react-input-mask.min.js": {
13-
"bundled": 44160,
14-
"minified": 15279,
15-
"gzipped": 5298
13+
"bundled": 46362,
14+
"minified": 15743,
15+
"gzipped": 5422
1616
},
1717
"lib/react-input-mask.production.min.js": {
18-
"bundled": 28947,
19-
"minified": 11818,
20-
"gzipped": 3931
18+
"bundled": 31067,
19+
"minified": 12282,
20+
"gzipped": 4057
2121
}
2222
}

package.json

+10-10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "react-input-mask",
33
"description": "Masked input component for React",
4-
"version": "3.0.0-alpha.2",
4+
"version": "3.0.0-alpha.3",
55
"homepage": "https://github.com/sanniassin/react-input-mask",
66
"license": "MIT",
77
"author": "Nikita Lobachev <sanniassin@gmail.com>",
@@ -17,15 +17,15 @@
1717
"react-dom": ">=16.8"
1818
},
1919
"devDependencies": {
20-
"@babel/cli": "^7.8.3",
21-
"@babel/core": "^7.8.3",
22-
"@babel/plugin-proposal-class-properties": "^7.8.3",
23-
"@babel/plugin-transform-modules-commonjs": "^7.8.3",
24-
"@babel/plugin-transform-proto-to-assign": "^7.8.3",
25-
"@babel/polyfill": "^7.8.3",
26-
"@babel/preset-env": "^7.8.3",
27-
"@babel/preset-react": "^7.8.3",
28-
"@babel/register": "^7.8.3",
20+
"@babel/cli": "^7.8.7",
21+
"@babel/core": "^7.8.7",
22+
"@babel/plugin-proposal-class-properties": "^7.8.7",
23+
"@babel/plugin-transform-modules-commonjs": "^7.8.7",
24+
"@babel/plugin-transform-proto-to-assign": "^7.8.7",
25+
"@babel/polyfill": "^7.8.7",
26+
"@babel/preset-env": "^7.8.7",
27+
"@babel/preset-react": "^7.8.7",
28+
"@babel/register": "^7.8.7",
2929
"babel-eslint": "^10.0.3",
3030
"babel-loader": "^8.0.6",
3131
"babel-plugin-dev-expression": "^0.2.2",

src/utils/mask.js

+67-3
Original file line numberDiff line numberDiff line change
@@ -222,17 +222,74 @@ export default class MaskUtils {
222222
return value;
223223
};
224224

225+
isAutoFilled = (
226+
{ value, selection },
227+
{ value: previousValue, selection: previousSelection }
228+
) => {
229+
const { maskPlaceholder } = this.maskOptions;
230+
if (
231+
// Autocomplete will set the previous selection to the length of the autocompleted value
232+
previousSelection.end < previousValue.length &&
233+
selection.end === value.length
234+
) {
235+
return true;
236+
}
237+
238+
if (
239+
selection.length === 0 &&
240+
previousSelection.length === 0 &&
241+
selection.start < previousSelection.start &&
242+
selection.start === value.length
243+
) {
244+
// When both previous and current state have no selection length, the cursor index is less than it was before
245+
// and the cursor is at the end of the new value
246+
// Check each character to see if there are any changes which is only possible if the value was autocompleted.
247+
return value.split("").some((char, index) => {
248+
return char !== previousValue[index];
249+
});
250+
}
251+
252+
if (
253+
!maskPlaceholder &&
254+
previousSelection.length === 0 &&
255+
previousValue.length < value.length
256+
) {
257+
// If there is no mask placeholder, the selection is 0 and the new value is longer than the previous value
258+
// (characters have been added)
259+
return value.split("").some((char, index) => {
260+
// Check each character before the selection to see if they have changed
261+
if (index < previousSelection.start) {
262+
// Any character before the previous selection that changes will be changed because of autofill
263+
return char !== previousValue[index];
264+
}
265+
return false;
266+
});
267+
}
268+
269+
return false;
270+
};
271+
225272
processChange = (currentState, previousState) => {
226273
const { mask, prefix, lastEditablePosition } = this.maskOptions;
227274
const { value, selection } = currentState;
228-
const previousValue = previousState.value;
229-
const previousSelection = previousState.selection;
275+
let previousValue = previousState.value;
276+
let previousSelection = previousState.selection;
230277
let newValue = value;
231278
let enteredString = "";
232279
let formattedEnteredStringLength = 0;
233280
let removedLength = 0;
234281
let cursorPosition = Math.min(previousSelection.start, selection.start);
235282

283+
if (this.isAutoFilled(currentState, previousState)) {
284+
// If the value is autocompleted treat it as if the input started empty.
285+
previousValue = prefix;
286+
previousSelection = {
287+
start: 0,
288+
end: 0,
289+
length: 0
290+
};
291+
}
292+
236293
if (selection.end > previousSelection.start) {
237294
enteredString = newValue.slice(previousSelection.start, selection.end);
238295
formattedEnteredStringLength = this.getStringFillingLengthAtPosition(
@@ -248,7 +305,14 @@ export default class MaskUtils {
248305
removedLength = previousValue.length - newValue.length;
249306
}
250307

251-
newValue = previousValue;
308+
if (
309+
!(
310+
newValue.length === previousValue.length &&
311+
selection.end === previousSelection.start
312+
)
313+
) {
314+
newValue = previousValue;
315+
}
252316

253317
if (removedLength) {
254318
if (removedLength === 1 && !previousSelection.length) {

tests/input/input.js

+84-1
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,8 @@ describe("react-input-mask", () => {
563563
await simulateBackspacePress(input);
564564
expect(input.value).to.equal("+7 (495) 156 45 4");
565565

566+
await setCursorPosition(input, 17);
567+
566568
input.value = "+7 (";
567569
setCursorPosition(input, 4);
568570
TestUtils.Simulate.change(input);
@@ -1162,11 +1164,43 @@ describe("react-input-mask", () => {
11621164
expect(input.value).to.equal("1234");
11631165
});
11641166

1165-
it("should handle autofill", async () => {
1167+
it("should handle autofill with no maskPlaceholder", async () => {
11661168
const { input } = createInput(
11671169
<Input mask="9999-9999" defaultValue="123" maskPlaceholder={null} />
11681170
);
11691171
await simulateFocus(input);
1172+
setCursorPosition(input, 3);
1173+
TestUtils.Simulate.change(input);
1174+
1175+
input.value = "12345678";
1176+
setCursorPosition(input, 8);
1177+
TestUtils.Simulate.change(input);
1178+
1179+
expect(input.value).to.equal("1234-5678");
1180+
});
1181+
1182+
it("should handle autofill with default maskPlaceholder", async () => {
1183+
const { input } = createInput(
1184+
<Input mask="9999-9999" defaultValue="123" />
1185+
);
1186+
await simulateFocus(input);
1187+
setCursorPosition(input, 9);
1188+
TestUtils.Simulate.change(input);
1189+
1190+
input.value = "12345678";
1191+
setCursorPosition(input, 8);
1192+
TestUtils.Simulate.change(input);
1193+
1194+
expect(input.value).to.equal("1234-5678");
1195+
});
1196+
1197+
it("should handle autofill with full length maskPlaceholder", async () => {
1198+
const { input } = createInput(
1199+
<Input mask="9999-9999" defaultValue="123" maskPlaceholder="####-####" />
1200+
);
1201+
await simulateFocus(input);
1202+
setCursorPosition(input, 9);
1203+
TestUtils.Simulate.change(input);
11701204

11711205
input.value = "12345678";
11721206
setCursorPosition(input, 8);
@@ -1175,6 +1209,55 @@ describe("react-input-mask", () => {
11751209
expect(input.value).to.equal("1234-5678");
11761210
});
11771211

1212+
it("should handle autofill a fully masked value", async () => {
1213+
// This handles a case where chrome will autofill this field then move to another auto fill field and then come back
1214+
// and fill this field again.
1215+
const { input } = createInput(
1216+
<Input mask="9999-9999" defaultValue="____-____" />
1217+
);
1218+
await simulateFocus(input);
1219+
setCursorPosition(input, 9);
1220+
TestUtils.Simulate.change(input);
1221+
1222+
input.value = "12345678";
1223+
setCursorPosition(input, 8);
1224+
TestUtils.Simulate.change(input);
1225+
1226+
expect(input.value).to.equal("1234-5678");
1227+
});
1228+
1229+
it("should handle autofill an existing value", async () => {
1230+
// This handles a case where chrome will autofill this field then move to another auto fill field and then come back
1231+
// and fill this field again.
1232+
const { input } = createInput(
1233+
<Input mask="9999-9999" defaultValue="1234-5678" />
1234+
);
1235+
await simulateFocus(input);
1236+
1237+
input.value = "12345678";
1238+
setCursorPosition(input, 8);
1239+
TestUtils.Simulate.change(input);
1240+
1241+
expect(input.value).to.equal("1234-5678");
1242+
});
1243+
1244+
it("should handle autofill when there is a prefix and no mask placeholder", async () => {
1245+
const { input } = createInput(
1246+
<Input mask="(9999-9999)" defaultValue="(" maskPlaceholder={null} />
1247+
);
1248+
await simulateFocus(input);
1249+
1250+
setCursorPosition(input, 1);
1251+
TestUtils.Simulate.change(input);
1252+
expect(input.value).to.equal("(");
1253+
1254+
input.value = "12345678";
1255+
setCursorPosition(input, 8);
1256+
TestUtils.Simulate.change(input);
1257+
1258+
expect(input.value).to.equal("(1234-5678)");
1259+
});
1260+
11781261
it("should handle transition between masked and non-masked state", async () => {
11791262
const { input, setProps } = createInput(<Input />);
11801263
setProps({

0 commit comments

Comments
 (0)