@@ -20,6 +20,7 @@ import '../services.dart';
2020import '../text/paragraph.dart' ;
2121import '../util.dart' ;
2222import 'autofill_hint.dart' ;
23+ import 'composition_aware_mixin.dart' ;
2324import 'input_type.dart' ;
2425import 'text_capitalization.dart' ;
2526
@@ -508,7 +509,6 @@ class TextEditingDeltaState {
508509 final bool isCurrentlyComposing = newTextEditingDeltaState.composingOffset != null && newTextEditingDeltaState.composingOffset != newTextEditingDeltaState.composingExtent;
509510 if (newTextEditingDeltaState.deltaText.isNotEmpty && previousSelectionWasCollapsed && isCurrentlyComposing) {
510511 newTextEditingDeltaState.deltaStart = newTextEditingDeltaState.composingOffset! ;
511- newTextEditingDeltaState.deltaEnd = newTextEditingDeltaState.composingExtent! ;
512512 }
513513
514514 final bool isDeltaRangeEmpty = newTextEditingDeltaState.deltaStart == - 1 && newTextEditingDeltaState.deltaStart == newTextEditingDeltaState.deltaEnd;
@@ -618,6 +618,8 @@ class TextEditingDeltaState {
618618 'deltaEnd' : deltaEnd,
619619 'selectionBase' : baseOffset,
620620 'selectionExtent' : extentOffset,
621+ 'composingBase' : composingOffset,
622+ 'composingExtent' : composingExtent
621623 },
622624 ],
623625 };
@@ -647,7 +649,13 @@ class TextEditingDeltaState {
647649
648650/// The current text and selection state of a text field.
649651class EditingState {
650- EditingState ({this .text, int ? baseOffset, int ? extentOffset}) :
652+ EditingState ({
653+ this .text,
654+ int ? baseOffset,
655+ int ? extentOffset,
656+ this .composingBaseOffset,
657+ this .composingExtentOffset
658+ }) :
651659 // Don't allow negative numbers. Pick the smallest selection index for base.
652660 baseOffset = math.max (0 , math.min (baseOffset ?? 0 , extentOffset ?? 0 )),
653661 // Don't allow negative numbers. Pick the greatest selection index for extent.
@@ -674,14 +682,20 @@ class EditingState {
674682 /// valid selection range for input DOM elements.
675683 factory EditingState .fromFrameworkMessage (
676684 Map <String , dynamic > flutterEditingState) {
685+ final String ? text = flutterEditingState.tryString ('text' );
686+
677687 final int selectionBase = flutterEditingState.readInt ('selectionBase' );
678688 final int selectionExtent = flutterEditingState.readInt ('selectionExtent' );
679- final String ? text = flutterEditingState.tryString ('text' );
689+
690+ final int ? composingBase = flutterEditingState.tryInt ('composingBase' );
691+ final int ? composingExtent = flutterEditingState.tryInt ('composingExtent' );
680692
681693 return EditingState (
682694 text: text,
683695 baseOffset: selectionBase,
684696 extentOffset: selectionExtent,
697+ composingBaseOffset: composingBase,
698+ composingExtentOffset: composingExtent
685699 );
686700 }
687701
@@ -708,13 +722,31 @@ class EditingState {
708722 }
709723 }
710724
725+ EditingState copyWith ({
726+ String ? text,
727+ int ? baseOffset,
728+ int ? extentOffset,
729+ int ? composingBaseOffset,
730+ int ? composingExtentOffset,
731+ }) {
732+ return EditingState (
733+ text: text ?? this .text,
734+ baseOffset: baseOffset ?? this .baseOffset,
735+ extentOffset: extentOffset ?? this .extentOffset,
736+ composingBaseOffset: composingBaseOffset ?? this .composingBaseOffset,
737+ composingExtentOffset: composingExtentOffset ?? this .composingExtentOffset,
738+ );
739+ }
740+
711741 /// The counterpart of [EditingState.fromFrameworkMessage] . It generates a Map that
712742 /// can be sent to Flutter.
713743 // TODO(mdebbar): Should we get `selectionAffinity` and other properties from flutter's editing state?
714744 Map <String , dynamic > toFlutter () => < String , dynamic > {
715745 'text' : text,
716746 'selectionBase' : baseOffset,
717747 'selectionExtent' : extentOffset,
748+ 'composingBase' : composingBaseOffset,
749+ 'composingExtent' : composingExtentOffset,
718750 };
719751
720752 /// The current text being edited.
@@ -726,11 +758,19 @@ class EditingState {
726758 /// The offset at which the text selection terminates.
727759 final int ? extentOffset;
728760
761+ /// The offset at which [CompositionAwareMixin.composingText] begins, if any.
762+ final int ? composingBaseOffset;
763+
764+ /// The offset at which [CompositionAwareMixin.composingText] terminates, if any.
765+ final int ? composingExtentOffset;
766+
729767 /// Whether the current editing state is valid or not.
730768 bool get isValid => baseOffset! >= 0 && extentOffset! >= 0 ;
731769
732770 @override
733- int get hashCode => Object .hash (text, baseOffset, extentOffset);
771+ int get hashCode => Object .hash (
772+ text, baseOffset, extentOffset, composingBaseOffset, composingExtentOffset
773+ );
734774
735775 @override
736776 bool operator == (Object other) {
@@ -743,13 +783,15 @@ class EditingState {
743783 return other is EditingState &&
744784 other.text == text &&
745785 other.baseOffset == baseOffset &&
746- other.extentOffset == extentOffset;
786+ other.extentOffset == extentOffset &&
787+ other.composingBaseOffset == composingBaseOffset &&
788+ other.composingExtentOffset == composingExtentOffset;
747789 }
748790
749791 @override
750792 String toString () {
751793 return assertionsEnabled
752- ? 'EditingState("$text ", base:$baseOffset , extent:$extentOffset )'
794+ ? 'EditingState("$text ", base:$baseOffset , extent:$extentOffset , composingBase:$ composingBaseOffset , composingExtent:$ composingExtentOffset )'
753795 : super .toString ();
754796 }
755797
@@ -1038,7 +1080,7 @@ class SafariDesktopTextEditingStrategy extends DefaultTextEditingStrategy {
10381080///
10391081/// Unless a formfactor/browser requires specific implementation for a specific
10401082/// strategy the methods in this class should be used.
1041- abstract class DefaultTextEditingStrategy implements TextEditingStrategy {
1083+ abstract class DefaultTextEditingStrategy with CompositionAwareMixin implements TextEditingStrategy {
10421084 final HybridTextEditing owner;
10431085
10441086 DefaultTextEditingStrategy (this .owner);
@@ -1169,7 +1211,7 @@ abstract class DefaultTextEditingStrategy implements TextEditingStrategy {
11691211
11701212 activeDomElement.addEventListener ('beforeinput' , handleBeforeInput);
11711213
1172- activeDomElement. addEventListener ( 'compositionupdate' , handleCompositionUpdate );
1214+ addCompositionEventHandlers (activeDomElement );
11731215
11741216 // Refocus on the activeDomElement after blur, so that user can keep editing the
11751217 // text field.
@@ -1210,6 +1252,8 @@ abstract class DefaultTextEditingStrategy implements TextEditingStrategy {
12101252 subscriptions[i].cancel ();
12111253 }
12121254 subscriptions.clear ();
1255+ removeCompositionEventHandlers (activeDomElement);
1256+
12131257 // If focused element is a part of a form, it needs to stay on the DOM
12141258 // until the autofill context of the form is finalized.
12151259 // More details on `TextInput.finishAutofillContext` call.
@@ -1246,9 +1290,13 @@ abstract class DefaultTextEditingStrategy implements TextEditingStrategy {
12461290 void handleChange (html.Event event) {
12471291 assert (isEnabled);
12481292
1249- final EditingState newEditingState = EditingState .fromDomElement (activeDomElement);
1293+ EditingState newEditingState = EditingState .fromDomElement (activeDomElement);
1294+ newEditingState = determineCompositionState (newEditingState);
1295+
12501296 TextEditingDeltaState ? newTextEditingDeltaState;
12511297 if (inputConfiguration.enableDeltaModel) {
1298+ editingDeltaState.composingOffset = newEditingState.composingBaseOffset;
1299+ editingDeltaState.composingExtent = newEditingState.composingExtentOffset;
12521300 newTextEditingDeltaState = TextEditingDeltaState .inferDeltaState (newEditingState, lastEditingState, editingDeltaState);
12531301 }
12541302
@@ -1295,12 +1343,6 @@ abstract class DefaultTextEditingStrategy implements TextEditingStrategy {
12951343 }
12961344 }
12971345
1298- void handleCompositionUpdate (html.Event event) {
1299- final EditingState newEditingState = EditingState .fromDomElement (activeDomElement);
1300- editingDeltaState.composingOffset = newEditingState.baseOffset! ;
1301- editingDeltaState.composingExtent = newEditingState.extentOffset! ;
1302- }
1303-
13041346 void maybeSendAction (html.Event event) {
13051347 if (event is html.KeyboardEvent && event.keyCode == _kReturnKeyCode) {
13061348 onAction !(inputConfiguration.inputAction);
@@ -1450,7 +1492,7 @@ class IOSTextEditingStrategy extends GloballyPositionedTextEditingStrategy {
14501492
14511493 activeDomElement.addEventListener ('beforeinput' , handleBeforeInput);
14521494
1453- activeDomElement. addEventListener ( 'compositionupdate' , handleCompositionUpdate );
1495+ addCompositionEventHandlers (activeDomElement );
14541496
14551497 // Position the DOM element after it is focused.
14561498 subscriptions.add (activeDomElement.onFocus.listen ((_) {
@@ -1594,7 +1636,7 @@ class AndroidTextEditingStrategy extends GloballyPositionedTextEditingStrategy {
15941636
15951637 activeDomElement.addEventListener ('beforeinput' , handleBeforeInput);
15961638
1597- activeDomElement. addEventListener ( 'compositionupdate' , handleCompositionUpdate );
1639+ addCompositionEventHandlers (activeDomElement );
15981640
15991641 subscriptions.add (activeDomElement.onBlur.listen ((_) {
16001642 if (windowHasFocus) {
@@ -1650,7 +1692,7 @@ class FirefoxTextEditingStrategy extends GloballyPositionedTextEditingStrategy {
16501692
16511693 activeDomElement.addEventListener ('beforeinput' , handleBeforeInput);
16521694
1653- activeDomElement. addEventListener ( 'compositionupdate' , handleCompositionUpdate );
1695+ addCompositionEventHandlers (activeDomElement );
16541696
16551697 // Detects changes in text selection.
16561698 //
0 commit comments