Skip to content

Commit 370c39d

Browse files
author
Jérémie Yardin
committed
Added support for eager autocompletion
1 parent 04a0eda commit 370c39d

File tree

2 files changed

+73
-15
lines changed

2 files changed

+73
-15
lines changed

lib/mask_text_input_formatter.dart

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ import 'package:flutter/material.dart';
44
import 'package:flutter/services.dart';
55

66
class MaskTextInputFormatter implements TextInputFormatter {
7+
/// Set the autocompletion behavior:
8+
/// - [MaskAutoCompletion.lazy] (default): autocomplete unfiltered characters once the following filtered character is input.
9+
/// For example, with the mask "#/#" and the sequence of characters "1" then "2", the formatter will output "1", then "1/2"
10+
/// - [MaskAutoCompletion.eager]: autocomplete unfiltered characters when the previous filtered character is input.
11+
/// For example, with the mask "#/#" and the sequence of characters "1" then "2", the formatter will output "1/", then "1/2"
12+
final MaskAutoCompletion autoCompletion;
713

814
String? _mask;
915
List<String> _maskChars = [];
@@ -23,7 +29,8 @@ class MaskTextInputFormatter implements TextInputFormatter {
2329
MaskTextInputFormatter({
2430
String? mask,
2531
Map<String, RegExp>? filter,
26-
String? initialText
32+
String? initialText,
33+
this.autoCompletion = MaskAutoCompletion.lazy,
2734
}) {
2835
updateMask(mask: mask, filter: filter ?? {"#": RegExp(r'[0-9]'), "A": RegExp(r'[^0-9]')});
2936
if (initialText != null) {
@@ -32,7 +39,7 @@ class MaskTextInputFormatter implements TextInputFormatter {
3239
}
3340

3441
/// Change the mask
35-
TextEditingValue updateMask({ String? mask, Map<String, RegExp>? filter}) {
42+
TextEditingValue updateMask({String? mask, Map<String, RegExp>? filter}) {
3643
_mask = mask;
3744
if (filter != null) {
3845
_updateFilter(filter);
@@ -74,17 +81,17 @@ class MaskTextInputFormatter implements TextInputFormatter {
7481

7582
/// Mask some text
7683
String maskText(String text) {
77-
return MaskTextInputFormatter(mask: _mask, filter: _maskFilter, initialText: text).getMaskedText();
84+
return MaskTextInputFormatter(mask: _mask, filter: _maskFilter, initialText: text, autoCompletion: autoCompletion).getMaskedText();
7885
}
7986

8087
/// Unmask some text
8188
String unmaskText(String text) {
82-
return MaskTextInputFormatter(mask: _mask, filter: _maskFilter, initialText: text).getUnmaskedText();
89+
return MaskTextInputFormatter(mask: _mask, filter: _maskFilter, initialText: text, autoCompletion: autoCompletion).getUnmaskedText();
8390
}
8491

8592
@override
8693
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
87-
if (_lastResValue == oldValue && newValue == _lastNewValue) {
94+
if (_lastResValue == oldValue && newValue == _lastNewValue && autoCompletion != MaskAutoCompletion.eager) {
8895
return oldValue;
8996
}
9097
if (oldValue.text.isEmpty) {
@@ -105,12 +112,21 @@ class MaskTextInputFormatter implements TextInputFormatter {
105112

106113
final String beforeText = oldValue.text;
107114
final String afterText = newValue.text;
115+
final bool isDeletion = afterText.length < beforeText.length;
108116

109117
final TextSelection beforeSelection = oldValue.selection;
110118
final TextSelection afterSelection = newValue.selection;
111119

112-
final int beforeSelectionStart = afterSelection.isValid ? beforeSelection.isValid ? beforeSelection.start : 0 : 0;
113-
final int beforeSelectionLength = afterSelection.isValid ? beforeSelection.isValid ? beforeSelection.end - beforeSelection.start : 0 : oldValue.text.length;
120+
final int beforeSelectionStart = afterSelection.isValid
121+
? beforeSelection.isValid
122+
? beforeSelection.start
123+
: 0
124+
: 0;
125+
final int beforeSelectionLength = afterSelection.isValid
126+
? beforeSelection.isValid
127+
? beforeSelection.end - beforeSelection.start
128+
: 0
129+
: oldValue.text.length;
114130

115131
final int lengthDifference = afterText.length - (beforeText.length - beforeSelectionLength);
116132
final int lengthRemoved = lengthDifference < 0 ? lengthDifference.abs() : 0;
@@ -152,7 +168,7 @@ class MaskTextInputFormatter implements TextInputFormatter {
152168
targetCursorPosition += replacementText.length;
153169
}
154170

155-
if (beforeResultTextLength == 0 && _resultTextArray.length > 1) {
171+
if (beforeResultTextLength == 0 && _resultTextArray.length > 1) {
156172
for (var i = 0; i < mask.length; i++) {
157173
if (_maskChars.contains(mask[i])) {
158174
final resultPrefix = _resultTextArray._symbolArray.take(i).toList();
@@ -207,7 +223,7 @@ class MaskTextInputFormatter implements TextInputFormatter {
207223
cursorPos = maskPos;
208224
}
209225

210-
if (!curTextInRange) {
226+
if (_mustEndMaskIteration(curTextInRange, isMaskChar)) {
211227
break;
212228
} else {
213229
_resultTextMasked += mask[maskPos];
@@ -220,8 +236,17 @@ class MaskTextInputFormatter implements TextInputFormatter {
220236
}
221237

222238
if (nonMaskedCount > 0) {
223-
_resultTextMasked = _resultTextMasked.substring(0, _resultTextMasked.length - nonMaskedCount);
224-
cursorPos -= nonMaskedCount;
239+
switch (autoCompletion) {
240+
case MaskAutoCompletion.eager:
241+
if (!isDeletion) {
242+
cursorPos += nonMaskedCount;
243+
}
244+
break;
245+
case MaskAutoCompletion.lazy:
246+
_resultTextMasked = _resultTextMasked.substring(0, _resultTextMasked.length - nonMaskedCount);
247+
cursorPos -= nonMaskedCount;
248+
break;
249+
}
225250
}
226251

227252
if (_resultTextArray.length > _maskLength) {
@@ -236,11 +261,20 @@ class MaskTextInputFormatter implements TextInputFormatter {
236261
baseOffset: finalCursorPosition,
237262
extentOffset: finalCursorPosition,
238263
affinity: newValue.selection.affinity,
239-
isDirectional: newValue.selection.isDirectional
240-
)
264+
isDirectional: newValue.selection.isDirectional,
265+
),
241266
);
242267
}
243268

269+
bool _mustEndMaskIteration(bool curTextInRange, bool isMaskChar) {
270+
switch (autoCompletion) {
271+
case MaskAutoCompletion.lazy:
272+
return !curTextInRange;
273+
case MaskAutoCompletion.eager:
274+
return isMaskChar;
275+
}
276+
}
277+
244278
void _calcMaskLength() {
245279
_maskLength = 0;
246280
final mask = _mask;
@@ -260,7 +294,6 @@ class MaskTextInputFormatter implements TextInputFormatter {
260294
}
261295

262296
class _TextMatcher {
263-
264297
final List<String> _symbolArray = <String>[];
265298

266299
int get length => _symbolArray.fold(0, (prev, match) => prev + match.length);
@@ -277,7 +310,7 @@ class _TextMatcher {
277310

278311
void removeAt(int index) => _symbolArray.removeAt(index);
279312

280-
String operator[](int index) => _symbolArray[index];
313+
String operator [](int index) => _symbolArray[index];
281314

282315
void clear() => _symbolArray.clear();
283316

@@ -290,5 +323,9 @@ class _TextMatcher {
290323
_symbolArray.add(text[i]);
291324
}
292325
}
326+
}
293327

328+
enum MaskAutoCompletion {
329+
lazy,
330+
eager,
294331
}

test/mask_text_input_formatter_test.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,27 @@ void main() {
240240
expect(maskTextInputFormatter.getMaskedText(), "");
241241
});
242242

243+
test('Eager autocompletion', () {
244+
const String input = '1';
245+
const String mask = '#/#';
246+
final maskTextInputFormatter = MaskTextInputFormatter(mask: mask, autoCompletion: MaskAutoCompletion.eager);
247+
expect(mask, maskTextInputFormatter.getMask());
248+
final masked = maskTextInputFormatter.maskText(input);
249+
expect(masked, '1/');
250+
final unmasked = maskTextInputFormatter.unmaskText(masked);
251+
expect(unmasked, input);
252+
});
253+
254+
test('Lazy autocompletion', () {
255+
const String input = '1';
256+
const String mask = '#/#';
257+
final maskTextInputFormatter = MaskTextInputFormatter(mask: mask, autoCompletion: MaskAutoCompletion.lazy);
258+
expect(mask, maskTextInputFormatter.getMask());
259+
final masked = maskTextInputFormatter.maskText(input);
260+
expect(masked, '1');
261+
final unmasked = maskTextInputFormatter.unmaskText(masked);
262+
expect(unmasked, input);
263+
});
243264
});
244265

245266
}

0 commit comments

Comments
 (0)