diff --git a/lib/src/editor/raw_editor/raw_editor_state_text_input_client_mixin.dart b/lib/src/editor/raw_editor/raw_editor_state_text_input_client_mixin.dart index e35489b75..cb42b2509 100644 --- a/lib/src/editor/raw_editor/raw_editor_state_text_input_client_mixin.dart +++ b/lib/src/editor/raw_editor/raw_editor_state_text_input_client_mixin.dart @@ -1,3 +1,4 @@ +import 'dart:io'; import 'dart:ui' show lerpDouble; import 'package:flutter/cupertino.dart'; @@ -224,6 +225,50 @@ mixin RawEditorStateTextInputClientMixin on EditorState return; } + // On iOS, we need to handle a special case where user input modifies a previous word. + // For example, in Vietnamese keyboard with the Telex input method, + // the word "dinh" becomes "định" when the user types "d". + // iOS handles this by deleting several characters + // from the end of the word to the beginning (plus one extra character) + // and then re-inserting the updated characters. + // + // The issue arises when the word is adjacent to an embedded object (e.g., an embedded block). + // iOS may accidentally remove the embedded object character (\uFFFC) + // during this replacement process. To prevent this, + // we should skip updating the text value + // if the change affects the starting character and is next to an embedded block. + if (Platform.isIOS) { + final lastKnownValue = _lastKnownRemoteTextEditingValue; + + if ( + //verify that lastKnownValue is not null + lastKnownValue != null && + // verify that lastKnownValue selection extentOffset is more than + // 2 characters away from baseOffset + lastKnownValue.selection.extentOffset - + lastKnownValue.selection.baseOffset == + 2 && + // verify that the lastKnownValue text length is more than 2 + // characters away from the value text length + lastKnownValue.text.length - value.text.length == 2 && + value.selection.isCollapsed && + value.selection.affinity == TextAffinity.downstream && + value.selection.isDirectional == false) { + // take the last two characters from the last known value + final deletedText = lastKnownValue.text.substring( + value.selection.baseOffset, + value.selection.baseOffset + 2, + ); + + // if the deleted text starts with the zero-width embed character, + // we don't want to send an update to the native side + // as it will cause the embedded block to be deleted + if (deletedText.startsWith('\uFFFC')) { + return; + } + } + } + final effectiveLastKnownValue = _lastKnownRemoteTextEditingValue!; _lastKnownRemoteTextEditingValue = value; final oldText = effectiveLastKnownValue.text;