diff --git a/apps/mobile/modules/t3-markdown-text/android/src/main/java/expo/modules/t3markdowntext/T3MarkdownTextSelectionModule.kt b/apps/mobile/modules/t3-markdown-text/android/src/main/java/expo/modules/t3markdowntext/T3MarkdownTextSelectionModule.kt index af8675831f2d2753660b924f7b1840d36e574ab5..8e9fa3894aa9dcc75edb7c14a2b0609d6388c6b9 100644 --- a/apps/mobile/modules/t3-markdown-text/android/src/main/java/expo/modules/t3markdowntext/T3MarkdownTextSelectionModule.kt +++ b/apps/mobile/modules/t3-markdown-text/android/src/main/java/expo/modules/t3markdowntext/T3MarkdownTextSelectionModule.kt @@ -3,6 +3,8 @@ package expo.modules.t3markdowntext import android.content.ClipData import android.content.ClipboardManager import android.content.Context +import android.text.Spannable +import android.text.SpannableStringBuilder import android.text.Spanned import android.text.style.ReplacementSpan import android.view.ActionMode @@ -18,6 +20,14 @@ import kotlin.math.min private const val OBJECT_REPLACEMENT_CHARACTER = "\uFFFC" +// Match React Native's measurement buffer. Android orders tied line-height +// spans differently in SpannableString, shifting inline images once RN's +// span priorities are exhausted. +private object MarkdownSpannableFactory : Spannable.Factory() { + override fun newSpannable(source: CharSequence): Spannable = + SpannableStringBuilder(source) +} + private fun copyTextWithoutInlineImages( text: CharSequence, start: Int, @@ -85,6 +95,7 @@ class T3MarkdownTextSelectionModule : Module() { if (currentCallback is SanitizingSelectionActionModeCallback) { return@runOnUiQueueThread } + textView.setSpannableFactory(MarkdownSpannableFactory) textView.customSelectionActionModeCallback = SanitizingSelectionActionModeCallback(textView, currentCallback) }