@@ -623,10 +623,13 @@ public void maybeSetText(ReactTextUpdate reactTextUpdate) {
623
623
if (reactTextUpdate .getText ().length () == 0 ) {
624
624
setText (null );
625
625
} else {
626
- // When we update text, we trigger onChangeText code that will
627
- // try to update state if the wrapper is available. Temporarily disable
628
- // to prevent an infinite loop.
626
+ boolean shouldKeepCompositeSpan = length () == spannableStringBuilder .length ();
627
+
629
628
getText ().replace (0 , length (), spannableStringBuilder );
629
+
630
+ if (shouldKeepCompositeSpan ) {
631
+ attachCompositeSpansToTextFrom (spannableStringBuilder );
632
+ }
630
633
}
631
634
mDisableTextDiffing = false ;
632
635
@@ -641,10 +644,13 @@ public void maybeSetText(ReactTextUpdate reactTextUpdate) {
641
644
}
642
645
643
646
/**
644
- * Remove and/or add {@link Spanned. SPAN_EXCLUSIVE_EXCLUSIVE} spans, since they should only exist
645
- * as long as the text they cover is the same. All other spans will remain the same, since they
646
- * will adapt to the new text, hence why {@link SpannableStringBuilder#replace} never removes
647
+ * Remove and/or add {@link Spanned# SPAN_EXCLUSIVE_EXCLUSIVE} spans, since they should only exist
648
+ * as long as the text they cover is the same unless they are {@link Spanned#SPAN_COMPOSING}.
649
+ * All other spans will remain the same, since they will adapt to the new text, hence why {@link SpannableStringBuilder#replace} never removes
647
650
* them.
651
+ * Keep copy of {@link Spanned#SPAN_COMPOSING} Spans in {@param spannableStringBuilder}, because they are important for
652
+ * keyboard suggestions. Without keeping these Spans, suggestions default to be put after the current selection position,
653
+ * possibly resulting in letter duplication.
648
654
*/
649
655
private void manageSpans (SpannableStringBuilder spannableStringBuilder ) {
650
656
Object [] spans = getText ().getSpans (0 , length (), Object .class );
@@ -653,6 +659,8 @@ private void manageSpans(SpannableStringBuilder spannableStringBuilder) {
653
659
int spanFlags = getText ().getSpanFlags (span );
654
660
boolean isExclusiveExclusive =
655
661
(spanFlags & Spanned .SPAN_EXCLUSIVE_EXCLUSIVE ) == Spanned .SPAN_EXCLUSIVE_EXCLUSIVE ;
662
+ boolean isComposing =
663
+ (spanFlags & Spanned .SPAN_COMPOSING ) == Spanned .SPAN_COMPOSING ;
656
664
657
665
// Remove all styling spans we might have previously set
658
666
if (span instanceof ReactSpan ) {
@@ -667,6 +675,12 @@ private void manageSpans(SpannableStringBuilder spannableStringBuilder) {
667
675
final int spanStart = getText ().getSpanStart (span );
668
676
final int spanEnd = getText ().getSpanEnd (span );
669
677
678
+ // We keep a copy of Composing spans
679
+ if (isComposing ) {
680
+ spannableStringBuilder .setSpan (span , spanStart , spanEnd , spanFlags );
681
+ continue ;
682
+ }
683
+
670
684
// Make sure the span is removed from existing text, otherwise the spans we set will be
671
685
// ignored or it will cover text that has changed.
672
686
getText ().removeSpan (span );
@@ -841,6 +855,33 @@ private void addSpansFromStyleAttributes(SpannableStringBuilder workingText) {
841
855
}
842
856
}
843
857
858
+ /**
859
+ * Attaches the {@link Spanned#SPAN_COMPOSING} from {@param spannableStringBuilder} to {@link ReactEditText#getText}
860
+ *
861
+ * See {@link ReactEditText#manageSpans} for more details.
862
+ * Also https://github.com/facebook/react-native/issues/11068
863
+ */
864
+ private void attachCompositeSpansToTextFrom (SpannableStringBuilder spannableStringBuilder ) {
865
+ Editable text = getText ();
866
+ if (text == null ) {
867
+ return ;
868
+ }
869
+ Object [] spans = spannableStringBuilder .getSpans (0 , length (), Object .class );
870
+ for (Object span : spans ) {
871
+ int spanFlags = spannableStringBuilder .getSpanFlags (span );
872
+ boolean isComposing = (spanFlags & Spanned .SPAN_COMPOSING ) == Spanned .SPAN_COMPOSING ;
873
+
874
+ if (!isComposing ) {
875
+ continue ;
876
+ }
877
+
878
+ final int spanStart = spannableStringBuilder .getSpanStart (span );
879
+ final int spanEnd = spannableStringBuilder .getSpanEnd (span );
880
+
881
+ text .setSpan (span , spanStart , spanEnd , spanFlags );
882
+ }
883
+ }
884
+
844
885
private static boolean sameTextForSpan (
845
886
final Editable oldText ,
846
887
final SpannableStringBuilder newText ,
0 commit comments