formatEditUpdate method

  1. @override
TextEditingValue formatEditUpdate(
  1. TextEditingValue oldValue,
  2. TextEditingValue newValue
)
override

Called when text is being typed or cut/copy/pasted in the EditableText.

You can override the resulting text based on the previous text value and the incoming new text value.

When formatters are chained, oldValue reflects the initial value of TextEditingValue at the beginning of the chain.

Implementation

@override
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
  final mask = _mask;

  if (mask == null || mask.isEmpty == true) {
    _resultTextMasked = newValue.text;
    _resultTextArray.set(newValue.text);
    return newValue;
  }

  if (oldValue.text.isEmpty) {
    _resultTextArray.clear();
  }

  final beforeText = oldValue.text;
  final afterText = newValue.text;

  final beforeSelection = oldValue.selection;
  final afterSelection = newValue.selection;

  var beforeSelectionStart = afterSelection.isValid ? beforeSelection.isValid ? beforeSelection.start : 0 : 0;

  for (var i = 0; i < beforeSelectionStart && i < beforeText.length && i < afterText.length; i++) {
    if (beforeText[i] != afterText[i]) {
      beforeSelectionStart = i;
      break;
    }
  }

  final beforeSelectionLength = afterSelection.isValid ? beforeSelection.isValid ? beforeSelection.end - beforeSelectionStart : 0 : oldValue.text.length;

  final lengthDifference = afterText.length - (beforeText.length - beforeSelectionLength);
  final lengthRemoved = lengthDifference < 0 ? lengthDifference.abs() : 0;
  final lengthAdded = lengthDifference > 0 ? lengthDifference : 0;

  final afterChangeStart = max(0, beforeSelectionStart - lengthRemoved);
  final afterChangeEnd = max(0, afterChangeStart + lengthAdded);

  final beforeReplaceStart = max(0, beforeSelectionStart - lengthRemoved);
  final beforeReplaceLength = beforeSelectionLength + lengthRemoved;

  final beforeResultTextLength = _resultTextArray.length;

  var currentResultTextLength = _resultTextArray.length;
  var currentResultSelectionStart = 0;
  var currentResultSelectionLength = 0;

  for (var i = 0; i < min(beforeReplaceStart + beforeReplaceLength, mask.length); i++) {
    if (_maskChars.contains(mask[i]) && currentResultTextLength > 0) {
      currentResultTextLength -= 1;
      if (i < beforeReplaceStart) {
        currentResultSelectionStart += 1;
      }
      if (i >= beforeReplaceStart) {
        currentResultSelectionLength += 1;
      }
    }
  }

  final replacementText = afterText.substring(afterChangeStart, afterChangeEnd);
  var targetCursorPosition = currentResultSelectionStart;
  if (replacementText.isEmpty) {
    _resultTextArray.removeRange(currentResultSelectionStart, currentResultSelectionStart + currentResultSelectionLength);
  } else {
    if (currentResultSelectionLength > 0) {
      _resultTextArray.removeRange(currentResultSelectionStart, currentResultSelectionStart + currentResultSelectionLength);
      currentResultSelectionLength = 0;
    }
    _resultTextArray.insert(currentResultSelectionStart, replacementText);
    targetCursorPosition += replacementText.length;
  }

  if (beforeResultTextLength == 0 && _resultTextArray.length  > 1) {
    var prefixLength = 0;
    for (var i = 0; i < mask.length; i++) {
      if (_maskChars.contains(mask[i])) {
        prefixLength = i;
        break;
      }
    }
    if (prefixLength > 0) {
      final resultPrefix = _resultTextArray._symbolArray.take(prefixLength).toList();
      final effectivePrefixLength = min(_resultTextArray.length, resultPrefix.length);
      for (var j = 0; j < effectivePrefixLength; j++) {
        if (mask[j] != resultPrefix[j]) {
          _resultTextArray.removeRange(0, j);
          break;
        }
        if (j == effectivePrefixLength - 1) {
          _resultTextArray.removeRange(0, effectivePrefixLength);
          break;
        }
      }
    }
  }

  var curTextPos = 0;
  var maskPos = 0;
  _resultTextMasked = "";
  var cursorPos = -1;
  var nonMaskedCount = 0;
  var maskInside = 0;

  while (maskPos < mask.length) {
    final curMaskChar = mask[maskPos];
    final isMaskChar = _maskChars.contains(curMaskChar);

    var curTextInRange = curTextPos < _resultTextArray.length;

    String? curTextChar;
    if (isMaskChar && curTextInRange) {
      if (maskInside > 0) {
        _resultTextArray.removeRange(curTextPos - maskInside, curTextPos);
        curTextPos -= maskInside;
      }
      maskInside = 0;
      while (curTextChar == null && curTextInRange) {
        final potentialTextChar = _resultTextArray[curTextPos];
        if (_maskFilter?[curMaskChar]?.hasMatch(potentialTextChar) ?? false) {
          curTextChar = potentialTextChar;
        } else {
          _resultTextArray.removeAt(curTextPos);
          curTextInRange = curTextPos < _resultTextArray.length;
          if (curTextPos <= targetCursorPosition) {
            targetCursorPosition -= 1;
          }
        }
      }
    } else if (!isMaskChar && !curTextInRange && type == MaskAutoCompletionType.eager) {
      curTextInRange = true;
    }

    if (isMaskChar && curTextInRange && curTextChar != null) {
      _resultTextMasked += curTextChar;
      if (curTextPos == targetCursorPosition && cursorPos == -1) {
        cursorPos = maskPos - nonMaskedCount;
      }
      nonMaskedCount = 0;
      curTextPos += 1;
    } else {
      if (!curTextInRange) {
        if (maskInside > 0) {
          curTextPos -= maskInside;
          maskInside = 0;
          nonMaskedCount = 0;
          continue;
        } else {
          break;
        }
      } else {
        _resultTextMasked += mask[maskPos];
        if (!isMaskChar && curTextPos < _resultTextArray.length && curMaskChar == _resultTextArray[curTextPos]) {
          maskInside++;
          curTextPos++;
        } else if (maskInside > 0) {
          curTextPos -= maskInside;
          maskInside = 0;
        }
      }

      if (curTextPos == targetCursorPosition && cursorPos == -1 && !curTextInRange) {
        cursorPos = maskPos;
      }

      if (type == MaskAutoCompletionType.lazy || lengthRemoved > 0 || currentResultSelectionLength > 0 || beforeReplaceLength > 0) {
        nonMaskedCount++;
      }
    }

    maskPos++;
  }

  if (nonMaskedCount > 0) {
    _resultTextMasked = _resultTextMasked.substring(0, _resultTextMasked.length - nonMaskedCount);
    cursorPos -= nonMaskedCount;
  }

  if (_resultTextArray.length > _maskLength) {
    _resultTextArray.removeRange(_maskLength, _resultTextArray.length);
  }

  final finalCursorPosition = cursorPos < 0 ? _resultTextMasked.length : cursorPos;

  return TextEditingValue(
    text: _resultTextMasked,
    selection: TextSelection(
      baseOffset: finalCursorPosition,
      extentOffset: finalCursorPosition,
      affinity: newValue.selection.affinity,
      isDirectional: newValue.selection.isDirectional
    )
  );
}