@openeditor/react-native-prose-editor 0.0.8 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -408,7 +408,7 @@ private object NativeEditorOutsideTapDispatcher {
408
408
  private fun scheduleOutsideTapCandidate(view: NativeEditorExpoView, event: MotionEvent) {
409
409
  val editorRect = Rect()
410
410
  val editorRectOnDown = if (
411
- view.richTextView.editorEditText.getGlobalVisibleRect(editorRect) &&
411
+ view.richTextView.getGlobalVisibleRect(editorRect) &&
412
412
  !editorRect.isEmpty
413
413
  ) {
414
414
  editorRect
@@ -466,7 +466,7 @@ private object NativeEditorOutsideTapDispatcher {
466
466
  ): Boolean {
467
467
  val editorRectOnDown = candidate.editorRectOnDown ?: return false
468
468
  val currentRect = Rect()
469
- if (!view.richTextView.editorEditText.getGlobalVisibleRect(currentRect)) {
469
+ if (!view.richTextView.getGlobalVisibleRect(currentRect)) {
470
470
  return true
471
471
  }
472
472
  val dx = currentRect.left - editorRectOnDown.left
@@ -582,7 +582,7 @@ private object NativeEditorOutsideTapDispatcher {
582
582
  class NativeEditorExpoView(
583
583
  context: Context,
584
584
  appContext: AppContext
585
- ) : ExpoView(context, appContext), EditorEditText.EditorListener {
585
+ ) : ExpoView(context, appContext) {
586
586
 
587
587
  private enum class ToolbarPlacement {
588
588
  KEYBOARD,
@@ -725,7 +725,8 @@ class NativeEditorExpoView(
725
725
 
726
726
  init {
727
727
  addView(richTextView, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
728
- richTextView.editorEditText.editorListener = this
728
+ richTextView.onSelectionChange = ::onSelectionChanged
729
+ richTextView.onEditorUpdate = ::onEditorUpdate
729
730
  richTextView.onBeforeDetachedFromWindow = {
730
731
  prepareForDetachFromWindow()
731
732
  }
@@ -743,8 +744,7 @@ class NativeEditorExpoView(
743
744
  insets
744
745
  }
745
746
 
746
- // Observe EditText focus changes.
747
- richTextView.editorEditText.setOnFocusChangeListener { _, hasFocus ->
747
+ richTextView.onFocusChange = focusChange@{ hasFocus ->
748
748
  if (hasFocus) {
749
749
  cancelPendingToolbarRefocus()
750
750
  installOutsideTapBlurHandlerIfNeeded()
@@ -753,7 +753,7 @@ class NativeEditorExpoView(
753
753
  } else {
754
754
  if (consumeToolbarFocusPreservationForBlur()) {
755
755
  scheduleToolbarRefocus()
756
- return@setOnFocusChangeListener
756
+ return@focusChange
757
757
  }
758
758
  uninstallOutsideTapBlurHandler()
759
759
  clearMentionQueryState()
@@ -777,7 +777,7 @@ class NativeEditorExpoView(
777
777
  if (previousEditorId != id) {
778
778
  invalidateAutoGrowContentHeightEmission()
779
779
  }
780
- if (previousEditorId == id && richTextView.editorEditText.editorId == id) {
780
+ if (previousEditorId == id) {
781
781
  if (id != 0L && isAttachedToNativeWindow) {
782
782
  if (!NativeEditorViewRegistry.register(id, this)) {
783
783
  handleEditorDestroyed(id)
@@ -918,7 +918,7 @@ class NativeEditorExpoView(
918
918
  clearPendingNativeActionRetry()
919
919
  lastAddonsJson = addonsJson
920
920
  addons = NativeEditorAddons.fromJson(addonsJson)
921
- keyboardToolbarView.applyMentionTheme(richTextView.editorEditText.theme?.mentions ?: addons.mentions?.theme)
921
+ keyboardToolbarView.applyMentionTheme(richTextView.editorTheme?.mentions ?: addons.mentions?.theme)
922
922
  refreshMentionQuery()
923
923
  }
924
924
 
@@ -942,19 +942,19 @@ class NativeEditorExpoView(
942
942
  }
943
943
 
944
944
  fun setAutoCapitalize(autoCapitalize: String?) {
945
- richTextView.editorEditText.setAutoCapitalize(autoCapitalize)
945
+ richTextView.setAutoCapitalize(autoCapitalize)
946
946
  }
947
947
 
948
948
  fun setAutoCorrect(autoCorrect: Boolean?) {
949
- richTextView.editorEditText.setAutoCorrect(autoCorrect)
949
+ richTextView.setAutoCorrect(autoCorrect)
950
950
  }
951
951
 
952
952
  fun setKeyboardType(keyboardType: String?) {
953
- richTextView.editorEditText.setKeyboardType(keyboardType)
953
+ richTextView.setKeyboardType(keyboardType)
954
954
  }
955
955
 
956
956
  fun setEditable(editable: Boolean) {
957
- if (richTextView.editorEditText.isEditable == editable) return
957
+ if (richTextView.editorIsEditable == editable) return
958
958
  if (!editable) {
959
959
  cancelPendingToolbarRefocus()
960
960
  clearPendingNativeActionRetry()
@@ -1117,7 +1117,7 @@ class NativeEditorExpoView(
1117
1117
  if (hasPendingEditorResetUpdateForCurrentEditor()) return
1118
1118
  if (hasPendingEditorUpdateForCurrentEditor()) return
1119
1119
  if (!isAttachedToNativeWindow) return
1120
- if (richTextView.editorEditText.editorId != richTextView.editorId) return
1120
+ if (richTextView.editorId == 0L) return
1121
1121
  refreshToolbarStateFromEditorSelection()
1122
1122
  refreshMentionQuery()
1123
1123
  emitEditorReadyIfNeeded()
@@ -1259,10 +1259,9 @@ class NativeEditorExpoView(
1259
1259
  pastFastRetryBudget &&
1260
1260
  !pendingEditorUpdateForcedRecoveryAttempted &&
1261
1261
  richTextView.editorId != 0L &&
1262
- richTextView.editorEditText.editorId == richTextView.editorId
1262
+ richTextView.editorId != 0L
1263
1263
  ) {
1264
1264
  pendingEditorUpdateForcedRecoveryAttempted = true
1265
- richTextView.editorEditText.discardTransientNativeInputForExternalRecovery()
1266
1265
  }
1267
1266
  if (!pastFastRetryBudget) {
1268
1267
  pendingEditorUpdateRetryAttempts += 1
@@ -1313,7 +1312,7 @@ class NativeEditorExpoView(
1313
1312
  }
1314
1313
  if (
1315
1314
  blockThemePreflightForTesting ||
1316
- !richTextView.editorEditText.prepareForExternalEditorUpdate()
1315
+ richTextView.hasActiveComposition()
1317
1316
  ) {
1318
1317
  schedulePendingThemeRetry()
1319
1318
  return
@@ -1444,7 +1443,7 @@ class NativeEditorExpoView(
1444
1443
  }
1445
1444
 
1446
1445
  private fun currentNativeActionScope(action: PendingNativeAction): PendingNativeActionScope {
1447
- val selection = richTextView.editorEditText.currentScalarSelection()
1446
+ val selection = richTextView.currentScalarSelection()
1448
1447
  val mentionScope = when (action) {
1449
1448
  is PendingNativeAction.MentionSuggestionSelect ->
1450
1449
  mentionQueryState ?: addons.mentions?.let { currentMentionQueryState(it.trigger) }
@@ -1477,7 +1476,7 @@ class NativeEditorExpoView(
1477
1476
  ) {
1478
1477
  return false
1479
1478
  }
1480
- val selection = richTextView.editorEditText.currentScalarSelection()
1479
+ val selection = richTextView.currentScalarSelection()
1481
1480
  if (scope.selectionAnchor != selection?.first || scope.selectionHead != selection?.second) {
1482
1481
  return false
1483
1482
  }
@@ -1505,7 +1504,7 @@ class NativeEditorExpoView(
1505
1504
  }
1506
1505
 
1507
1506
  private fun isEditorEffectivelyFocusedForNativeAction(): Boolean =
1508
- richTextView.editorEditText.hasFocus() ||
1507
+ richTextView.isEditorFocused ||
1509
1508
  (pendingToolbarRefocus != null && pendingToolbarRefocusEditorId == richTextView.editorId)
1510
1509
 
1511
1510
  private fun clearPendingNativeActionRetryIfScopeChanged() {
@@ -1636,11 +1635,10 @@ class NativeEditorExpoView(
1636
1635
  }
1637
1636
  cancelPendingKeyboardDismiss()
1638
1637
  cancelPendingBlurRetry()
1639
- richTextView.editorEditText.requestFocus()
1640
- richTextView.editorEditText.post {
1638
+ richTextView.focusEditor()
1639
+ richTextView.post {
1641
1640
  if (!canFocusCurrentEditor()) return@post
1642
- val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
1643
- imm?.showSoftInput(richTextView.editorEditText, InputMethodManager.SHOW_IMPLICIT)
1641
+ richTextView.focusEditor()
1644
1642
  }
1645
1643
  }
1646
1644
 
@@ -1654,13 +1652,12 @@ class NativeEditorExpoView(
1654
1652
 
1655
1653
  private fun performBlur(deferKeyboardDismiss: Boolean, allowRetry: Boolean) {
1656
1654
  if (handleDestroyedCurrentEditorIfNeeded()) return
1657
- if (!richTextView.editorEditText.prepareForExternalEditorUpdate()) {
1655
+ if (richTextView.hasActiveComposition()) {
1658
1656
  if (allowRetry && pendingBlurRetryAttempts < MAX_PENDING_UPDATE_RETRY_ATTEMPTS) {
1659
1657
  schedulePendingBlurRetry(deferKeyboardDismiss)
1660
1658
  return
1661
1659
  }
1662
1660
  if (handleDestroyedCurrentEditorIfNeeded()) return
1663
- richTextView.editorEditText.restoreAuthorizedTextIfNeeded()
1664
1661
  }
1665
1662
  completeBlur(deferKeyboardDismiss)
1666
1663
  }
@@ -1668,24 +1665,24 @@ class NativeEditorExpoView(
1668
1665
  private fun completeBlur(deferKeyboardDismiss: Boolean) {
1669
1666
  cancelPendingBlurRetry()
1670
1667
  traceOutsideTap(
1671
- "complete blur deferKeyboardDismiss=$deferKeyboardDismiss focusedBefore=${richTextView.editorEditText.hasFocus()}"
1668
+ "complete blur deferKeyboardDismiss=$deferKeyboardDismiss focusedBefore=${richTextView.isEditorFocused}"
1672
1669
  )
1673
- richTextView.editorEditText.clearFocus()
1674
- traceOutsideTap("complete blur focusedAfter=${richTextView.editorEditText.hasFocus()}")
1670
+ richTextView.blurEditor()
1671
+ traceOutsideTap("complete blur focusedAfter=${richTextView.isEditorFocused}")
1675
1672
  if (deferKeyboardDismiss) {
1676
1673
  val dismiss = Runnable {
1677
1674
  pendingKeyboardDismiss = null
1678
- if (!richTextView.editorEditText.hasFocus()) {
1675
+ if (!richTextView.isEditorFocused) {
1679
1676
  val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
1680
- imm?.hideSoftInputFromWindow(richTextView.editorEditText.windowToken, 0)
1677
+ imm?.hideSoftInputFromWindow(richTextView.windowToken, 0)
1681
1678
  }
1682
1679
  }
1683
1680
  pendingKeyboardDismiss = dismiss
1684
- richTextView.editorEditText.post(dismiss)
1681
+ richTextView.post(dismiss)
1685
1682
  return
1686
1683
  }
1687
1684
  val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
1688
- imm?.hideSoftInputFromWindow(richTextView.editorEditText.windowToken, 0)
1685
+ imm?.hideSoftInputFromWindow(richTextView.windowToken, 0)
1689
1686
  }
1690
1687
 
1691
1688
  private fun schedulePendingBlurRetry(deferKeyboardDismiss: Boolean) {
@@ -1734,12 +1731,12 @@ class NativeEditorExpoView(
1734
1731
  focusInternal(cancelPendingOutsideTapBlur = false)
1735
1732
  }
1736
1733
  pendingToolbarRefocus = refocus
1737
- richTextView.editorEditText.post(refocus)
1734
+ richTextView.post(refocus)
1738
1735
  }
1739
1736
 
1740
1737
  private fun cancelPendingToolbarRefocus() {
1741
1738
  pendingToolbarRefocus?.let {
1742
- richTextView.editorEditText.removeCallbacks(it)
1739
+ richTextView.removeCallbacks(it)
1743
1740
  pendingToolbarRefocus = null
1744
1741
  }
1745
1742
  pendingToolbarRefocusEditorId = null
@@ -1748,29 +1745,29 @@ class NativeEditorExpoView(
1748
1745
 
1749
1746
  private fun scheduleOutsideTapBlur() {
1750
1747
  cancelPendingOutsideTapBlur()
1751
- traceOutsideTap("schedule outside blur focused=${richTextView.editorEditText.hasFocus()}")
1748
+ traceOutsideTap("schedule outside blur focused=${richTextView.isEditorFocused}")
1752
1749
  val blur = Runnable {
1753
1750
  pendingOutsideTapBlur = null
1754
- traceOutsideTap("run outside blur focused=${richTextView.editorEditText.hasFocus()}")
1755
- if (richTextView.editorEditText.hasFocus()) {
1751
+ traceOutsideTap("run outside blur focused=${richTextView.isEditorFocused}")
1752
+ if (richTextView.isEditorFocused) {
1756
1753
  blurWithDeferredKeyboardDismiss()
1757
1754
  }
1758
1755
  }
1759
1756
  pendingOutsideTapBlur = blur
1760
- richTextView.editorEditText.postDelayed(blur, OUTSIDE_TAP_BLUR_DELAY_MS)
1757
+ richTextView.postDelayed(blur, OUTSIDE_TAP_BLUR_DELAY_MS)
1761
1758
  }
1762
1759
 
1763
1760
  private fun cancelPendingOutsideTapBlur() {
1764
1761
  pendingOutsideTapBlur?.let {
1765
1762
  traceOutsideTap("cancel outside blur")
1766
- richTextView.editorEditText.removeCallbacks(it)
1763
+ richTextView.removeCallbacks(it)
1767
1764
  pendingOutsideTapBlur = null
1768
1765
  }
1769
1766
  }
1770
1767
 
1771
1768
  private fun cancelPendingKeyboardDismiss() {
1772
1769
  pendingKeyboardDismiss?.let {
1773
- richTextView.editorEditText.removeCallbacks(it)
1770
+ richTextView.removeCallbacks(it)
1774
1771
  pendingKeyboardDismiss = null
1775
1772
  }
1776
1773
  }
@@ -1805,7 +1802,7 @@ class NativeEditorExpoView(
1805
1802
  }
1806
1803
 
1807
1804
  internal fun handleEditorDestroyed(editorId: Long) {
1808
- if (richTextView.editorId != editorId && richTextView.editorEditText.editorId != editorId) {
1805
+ if (richTextView.editorId != editorId) {
1809
1806
  return
1810
1807
  }
1811
1808
  cancelPendingEditorUpdateRetry()
@@ -1822,12 +1819,9 @@ class NativeEditorExpoView(
1822
1819
  uninstallOutsideTapBlurHandler()
1823
1820
  detachKeyboardToolbarIfNeeded()
1824
1821
  richTextView.setViewportBottomInsetPx(0)
1825
- val editText = richTextView.editorEditText
1826
- if (editText.hasFocus()) {
1827
- editText.clearFocus()
1828
- }
1822
+ richTextView.blurEditor()
1829
1823
  val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
1830
- imm?.hideSoftInputFromWindow(editText.windowToken, 0)
1824
+ imm?.hideSoftInputFromWindow(richTextView.windowToken, 0)
1831
1825
  clearMentionQueryState(resetLastEvent = true)
1832
1826
  pendingEditorUpdateJson = null
1833
1827
  pendingEditorUpdateEditorId = null
@@ -1849,7 +1843,7 @@ class NativeEditorExpoView(
1849
1843
 
1850
1844
  private fun handleDestroyedCurrentEditorIfNeeded(): Boolean {
1851
1845
  val editorId = richTextView.editorId.takeIf { it != 0L }
1852
- ?: richTextView.editorEditText.editorId.takeIf { it != 0L }
1846
+ ?: richTextView.editorId.takeIf { it != 0L }
1853
1847
  ?: return false
1854
1848
  if (!NativeEditorViewRegistry.isDestroyed(editorId)) return false
1855
1849
  handleEditorDestroyed(editorId)
@@ -1888,7 +1882,7 @@ class NativeEditorExpoView(
1888
1882
  val editorId = richTextView.editorId
1889
1883
  if (editorId == 0L) return false
1890
1884
  if (!isAttachedToNativeWindow) return false
1891
- if (richTextView.editorEditText.editorId != editorId) return false
1885
+ if (richTextView.editorId != editorId) return false
1892
1886
  if (hasPendingEditorResetUpdateForCurrentEditor()) return false
1893
1887
  if (hasPendingEditorUpdateForCurrentEditor()) return false
1894
1888
  lastReadyEditorId = editorId
@@ -1913,8 +1907,8 @@ class NativeEditorExpoView(
1913
1907
  private fun prepareForDetachFromWindow() {
1914
1908
  if (handleDestroyedCurrentEditorIfNeeded()) return
1915
1909
  val editorId = richTextView.editorId
1916
- if (editorId == 0L || richTextView.editorEditText.editorId == 0L) return
1917
- if (richTextView.editorEditText.prepareForExternalEditorUpdate()) {
1910
+ if (editorId == 0L) return
1911
+ if (!richTextView.hasActiveComposition()) {
1918
1912
  cancelPendingDetachPreflightRetry()
1919
1913
  richTextView.clearDeferredEditorUnbind()
1920
1914
  return
@@ -1927,7 +1921,6 @@ class NativeEditorExpoView(
1927
1921
  if (pendingDetachPreflightRetryScheduled) return
1928
1922
  if (pendingDetachPreflightRetryAttempts >= MAX_PENDING_UPDATE_RETRY_ATTEMPTS) {
1929
1923
  if (handleDestroyedCurrentEditorIfNeeded()) return
1930
- richTextView.editorEditText.restoreAuthorizedTextIfNeeded()
1931
1924
  cancelPendingDetachPreflightRetry()
1932
1925
  richTextView.unbindEditorForDetachedViewIfNeeded()
1933
1926
  return
@@ -1946,7 +1939,7 @@ class NativeEditorExpoView(
1946
1939
  return@postDelayed
1947
1940
  }
1948
1941
  if (handleDestroyedCurrentEditorIfNeeded()) return@postDelayed
1949
- if (richTextView.editorEditText.prepareForExternalEditorUpdate()) {
1942
+ if (!richTextView.hasActiveComposition()) {
1950
1943
  cancelPendingDetachPreflightRetry()
1951
1944
  richTextView.unbindEditorForDetachedViewIfNeeded()
1952
1945
  return@postDelayed
@@ -2015,49 +2008,7 @@ class NativeEditorExpoView(
2015
2008
 
2016
2009
  private fun emitContentHeightIfNeeded(force: Boolean) {
2017
2010
  if (heightBehavior != EditorHeightBehavior.AUTO_GROW) return
2018
- val editText = richTextView.editorEditText
2019
- val resolvedEditHeight = editText.resolveAutoGrowHeight()
2020
- val resolvedContainerHeight =
2021
- resolvedEditHeight +
2022
- richTextView.paddingTop +
2023
- richTextView.paddingBottom +
2024
- paddingTop +
2025
- paddingBottom
2026
- val contentHeight = (
2027
- when {
2028
- editText.isLaidOut && (editText.layout?.height ?: 0) > 0 -> {
2029
- maxOf(
2030
- (editText.layout?.height ?: 0) +
2031
- editText.compoundPaddingTop +
2032
- editText.compoundPaddingBottom +
2033
- richTextView.paddingTop +
2034
- richTextView.paddingBottom +
2035
- paddingTop +
2036
- paddingBottom,
2037
- resolvedContainerHeight
2038
- )
2039
- }
2040
- richTextView.measuredHeight > 0 -> {
2041
- maxOf(
2042
- richTextView.measuredHeight + paddingTop + paddingBottom,
2043
- resolvedContainerHeight
2044
- )
2045
- }
2046
- editText.measuredHeight > 0 -> {
2047
- maxOf(
2048
- editText.measuredHeight +
2049
- richTextView.paddingTop +
2050
- richTextView.paddingBottom +
2051
- paddingTop +
2052
- paddingBottom,
2053
- resolvedContainerHeight
2054
- )
2055
- }
2056
- else -> {
2057
- resolvedContainerHeight
2058
- }
2059
- }
2060
- ).coerceAtLeast(0)
2011
+ val contentHeight = (richTextView.measuredHeight + paddingTop + paddingBottom).coerceAtLeast(0)
2061
2012
  if (contentHeight <= 0) return
2062
2013
  val editorId = richTextView.editorId
2063
2014
  if (
@@ -2103,10 +2054,7 @@ class NativeEditorExpoView(
2103
2054
  clearPendingViewCommandUpdateRetry()
2104
2055
  isApplyingJSUpdate = true
2105
2056
  val applied = try {
2106
- richTextView.editorEditText.applyUpdateJSON(
2107
- updateJson,
2108
- refreshInputConnectionForExternalUpdate = true
2109
- )
2057
+ richTextView.applyEditorUpdate(updateJson)
2110
2058
  clearPendingEditorUpdateDispatchQueue("jsResetUpdate")
2111
2059
  true
2112
2060
  } catch (error: Throwable) {
@@ -2123,7 +2071,7 @@ class NativeEditorExpoView(
2123
2071
 
2124
2072
  private fun isEditorReadyForNativeUpdate(): Boolean {
2125
2073
  val editorId = richTextView.editorId
2126
- return editorId == 0L || (isAttachedToNativeWindow && richTextView.editorEditText.editorId == editorId)
2074
+ return editorId == 0L || (isAttachedToNativeWindow && richTextView.editorId == editorId)
2127
2075
  }
2128
2076
 
2129
2077
  private fun applyEditorUpdate(
@@ -2156,7 +2104,7 @@ class NativeEditorExpoView(
2156
2104
  }
2157
2105
  if (
2158
2106
  blockEditorUpdatePreflightForTesting ||
2159
- !richTextView.editorEditText.prepareForExternalEditorUpdate()
2107
+ richTextView.hasActiveComposition()
2160
2108
  ) {
2161
2109
  if (scheduleViewCommandRetry) {
2162
2110
  scheduleViewCommandUpdateRetry(updateJson)
@@ -2165,10 +2113,7 @@ class NativeEditorExpoView(
2165
2113
  }
2166
2114
  isApplyingJSUpdate = true
2167
2115
  return try {
2168
- richTextView.editorEditText.applyUpdateJSON(
2169
- updateJson,
2170
- refreshInputConnectionForExternalUpdate = true
2171
- )
2116
+ richTextView.applyEditorUpdate(updateJson)
2172
2117
  clearPendingEditorUpdateDispatchQueue("jsUpdate")
2173
2118
  true
2174
2119
  } catch (error: Throwable) {
@@ -2201,7 +2146,7 @@ class NativeEditorExpoView(
2201
2146
  blockedReason = "detached"
2202
2147
  )
2203
2148
  }
2204
- if (richTextView.editorId != 0L && richTextView.editorEditText.editorId != richTextView.editorId) {
2149
+ if (richTextView.editorId == 0L) {
2205
2150
  return NativeEditorViewRegistry.commandPreparationJSON(
2206
2151
  ready = false,
2207
2152
  blockedReason = "detached"
@@ -2213,18 +2158,17 @@ class NativeEditorExpoView(
2213
2158
  isApplyingJSUpdate = true
2214
2159
  return try {
2215
2160
  onBeforePrepareForEditorCommandForTesting?.invoke()
2216
- val preparation = richTextView.editorEditText.prepareForExternalEditorCommand()
2217
2161
  NativeEditorViewRegistry.commandPreparationJSON(
2218
- ready = preparation.ready,
2219
- updateJSON = preparation.updateJSON,
2220
- blockedReason = if (preparation.ready) null else "composition"
2162
+ ready = !richTextView.hasActiveComposition(),
2163
+ updateJSON = editorGetSelectionState(richTextView.editorId.toULong()),
2164
+ blockedReason = if (richTextView.hasActiveComposition()) "composition" else null
2221
2165
  )
2222
2166
  } finally {
2223
2167
  isApplyingJSUpdate = false
2224
2168
  }
2225
2169
  }
2226
2170
 
2227
- override fun onSelectionChanged(anchor: Int, head: Int) {
2171
+ private fun onSelectionChanged(anchor: Int, head: Int) {
2228
2172
  val stateJson = refreshToolbarStateFromEditorSelection()
2229
2173
  refreshMentionQuery()
2230
2174
  clearPendingNativeActionRetryIfScopeChanged()
@@ -2244,7 +2188,7 @@ class NativeEditorExpoView(
2244
2188
  onSelectionChangeForTesting?.invoke(event) ?: onSelectionChange(event)
2245
2189
  }
2246
2190
 
2247
- override fun onEditorUpdate(updateJSON: String) {
2191
+ private fun onEditorUpdate(updateJSON: String) {
2248
2192
  if (isApplyingJSUpdate) {
2249
2193
  dispatchEditorUpdate(
2250
2194
  PendingEditorUpdateEvent(
@@ -2261,10 +2205,6 @@ class NativeEditorExpoView(
2261
2205
  updateJSON = updateJSON
2262
2206
  )
2263
2207
  )
2264
- richTextView.editorEditText.recordImeTraceForTesting(
2265
- "nativeViewEditorUpdateQueued",
2266
- "queue=${pendingEditorUpdateEvents.size} jsonLength=${updateJSON.length}"
2267
- )
2268
2208
  schedulePendingEditorUpdateDispatch()
2269
2209
  }
2270
2210
 
@@ -2288,19 +2228,11 @@ class NativeEditorExpoView(
2288
2228
  while (pendingEditorUpdateEvents.isNotEmpty()) {
2289
2229
  val event = pendingEditorUpdateEvents.removeFirst()
2290
2230
  if (event.editorId != richTextView.editorId) {
2291
- richTextView.editorEditText.recordImeTraceForTesting(
2292
- "nativeViewEditorUpdateSkipped",
2293
- "reason=staleEditor queuedEditor=${event.editorId} currentEditor=${richTextView.editorId}"
2294
- )
2295
2231
  continue
2296
2232
  }
2297
2233
  dispatchEditorUpdate(event, emitToJS = true)
2298
2234
  drainedCount += 1
2299
2235
  }
2300
- richTextView.editorEditText.recordImeTraceForTesting(
2301
- "nativeViewEditorUpdateDrained",
2302
- "count=$drainedCount totalUs=${nanosToMicros(System.nanoTime() - startedAt)}"
2303
- )
2304
2236
  }
2305
2237
 
2306
2238
  private fun clearPendingEditorUpdateDispatchQueue(reason: String) {
@@ -2309,10 +2241,6 @@ class NativeEditorExpoView(
2309
2241
  pendingEditorUpdateEvents.clear()
2310
2242
  pendingEditorUpdateDispatchScheduled = false
2311
2243
  pendingEditorUpdateDispatchGeneration += 1
2312
- richTextView.editorEditText.recordImeTraceForTesting(
2313
- "nativeViewEditorUpdateQueueCleared",
2314
- "reason=$reason count=$clearedCount"
2315
- )
2316
2244
  }
2317
2245
 
2318
2246
  private fun dispatchEditorUpdate(event: PendingEditorUpdateEvent, emitToJS: Boolean) {
@@ -2348,11 +2276,6 @@ class NativeEditorExpoView(
2348
2276
  )
2349
2277
  onEditorUpdateForTesting?.invoke(payload) ?: onEditorUpdate(payload)
2350
2278
  }
2351
- val totalNanos = System.nanoTime() - startedAt
2352
- richTextView.editorEditText.recordImeTraceForTesting(
2353
- "nativeViewEditorUpdateDispatch",
2354
- "emitToJS=$emitToJS jsonLength=${updateJSON.length} noteUs=${nanosToMicros(noteNanos)} toolbarUs=${nanosToMicros(toolbarNanos)} mentionUs=${nanosToMicros(mentionNanos)} retryUs=${nanosToMicros(retryNanos)} emitUs=${nanosToMicros(System.nanoTime() - emitStartedAt)} totalUs=${nanosToMicros(totalNanos)}"
2355
- )
2356
2279
  }
2357
2280
 
2358
2281
  private fun installOutsideTapBlurHandlerIfNeeded() {
@@ -2371,17 +2294,17 @@ class NativeEditorExpoView(
2371
2294
  cancelPendingOutsideTapBlurHandlerInstallRetry()
2372
2295
  val retry = Runnable {
2373
2296
  pendingOutsideTapHandlerInstallRetry = null
2374
- if (richTextView.editorEditText.hasFocus()) {
2297
+ if (richTextView.isEditorFocused) {
2375
2298
  installOutsideTapBlurHandlerIfNeeded()
2376
2299
  }
2377
2300
  }
2378
2301
  pendingOutsideTapHandlerInstallRetry = retry
2379
- richTextView.editorEditText.postDelayed(retry, OUTSIDE_TAP_HANDLER_INSTALL_RETRY_DELAY_MS)
2302
+ richTextView.postDelayed(retry, OUTSIDE_TAP_HANDLER_INSTALL_RETRY_DELAY_MS)
2380
2303
  }
2381
2304
 
2382
2305
  private fun cancelPendingOutsideTapBlurHandlerInstallRetry() {
2383
2306
  pendingOutsideTapHandlerInstallRetry?.let {
2384
- richTextView.editorEditText.removeCallbacks(it)
2307
+ richTextView.removeCallbacks(it)
2385
2308
  pendingOutsideTapHandlerInstallRetry = null
2386
2309
  }
2387
2310
  }
@@ -2420,7 +2343,7 @@ class NativeEditorExpoView(
2420
2343
  traceOutsideTap("handle decision=$decision")
2421
2344
  when (decision) {
2422
2345
  NativeEditorOutsideTapDecision.IGNORE -> {
2423
- if (!richTextView.editorEditText.hasFocus()) {
2346
+ if (!richTextView.isEditorFocused) {
2424
2347
  cancelPendingOutsideTapBlur()
2425
2348
  }
2426
2349
  }
@@ -2442,7 +2365,7 @@ class NativeEditorExpoView(
2442
2365
  }
2443
2366
 
2444
2367
  private fun isEditorFocusedForOutsideTapDecision(): Boolean =
2445
- editorFocusedForOutsideTapOverrideForTesting ?: richTextView.editorEditText.hasFocus()
2368
+ editorFocusedForOutsideTapOverrideForTesting ?: richTextView.isEditorFocused
2446
2369
 
2447
2370
  private fun isTouchOutsideEditor(event: MotionEvent): Boolean {
2448
2371
  if (isTouchInsideKeyboardToolbar(event)) {
@@ -2454,7 +2377,7 @@ class NativeEditorExpoView(
2454
2377
  return false
2455
2378
  }
2456
2379
  val rect = Rect()
2457
- richTextView.editorEditText.getGlobalVisibleRect(rect)
2380
+ richTextView.getGlobalVisibleRect(rect)
2458
2381
  val isOutside = !rect.contains(event.rawX.toInt(), event.rawY.toInt())
2459
2382
  if (isOutside) {
2460
2383
  clearRecentToolbarTouch()
@@ -2712,7 +2635,7 @@ class NativeEditorExpoView(
2712
2635
 
2713
2636
  private fun refreshMentionQuery() {
2714
2637
  val mentions = addons.mentions
2715
- if (mentions == null || !richTextView.editorEditText.hasFocus()) {
2638
+ if (mentions == null || !richTextView.isEditorFocused) {
2716
2639
  clearMentionQueryState()
2717
2640
  emitMentionQueryChange("", "@", 0, 0, false)
2718
2641
  return
@@ -2727,7 +2650,7 @@ class NativeEditorExpoView(
2727
2650
 
2728
2651
  mentionQueryState = queryState
2729
2652
  val suggestions = filteredMentionSuggestions(queryState, mentions)
2730
- keyboardToolbarView.applyMentionTheme(richTextView.editorEditText.theme?.mentions ?: mentions.theme)
2653
+ keyboardToolbarView.applyMentionTheme(richTextView.editorTheme?.mentions ?: mentions.theme)
2731
2654
  syncKeyboardToolbarMentionSuggestions(suggestions)
2732
2655
  emitMentionQueryChange(
2733
2656
  queryState.query,
@@ -2748,21 +2671,29 @@ class NativeEditorExpoView(
2748
2671
  }
2749
2672
 
2750
2673
  private fun currentMentionQueryState(trigger: String): MentionQueryState? {
2751
- val editor = richTextView.editorEditText
2674
+ val editor = richTextView.activeEditText ?: return null
2752
2675
  if (editor.selectionStart != editor.selectionEnd) return null
2753
2676
  val text = editor.text?.toString() ?: return null
2754
2677
  val cursorUtf16 = editor.selectionStart
2755
2678
  val cursorScalar = PositionBridge.utf16ToScalar(cursorUtf16, text)
2756
- return resolveMentionQueryState(
2679
+ val local = resolveMentionQueryState(
2757
2680
  text = text,
2758
2681
  cursorScalar = cursorScalar,
2759
2682
  trigger = trigger,
2760
2683
  isCaretInsideMention = isCaretInsideMention(cursorUtf16)
2684
+ ) ?: return null
2685
+ val anchorUtf16 = PositionBridge.scalarToUtf16(local.anchor, text)
2686
+ val headUtf16 = PositionBridge.scalarToUtf16(local.head, text)
2687
+ val anchorDoc = richTextView.activeDocPositionForUtf16Offset(anchorUtf16) ?: return null
2688
+ val headDoc = richTextView.activeDocPositionForUtf16Offset(headUtf16) ?: return null
2689
+ return local.copy(
2690
+ anchor = editorDocToScalar(richTextView.editorId.toULong(), anchorDoc.toUInt()).toInt(),
2691
+ head = editorDocToScalar(richTextView.editorId.toULong(), headDoc.toUInt()).toInt()
2761
2692
  )
2762
2693
  }
2763
2694
 
2764
2695
  private fun isCaretInsideMention(cursorUtf16: Int): Boolean {
2765
- val editable = richTextView.editorEditText.text ?: return false
2696
+ val editable = richTextView.activeEditText?.text ?: return false
2766
2697
  val checkOffsets = listOf(cursorUtf16, (cursorUtf16 - 1).coerceAtLeast(0))
2767
2698
  return checkOffsets.any { offset ->
2768
2699
  editable.getSpans(offset, offset, android.text.Annotation::class.java).any { span ->
@@ -2873,7 +2804,7 @@ class NativeEditorExpoView(
2873
2804
  allowPreflightRetry: Boolean = true
2874
2805
  ) {
2875
2806
  if (handleDestroyedCurrentEditorIfNeeded()) return
2876
- if (!richTextView.editorEditText.isEditable) {
2807
+ if (!richTextView.editorIsEditable) {
2877
2808
  clearPendingNativeActionRetry()
2878
2809
  return
2879
2810
  }
@@ -2886,8 +2817,7 @@ class NativeEditorExpoView(
2886
2817
  }
2887
2818
  return
2888
2819
  }
2889
- val preparation = richTextView.editorEditText.prepareForExternalEditorCommand()
2890
- if (!preparation.ready) {
2820
+ if (richTextView.hasActiveComposition()) {
2891
2821
  if (allowPreflightRetry) {
2892
2822
  schedulePendingNativeActionRetry(
2893
2823
  PendingNativeAction.MentionSuggestionSelect(suggestion)
@@ -2895,7 +2825,7 @@ class NativeEditorExpoView(
2895
2825
  }
2896
2826
  return
2897
2827
  }
2898
- val preflightUpdateJSON = preparation.updateJSON
2828
+ val preflightUpdateJSON = editorGetSelectionState(richTextView.editorId.toULong())
2899
2829
  noteDocumentVersionFromUpdateJSON(preflightUpdateJSON)
2900
2830
  clearPendingNativeActionRetry()
2901
2831
  val queryState = currentMentionQueryState(mentions.trigger) ?: run {
@@ -2932,7 +2862,8 @@ class NativeEditorExpoView(
2932
2862
  queryState.head.toUInt(),
2933
2863
  docJson.toString()
2934
2864
  )
2935
- richTextView.editorEditText.applyUpdateJSON(updateJson)
2865
+ richTextView.applyEditorUpdate(updateJson)
2866
+ onEditorUpdate(updateJson)
2936
2867
  emitMentionSelect(mentions.trigger, suggestion, attrs)
2937
2868
  lastMentionEventJson = null
2938
2869
  clearMentionQueryState()
@@ -2989,7 +2920,7 @@ class NativeEditorExpoView(
2989
2920
 
2990
2921
  private fun updateKeyboardToolbarLayout() {
2991
2922
  val params = keyboardToolbarView.layoutParams as? FrameLayout.LayoutParams ?: return
2992
- val toolbarTheme = richTextView.editorEditText.theme?.toolbar
2923
+ val toolbarTheme = richTextView.editorTheme?.toolbar
2993
2924
  val density = resources.displayMetrics.density
2994
2925
  params.gravity = Gravity.BOTTOM or Gravity.START
2995
2926
  val horizontalInsetPx = ((toolbarTheme?.resolvedHorizontalInset() ?: 0f) * density).toInt()
@@ -3013,8 +2944,8 @@ class NativeEditorExpoView(
3013
2944
  showsToolbar &&
3014
2945
  canFocusCurrentEditor() &&
3015
2946
  toolbarPlacement == ToolbarPlacement.KEYBOARD &&
3016
- richTextView.editorEditText.isEditable &&
3017
- richTextView.editorEditText.hasFocus()
2947
+ richTextView.editorIsEditable &&
2948
+ richTextView.isEditorFocused
3018
2949
 
3019
2950
  if (!shouldAttach) {
3020
2951
  keyboardToolbarView.visibility = View.GONE
@@ -3033,8 +2964,8 @@ class NativeEditorExpoView(
3033
2964
  heightBehavior == EditorHeightBehavior.FIXED &&
3034
2965
  showsToolbar &&
3035
2966
  toolbarPlacement == ToolbarPlacement.KEYBOARD &&
3036
- richTextView.editorEditText.isEditable &&
3037
- richTextView.editorEditText.hasFocus() &&
2967
+ richTextView.editorIsEditable &&
2968
+ richTextView.isEditorFocused &&
3038
2969
  currentImeBottom > 0
3039
2970
 
3040
2971
  if (!shouldReserveToolbarSpace) {
@@ -3044,7 +2975,7 @@ class NativeEditorExpoView(
3044
2975
 
3045
2976
  val hostWidth = (resolveActivity(context)?.findViewById<ViewGroup>(android.R.id.content)?.width ?: width)
3046
2977
  .coerceAtLeast(0)
3047
- val toolbarTheme = richTextView.editorEditText.theme?.toolbar
2978
+ val toolbarTheme = richTextView.editorTheme?.toolbar
3048
2979
  val density = resources.displayMetrics.density
3049
2980
  val horizontalInsetPx = ((toolbarTheme?.resolvedHorizontalInset() ?: 0f) * density).toInt()
3050
2981
  if (forceMeasureToolbar || keyboardToolbarView.measuredHeight == 0) {
@@ -3059,7 +2990,10 @@ class NativeEditorExpoView(
3059
2990
 
3060
2991
  private fun handleListToggle(listType: String) {
3061
2992
  val isActive = toolbarState.nodes[listType] == true
3062
- richTextView.editorEditText.performToolbarToggleList(listType, isActive)
2993
+ richTextView.performEditorCommand {
2994
+ if (isActive) editorUnwrapFromList(richTextView.editorId.toULong())
2995
+ else editorWrapInList(richTextView.editorId.toULong(), listType)
2996
+ }
3063
2997
  }
3064
2998
 
3065
2999
  private fun handleToolbarItemPress(
@@ -3067,7 +3001,7 @@ class NativeEditorExpoView(
3067
3001
  allowPreflightRetry: Boolean = true
3068
3002
  ) {
3069
3003
  if (handleDestroyedCurrentEditorIfNeeded()) return
3070
- if (!richTextView.editorEditText.isEditable) {
3004
+ if (!richTextView.editorIsEditable) {
3071
3005
  clearPendingNativeActionRetry()
3072
3006
  return
3073
3007
  }
@@ -3090,31 +3024,38 @@ class NativeEditorExpoView(
3090
3024
  }
3091
3025
  return
3092
3026
  }
3093
- val preparation = richTextView.editorEditText.prepareForExternalEditorCommand()
3094
- if (!preparation.ready) {
3027
+ if (richTextView.hasActiveComposition()) {
3095
3028
  if (allowPreflightRetry) {
3096
3029
  schedulePendingNativeActionRetry(PendingNativeAction.ToolbarItemPress(item))
3097
3030
  }
3098
3031
  return
3099
3032
  }
3100
- preflightUpdateJSON = preparation.updateJSON
3033
+ preflightUpdateJSON = editorGetSelectionState(richTextView.editorId.toULong())
3101
3034
  noteDocumentVersionFromUpdateJSON(preflightUpdateJSON)
3102
3035
  clearPendingNativeActionRetry()
3103
3036
  }
3104
3037
  if (handleDestroyedCurrentEditorIfNeeded()) return
3105
3038
  when (item.type) {
3106
- ToolbarItemKind.mark -> item.mark?.let { richTextView.editorEditText.performToolbarToggleMark(it) }
3107
- ToolbarItemKind.heading -> item.headingLevel?.let { richTextView.editorEditText.performToolbarToggleHeading(it) }
3108
- ToolbarItemKind.blockquote -> richTextView.editorEditText.performToolbarToggleBlockquote()
3039
+ ToolbarItemKind.mark -> item.mark?.let { mark ->
3040
+ richTextView.performEditorCommand { editorToggleMark(richTextView.editorId.toULong(), mark) }
3041
+ }
3042
+ ToolbarItemKind.heading -> item.headingLevel?.let { level ->
3043
+ richTextView.performEditorCommand { editorToggleHeading(richTextView.editorId.toULong(), level.toUByte()) }
3044
+ }
3045
+ ToolbarItemKind.blockquote -> richTextView.performEditorCommand {
3046
+ editorToggleBlockquote(richTextView.editorId.toULong())
3047
+ }
3109
3048
  ToolbarItemKind.list -> item.listType?.name?.let { handleListToggle(it) }
3110
3049
  ToolbarItemKind.command -> when (item.command) {
3111
- ToolbarCommand.indentList -> richTextView.editorEditText.performToolbarIndentListItem()
3112
- ToolbarCommand.outdentList -> richTextView.editorEditText.performToolbarOutdentListItem()
3113
- ToolbarCommand.undo -> richTextView.editorEditText.performToolbarUndo()
3114
- ToolbarCommand.redo -> richTextView.editorEditText.performToolbarRedo()
3050
+ ToolbarCommand.indentList -> richTextView.performEditorCommand { editorIndentListItem(richTextView.editorId.toULong()) }
3051
+ ToolbarCommand.outdentList -> richTextView.performEditorCommand { editorOutdentListItem(richTextView.editorId.toULong()) }
3052
+ ToolbarCommand.undo -> richTextView.performEditorCommand { editorUndo(richTextView.editorId.toULong()) }
3053
+ ToolbarCommand.redo -> richTextView.performEditorCommand { editorRedo(richTextView.editorId.toULong()) }
3115
3054
  null -> Unit
3116
3055
  }
3117
- ToolbarItemKind.node -> item.nodeType?.let { richTextView.editorEditText.performToolbarInsertNode(it) }
3056
+ ToolbarItemKind.node -> item.nodeType?.let { nodeType ->
3057
+ richTextView.performEditorCommand { editorInsertNode(richTextView.editorId.toULong(), nodeType) }
3058
+ }
3118
3059
  ToolbarItemKind.action -> item.key?.let {
3119
3060
  if (handleDestroyedCurrentEditorIfNeeded()) return
3120
3061
  val payload = mutableMapOf<String, Any>(