@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.
@@ -1974,7 +1974,7 @@ final class EditorAccessoryPlaceholderView: UIView {
1974
1974
  }
1975
1975
  }
1976
1976
 
1977
- class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognizerDelegate {
1977
+ class NativeEditorExpoView: ExpoView, UIGestureRecognizerDelegate {
1978
1978
 
1979
1979
  // MARK: - Subviews
1980
1980
 
@@ -2091,29 +2091,23 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
2091
2091
  self.invalidateIntrinsicContentSize()
2092
2092
  self.emitContentHeightIfNeeded(force: true, measuredHeight: measuredHeight)
2093
2093
  }
2094
- richTextView.textView.editorDelegate = self
2094
+ richTextView.onSelectionChange = { [weak self] anchor, head in
2095
+ self?.handleSelectionChange(anchor: anchor, head: head)
2096
+ }
2097
+ richTextView.onEditorUpdate = { [weak self] updateJSON in
2098
+ self?.handleEditorUpdate(updateJSON)
2099
+ }
2100
+ richTextView.onFocusChange = { [weak self] focused in
2101
+ if focused { self?.handleTextDidBeginEditing() }
2102
+ else { self?.handleTextDidEndEditing() }
2103
+ }
2095
2104
  configureAccessoryToolbar()
2096
2105
 
2097
- // Observe UITextView focus changes via NotificationCenter.
2098
- NotificationCenter.default.addObserver(
2099
- self,
2100
- selector: #selector(textViewDidBeginEditing(_:)),
2101
- name: UITextView.textDidBeginEditingNotification,
2102
- object: richTextView.textView
2103
- )
2104
- NotificationCenter.default.addObserver(
2105
- self,
2106
- selector: #selector(textViewDidEndEditing(_:)),
2107
- name: UITextView.textDidEndEditingNotification,
2108
- object: richTextView.textView
2109
- )
2110
-
2111
2106
  addSubview(richTextView)
2112
2107
  }
2113
2108
 
2114
2109
  deinit {
2115
2110
  NativeEditorViewRegistry.shared.unregister(editorId: richTextView.editorId, view: self)
2116
- NotificationCenter.default.removeObserver(self)
2117
2111
  }
2118
2112
 
2119
2113
  // MARK: - Layout
@@ -2142,7 +2136,7 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
2142
2136
 
2143
2137
  override func didMoveToWindow() {
2144
2138
  super.didMoveToWindow()
2145
- if richTextView.textView.isFirstResponder {
2139
+ if richTextView.isEditorFocused {
2146
2140
  installOutsideTapRecognizerIfNeeded()
2147
2141
  } else {
2148
2142
  uninstallOutsideTapRecognizer()
@@ -2153,7 +2147,7 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
2153
2147
 
2154
2148
  func handleEditorDestroyed(_ editorId: UInt64) {
2155
2149
  guard editorId != 0 else { return }
2156
- guard richTextView.editorId == editorId || richTextView.textView.editorId == editorId else {
2150
+ guard richTextView.editorId == editorId else {
2157
2151
  NativeEditorViewRegistry.shared.unregister(editorId: editorId, view: self)
2158
2152
  return
2159
2153
  }
@@ -2166,7 +2160,7 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
2166
2160
  clearPendingAccessoryRetry()
2167
2161
  clearPendingMentionSuggestionRetry()
2168
2162
  lastMentionEventJSON = nil
2169
- _ = richTextView.textView.resignFirstResponder()
2163
+ richTextView.blurEditor()
2170
2164
  richTextView.editorId = 0
2171
2165
  mentionQueryState = nil
2172
2166
  _ = accessoryToolbar.setMentionSuggestions([])
@@ -2246,8 +2240,8 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
2246
2240
  accessoryToolbar.apply(theme: theme?.toolbar)
2247
2241
  accessoryToolbar.apply(mentionTheme: theme?.mentions ?? addons.mentions?.theme)
2248
2242
  refreshSystemAssistantToolbarIfNeeded()
2249
- if richTextView.textView.isFirstResponder,
2250
- (richTextView.textView.inputAccessoryView === accessoryToolbar || shouldUseSystemAssistantToolbar)
2243
+ if richTextView.isEditorFocused,
2244
+ (richTextView.editorInputAccessoryView === accessoryToolbar || shouldUseSystemAssistantToolbar)
2251
2245
  {
2252
2246
  reloadInputViewsAfterPreparingOrRetry()
2253
2247
  }
@@ -2325,10 +2319,10 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
2325
2319
  }
2326
2320
 
2327
2321
  private func prepareForInputAccessoryMutationOrRetry(_ action: PendingAccessoryRetryAction) -> Bool {
2328
- guard richTextView.editorId != 0, richTextView.textView.isFirstResponder else {
2322
+ guard richTextView.editorId != 0, richTextView.isEditorFocused else {
2329
2323
  return true
2330
2324
  }
2331
- guard richTextView.textView.prepareForExternalEditorUpdate() else {
2325
+ guard richTextView.activeTextView?.markedTextRange == nil else {
2332
2326
  scheduleAccessoryRetry(action)
2333
2327
  return false
2334
2328
  }
@@ -2337,7 +2331,7 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
2337
2331
 
2338
2332
  private func reloadInputViewsAfterPreparingOrRetry() {
2339
2333
  guard prepareForInputAccessoryMutationOrRetry(.reloadInputViews) else { return }
2340
- richTextView.textView.reloadInputViews()
2334
+ richTextView.reloadEditorInputViews()
2341
2335
  markAccessoryMutationSucceeded(.reloadInputViews)
2342
2336
  }
2343
2337
 
@@ -2434,7 +2428,7 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
2434
2428
 
2435
2429
  private func hasActiveMentionQueryForCurrentAddons() -> Bool {
2436
2430
  guard richTextView.editorId != 0,
2437
- richTextView.textView.isFirstResponder,
2431
+ richTextView.isEditorFocused,
2438
2432
  let mentions = addons.mentions
2439
2433
  else {
2440
2434
  return false
@@ -2446,7 +2440,7 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
2446
2440
  guard lastAddonsJSON != addonsJson else { return }
2447
2441
  lastAddonsJSON = addonsJson
2448
2442
  addons = NativeEditorAddons.from(json: addonsJson)
2449
- accessoryToolbar.apply(mentionTheme: richTextView.textView.theme?.mentions ?? addons.mentions?.theme)
2443
+ accessoryToolbar.apply(mentionTheme: richTextView.editorTheme?.mentions ?? addons.mentions?.theme)
2450
2444
  refreshMentionQuery()
2451
2445
  }
2452
2446
 
@@ -2458,9 +2452,9 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
2458
2452
 
2459
2453
  func setEditable(_ editable: Bool) {
2460
2454
  if !editable,
2461
- richTextView.textView.isEditable,
2455
+ richTextView.editorIsEditable,
2462
2456
  richTextView.editorId != 0,
2463
- !richTextView.textView.prepareForExternalEditorUpdate()
2457
+ richTextView.activeTextView?.markedTextRange != nil
2464
2458
  {
2465
2459
  scheduleEditableRetry(editable)
2466
2460
  return
@@ -2504,19 +2498,36 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
2504
2498
  }
2505
2499
 
2506
2500
  func setAutoCapitalize(_ autoCapitalize: String?) {
2507
- richTextView.textView.setAutoCapitalize(autoCapitalize)
2501
+ let value: UITextAutocapitalizationType
2502
+ switch autoCapitalize {
2503
+ case "none": value = .none
2504
+ case "words": value = .words
2505
+ case "characters": value = .allCharacters
2506
+ default: value = .sentences
2507
+ }
2508
+ richTextView.configureEditorInputTraits(autocapitalizationType: value)
2508
2509
  }
2509
2510
 
2510
2511
  func setAutoCorrect(_ autoCorrect: Bool?) {
2511
- richTextView.textView.setAutoCorrect(autoCorrect)
2512
+ richTextView.configureEditorInputTraits(autocorrectionType: (autoCorrect ?? false) ? .yes : .no)
2512
2513
  }
2513
2514
 
2514
2515
  func setKeyboardType(_ keyboardType: String?) {
2515
- richTextView.textView.setKeyboardType(keyboardType)
2516
+ let value: UIKeyboardType
2517
+ switch keyboardType {
2518
+ case "email-address": value = .emailAddress
2519
+ case "numeric", "number-pad": value = .numberPad
2520
+ case "decimal-pad": value = .decimalPad
2521
+ case "phone-pad": value = .phonePad
2522
+ case "url": value = .URL
2523
+ default: value = .default
2524
+ }
2525
+ richTextView.configureEditorInputTraits(keyboardType: value)
2516
2526
  }
2517
2527
 
2518
2528
  func setKeyboardAppearance(_ keyboardAppearance: String?) {
2519
- richTextView.textView.setKeyboardAppearance(keyboardAppearance)
2529
+ let value: UIKeyboardAppearance = keyboardAppearance == "dark" ? .dark : keyboardAppearance == "light" ? .light : .default
2530
+ richTextView.configureEditorInputTraits(keyboardAppearance: value)
2520
2531
  }
2521
2532
 
2522
2533
  func setShowToolbar(_ showToolbar: Bool) {
@@ -2648,13 +2659,13 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
2648
2659
  /// resulting delegate callback is NOT re-dispatched back to JS.
2649
2660
  @discardableResult
2650
2661
  func applyEditorUpdate(_ updateJson: String) -> Bool {
2651
- guard richTextView.textView.prepareForExternalEditorUpdate() else {
2662
+ guard richTextView.activeTextView?.markedTextRange == nil else {
2652
2663
  scheduleViewCommandUpdateRetry(updateJson)
2653
2664
  return false
2654
2665
  }
2655
2666
  isApplyingJSUpdate = true
2656
2667
  defer { isApplyingJSUpdate = false }
2657
- richTextView.textView.applyUpdateJSON(updateJson)
2668
+ richTextView.applyEditorUpdate(updateJson)
2658
2669
  return true
2659
2670
  }
2660
2671
 
@@ -2697,23 +2708,22 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
2697
2708
  func prepareForEditorCommandJSON() -> String {
2698
2709
  isApplyingJSUpdate = true
2699
2710
  defer { isApplyingJSUpdate = false }
2700
- let preparation = richTextView.textView.prepareForExternalEditorCommand()
2701
2711
  return NativeEditorViewRegistry.commandPreparationJSON(
2702
- ready: preparation.ready,
2703
- updateJSON: preparation.updateJSON,
2704
- blockedReason: preparation.blockedReason
2712
+ ready: richTextView.activeTextView?.markedTextRange == nil,
2713
+ updateJSON: richTextView.editorId == 0 ? nil : editorGetSelectionState(id: richTextView.editorId),
2714
+ blockedReason: richTextView.activeTextView?.markedTextRange == nil ? nil : "composition"
2705
2715
  )
2706
2716
  }
2707
2717
 
2708
2718
  // MARK: - Focus Commands
2709
2719
 
2710
2720
  func focus() {
2711
- _ = richTextView.textView.becomeFirstResponder()
2721
+ _ = richTextView.focusEditor()
2712
2722
  }
2713
2723
 
2714
2724
  func blur() {
2715
2725
  clearRecentToolbarTouch()
2716
- _ = richTextView.textView.resignFirstResponder()
2726
+ richTextView.blurEditor()
2717
2727
  }
2718
2728
 
2719
2729
  func getCaretRectJson() -> String? {
@@ -2742,30 +2752,28 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
2742
2752
 
2743
2753
  // MARK: - Focus Notifications
2744
2754
 
2745
- @objc private func textViewDidBeginEditing(_ notification: Notification) {
2755
+ private func handleTextDidBeginEditing() {
2746
2756
  installOutsideTapRecognizerIfNeeded()
2747
- richTextView.textView.refreshSelectionVisualState()
2748
2757
  refreshMentionQuery()
2749
2758
  onFocusChange(["isFocused": true])
2750
2759
  }
2751
2760
 
2752
- @objc private func textViewDidEndEditing(_ notification: Notification) {
2761
+ private func handleTextDidEndEditing() {
2753
2762
  if consumeToolbarFocusPreservationForBlur() {
2754
2763
  DispatchQueue.main.async { [weak self] in
2755
- _ = self?.richTextView.textView.becomeFirstResponder()
2764
+ _ = self?.richTextView.focusEditor()
2756
2765
  }
2757
2766
  return
2758
2767
  }
2759
2768
 
2760
2769
  uninstallOutsideTapRecognizer()
2761
- richTextView.textView.refreshSelectionVisualState()
2762
2770
  clearMentionQueryStateAndHidePopover()
2763
2771
  onFocusChange(["isFocused": false])
2764
2772
  }
2765
2773
 
2766
2774
  @objc private func handleOutsideTap(_ recognizer: UITapGestureRecognizer) {
2767
2775
  guard recognizer.state == .ended else { return }
2768
- guard richTextView.textView.isFirstResponder else { return }
2776
+ guard richTextView.isEditorFocused else { return }
2769
2777
  guard let tapWindow = gestureWindow ?? window else { return }
2770
2778
  let locationInWindow = recognizer.location(in: tapWindow)
2771
2779
  guard shouldHandleOutsideTap(locationInWindow: locationInWindow, touchedView: nil) else {
@@ -2863,9 +2871,7 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
2863
2871
  return true
2864
2872
  }
2865
2873
 
2866
- // MARK: - EditorTextViewDelegate
2867
-
2868
- func editorTextView(_ textView: EditorTextView, selectionDidChange anchor: UInt32, head: UInt32) {
2874
+ private func handleSelectionChange(anchor: UInt32, head: UInt32) {
2869
2875
  let stateJSON = refreshToolbarStateFromEditorSelection()
2870
2876
  refreshSystemAssistantToolbarIfNeeded()
2871
2877
  refreshMentionQuery()
@@ -2877,7 +2883,7 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
2877
2883
  onSelectionChange(event)
2878
2884
  }
2879
2885
 
2880
- func editorTextView(_ textView: EditorTextView, didReceiveUpdate updateJSON: String) {
2886
+ private func handleEditorUpdate(_ updateJSON: String) {
2881
2887
  if let state = NativeToolbarState(updateJSON: updateJSON) {
2882
2888
  toolbarState = state
2883
2889
  accessoryToolbar.apply(state: state)
@@ -2913,7 +2919,7 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
2913
2919
 
2914
2920
  private func refreshMentionQuery() {
2915
2921
  guard richTextView.editorId != 0,
2916
- richTextView.textView.isFirstResponder,
2922
+ richTextView.isEditorFocused,
2917
2923
  let mentions = addons.mentions
2918
2924
  else {
2919
2925
  clearMentionQueryStateAndHidePopover()
@@ -2929,14 +2935,14 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
2929
2935
 
2930
2936
  let suggestions = filteredMentionSuggestions(for: queryState, config: mentions)
2931
2937
  mentionQueryState = queryState
2932
- accessoryToolbar.apply(mentionTheme: richTextView.textView.theme?.mentions ?? mentions.theme)
2938
+ accessoryToolbar.apply(mentionTheme: richTextView.editorTheme?.mentions ?? mentions.theme)
2933
2939
  let didChangeToolbarHeight = accessoryToolbar.setMentionSuggestions(suggestions)
2934
2940
  refreshSystemAssistantToolbarIfNeeded()
2935
2941
  if didChangeToolbarHeight,
2936
- richTextView.textView.isFirstResponder,
2937
- richTextView.textView.inputAccessoryView === accessoryToolbar
2942
+ richTextView.isEditorFocused,
2943
+ richTextView.editorInputAccessoryView === accessoryToolbar
2938
2944
  {
2939
- richTextView.textView.reloadInputViews()
2945
+ richTextView.reloadEditorInputViews()
2940
2946
  }
2941
2947
  markAccessoryMutationSucceeded(.refreshMentionQuery)
2942
2948
  emitMentionQueryChange(
@@ -2954,10 +2960,10 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
2954
2960
  let didChangeToolbarHeight = accessoryToolbar.setMentionSuggestions([])
2955
2961
  refreshSystemAssistantToolbarIfNeeded()
2956
2962
  if didChangeToolbarHeight,
2957
- richTextView.textView.isFirstResponder,
2958
- richTextView.textView.inputAccessoryView === accessoryToolbar
2963
+ richTextView.isEditorFocused,
2964
+ richTextView.editorInputAccessoryView === accessoryToolbar
2959
2965
  {
2960
- richTextView.textView.reloadInputViews()
2966
+ richTextView.reloadEditorInputViews()
2961
2967
  }
2962
2968
  markAccessoryMutationSucceeded(.clearMentionQueryState)
2963
2969
  }
@@ -3091,15 +3097,16 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
3091
3097
  }
3092
3098
 
3093
3099
  private func currentMentionQueryState(trigger: String) -> MentionQueryState? {
3094
- guard let selectedTextRange = richTextView.textView.selectedTextRange,
3100
+ guard let activeTextView = richTextView.activeTextView,
3101
+ let selectedTextRange = activeTextView.selectedTextRange,
3095
3102
  selectedTextRange.isEmpty
3096
3103
  else {
3097
3104
  return nil
3098
3105
  }
3099
3106
 
3100
- let currentText = richTextView.textView.text ?? ""
3101
- let cursorUtf16Offset = richTextView.textView.offset(
3102
- from: richTextView.textView.beginningOfDocument,
3107
+ let currentText = activeTextView.text ?? ""
3108
+ let cursorUtf16Offset = activeTextView.offset(
3109
+ from: activeTextView.beginningOfDocument,
3103
3110
  to: selectedTextRange.start
3104
3111
  )
3105
3112
  let visibleCursorScalar = PositionBridge.utf16OffsetToScalar(
@@ -3114,7 +3121,7 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
3114
3121
  isCaretInsideMention: isCaretInsideMention(
3115
3122
  cursorScalar: PositionBridge.textViewToScalar(
3116
3123
  selectedTextRange.start,
3117
- in: richTextView.textView
3124
+ in: activeTextView
3118
3125
  )
3119
3126
  )
3120
3127
  ) else {
@@ -3130,39 +3137,22 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
3130
3137
  in: currentText
3131
3138
  )
3132
3139
 
3140
+ guard let anchorDoc = richTextView.activeDocPosition(forUTF16Offset: anchorUtf16Offset),
3141
+ let headDoc = richTextView.activeDocPosition(forUTF16Offset: headUtf16Offset)
3142
+ else { return nil }
3133
3143
  return MentionQueryState(
3134
3144
  query: visibleQueryState.query,
3135
3145
  trigger: visibleQueryState.trigger,
3136
- anchor: PositionBridge.utf16OffsetToScalar(
3137
- anchorUtf16Offset,
3138
- in: richTextView.textView
3139
- ),
3140
- head: PositionBridge.utf16OffsetToScalar(
3141
- headUtf16Offset,
3142
- in: richTextView.textView
3143
- )
3146
+ anchor: editorDocToScalar(id: richTextView.editorId, docPos: anchorDoc),
3147
+ head: editorDocToScalar(id: richTextView.editorId, docPos: headDoc)
3144
3148
  )
3145
3149
  }
3146
3150
 
3147
3151
  private func isCaretInsideMention(cursorScalar: UInt32) -> Bool {
3148
- let utf16Offset = PositionBridge.scalarToUtf16Offset(
3149
- cursorScalar,
3150
- in: richTextView.textView.text ?? ""
3151
- )
3152
- let textStorage = richTextView.textView.textStorage
3153
- guard textStorage.length > 0 else { return false }
3154
- let candidateOffsets = [
3155
- min(max(utf16Offset, 0), max(textStorage.length - 1, 0)),
3156
- min(max(utf16Offset - 1, 0), max(textStorage.length - 1, 0)),
3157
- ]
3158
-
3159
- for offset in candidateOffsets where offset >= 0 && offset < textStorage.length {
3160
- if let nodeType = textStorage.attribute(InputCoordinatorAttributes.voidNodeType, at: offset, effectiveRange: nil) as? String,
3161
- nodeType == "mention" {
3162
- return true
3163
- }
3164
- }
3165
- return false
3152
+ guard let activeTextView = richTextView.activeTextView else { return false }
3153
+ let utf16Offset = PositionBridge.scalarToUtf16Offset(cursorScalar, in: activeTextView.text ?? "")
3154
+ return richTextView.activeInlineAtomType(atUTF16Offset: utf16Offset) == "mention"
3155
+ || richTextView.activeInlineAtomType(atUTF16Offset: max(0, utf16Offset - 1)) == "mention"
3166
3156
  }
3167
3157
 
3168
3158
  private func insertMentionSuggestion(
@@ -3200,8 +3190,9 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
3200
3190
  clearMentionQueryStateAndHidePopover()
3201
3191
  return
3202
3192
  }
3203
- let preparation = richTextView.textView.prepareForExternalEditorCommand()
3204
- guard preparation.ready else {
3193
+ let compositionActive = richTextView.activeTextView?.markedTextRange != nil
3194
+ let preflightUpdateJSON = editorGetSelectionState(id: richTextView.editorId)
3195
+ guard !compositionActive else {
3205
3196
  scheduleMentionSuggestionRetry(
3206
3197
  PendingMentionSuggestionRetry(
3207
3198
  suggestionKey: suggestionKey,
@@ -3211,13 +3202,13 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
3211
3202
  anchor: scopedQueryState.anchor,
3212
3203
  head: scopedQueryState.head,
3213
3204
  documentVersion: currentDocumentVersion(),
3214
- textSnapshot: richTextView.textView.text ?? ""
3205
+ textSnapshot: richTextView.activeTextView?.text ?? ""
3215
3206
  )
3216
3207
  )
3217
3208
  return
3218
3209
  }
3219
3210
  let queryState = currentMentionQueryState(trigger: mentions.trigger)
3220
- ?? (richTextView.textView.isFirstResponder ? nil : mentionQueryState)
3211
+ ?? (richTextView.isEditorFocused ? nil : mentionQueryState)
3221
3212
  guard let queryState else {
3222
3213
  clearMentionQueryStateAndHidePopover()
3223
3214
  return
@@ -3226,8 +3217,8 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
3226
3217
  !doesMentionQueryState(
3227
3218
  queryState,
3228
3219
  match: retryScope,
3229
- acceptingPreflightDocumentVersion: documentVersion(fromUpdateJSON: preparation.updateJSON),
3230
- currentText: richTextView.textView.text ?? ""
3220
+ acceptingPreflightDocumentVersion: documentVersion(fromUpdateJSON: preflightUpdateJSON),
3221
+ currentText: richTextView.activeTextView?.text ?? ""
3231
3222
  )
3232
3223
  {
3233
3224
  return
@@ -3248,7 +3239,7 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
3248
3239
  suggestion: currentSuggestion,
3249
3240
  attrs: attrs,
3250
3241
  range: queryState,
3251
- preflightUpdateJSON: preparation.updateJSON
3242
+ preflightUpdateJSON: preflightUpdateJSON
3252
3243
  )
3253
3244
  lastMentionEventJSON = nil
3254
3245
  clearMentionQueryStateAndHidePopover()
@@ -3273,7 +3264,8 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
3273
3264
  scalarHead: queryState.head,
3274
3265
  json: json
3275
3266
  )
3276
- richTextView.textView.applyUpdateJSON(updateJSON)
3267
+ richTextView.applyEditorUpdate(updateJSON)
3268
+ handleEditorUpdate(updateJSON)
3277
3269
  emitMentionSelect(trigger: mentions.trigger, suggestion: currentSuggestion, attrs: attrs)
3278
3270
  lastMentionEventJSON = nil
3279
3271
  clearMentionQueryStateAndHidePopover()
@@ -3477,15 +3469,15 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
3477
3469
  }
3478
3470
 
3479
3471
  func inputAccessoryViewForTesting() -> UIView? {
3480
- richTextView.textView.inputAccessoryView
3472
+ richTextView.editorInputAccessoryView
3481
3473
  }
3482
3474
 
3483
3475
  func isUsingAccessoryToolbarForTesting() -> Bool {
3484
- richTextView.textView.inputAccessoryView === accessoryToolbar
3476
+ richTextView.editorInputAccessoryView === accessoryToolbar
3485
3477
  }
3486
3478
 
3487
3479
  func isUsingAccessoryPlaceholderForTesting() -> Bool {
3488
- richTextView.textView.inputAccessoryView === accessoryPlaceholder
3480
+ richTextView.editorInputAccessoryView === accessoryPlaceholder
3489
3481
  }
3490
3482
 
3491
3483
  func markRecentToolbarTouchForTesting() {
@@ -3516,19 +3508,19 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
3516
3508
  let nextAccessoryView: UIView?
3517
3509
  if showsToolbar &&
3518
3510
  toolbarPlacement == "keyboard" &&
3519
- richTextView.textView.isEditable &&
3511
+ richTextView.editorIsEditable &&
3520
3512
  !shouldUseSystemAssistantToolbar
3521
3513
  {
3522
3514
  nextAccessoryView = accessoryToolbar
3523
- } else if richTextView.textView.isEditable && !shouldUseSystemAssistantToolbar {
3515
+ } else if richTextView.editorIsEditable && !shouldUseSystemAssistantToolbar {
3524
3516
  nextAccessoryView = accessoryPlaceholder
3525
3517
  } else {
3526
3518
  nextAccessoryView = nil
3527
3519
  }
3528
- if richTextView.textView.inputAccessoryView !== nextAccessoryView {
3529
- richTextView.textView.inputAccessoryView = nextAccessoryView
3530
- if richTextView.textView.isFirstResponder {
3531
- richTextView.textView.reloadInputViews()
3520
+ if richTextView.editorInputAccessoryView !== nextAccessoryView {
3521
+ richTextView.setEditorInputAccessoryView(nextAccessoryView)
3522
+ if richTextView.isEditorFocused {
3523
+ richTextView.reloadEditorInputViews()
3532
3524
  }
3533
3525
  }
3534
3526
  markAccessoryMutationSucceeded(.updateAccessoryToolbarVisibility)
@@ -3541,7 +3533,7 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
3541
3533
  private func refreshSystemAssistantToolbarIfNeeded() {
3542
3534
  guard #available(iOS 26.0, *) else { return }
3543
3535
 
3544
- let assistantItem = richTextView.textView.inputAssistantItem
3536
+ guard let assistantItem = richTextView.editorInputAssistantItem else { return }
3545
3537
  assistantItem.allowsHidingShortcuts = false
3546
3538
  assistantItem.leadingBarButtonGroups = []
3547
3539
  assistantItem.trailingBarButtonGroups = []
@@ -3549,38 +3541,43 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
3549
3541
 
3550
3542
  private func handleListToggle(_ listType: String) {
3551
3543
  let isActive = toolbarState.nodes[listType] == true
3552
- richTextView.textView.performToolbarToggleList(listType, isActive: isActive)
3544
+ guard richTextView.editorId != 0 else { return }
3545
+ _ = richTextView.performEditorCommand {
3546
+ isActive
3547
+ ? editorUnwrapFromList(id: richTextView.editorId)
3548
+ : editorWrapInList(id: richTextView.editorId, listType: listType)
3549
+ }
3553
3550
  }
3554
3551
 
3555
3552
  private func handleToolbarItemPress(_ item: NativeToolbarItem) {
3556
3553
  switch item.type {
3557
3554
  case .mark:
3558
3555
  guard let mark = item.mark else { return }
3559
- richTextView.textView.performToolbarToggleMark(mark)
3556
+ _ = richTextView.performEditorCommand { editorToggleMark(id: richTextView.editorId, markName: mark) }
3560
3557
  case .heading:
3561
3558
  guard let level = item.headingLevel else { return }
3562
- richTextView.textView.performToolbarToggleHeading(level)
3559
+ _ = richTextView.performEditorCommand { editorToggleHeading(id: richTextView.editorId, level: UInt8(level)) }
3563
3560
  case .blockquote:
3564
- richTextView.textView.performToolbarToggleBlockquote()
3561
+ _ = richTextView.performEditorCommand { editorToggleBlockquote(id: richTextView.editorId) }
3565
3562
  case .list:
3566
3563
  guard let listType = item.listType?.rawValue else { return }
3567
3564
  handleListToggle(listType)
3568
3565
  case .command:
3569
3566
  switch item.command {
3570
3567
  case .indentList:
3571
- richTextView.textView.performToolbarIndentListItem()
3568
+ _ = richTextView.performEditorCommand { editorIndentListItem(id: richTextView.editorId) }
3572
3569
  case .outdentList:
3573
- richTextView.textView.performToolbarOutdentListItem()
3570
+ _ = richTextView.performEditorCommand { editorOutdentListItem(id: richTextView.editorId) }
3574
3571
  case .undo:
3575
- richTextView.textView.performToolbarUndo()
3572
+ _ = richTextView.performEditorCommand { editorUndo(id: richTextView.editorId) }
3576
3573
  case .redo:
3577
- richTextView.textView.performToolbarRedo()
3574
+ _ = richTextView.performEditorCommand { editorRedo(id: richTextView.editorId) }
3578
3575
  case .none:
3579
3576
  break
3580
3577
  }
3581
3578
  case .node:
3582
3579
  guard let nodeType = item.nodeType else { return }
3583
- richTextView.textView.performToolbarInsertNode(nodeType)
3580
+ _ = richTextView.performEditorCommand { editorInsertNode(id: richTextView.editorId, nodeType: nodeType) }
3584
3581
  case .action:
3585
3582
  guard let key = item.key else { return }
3586
3583
  onToolbarAction(["key": key])
@@ -538,7 +538,7 @@ public class NativeEditorModule: Module {
538
538
  view.setEditable(editable)
539
539
  }
540
540
  Prop("placeholder") { (view: NativeEditorExpoView, placeholder: String) in
541
- view.richTextView.textView.placeholder = placeholder
541
+ view.richTextView.placeholder = placeholder
542
542
  }
543
543
  Prop("autoFocus") { (view: NativeEditorExpoView, autoFocus: Bool) in
544
544
  view.setAutoFocus(autoFocus)