@apollohg/react-native-rich-text-editor 2.0.0

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.
Files changed (494) hide show
  1. package/LICENSE +160 -0
  2. package/README.md +259 -0
  3. package/RUST-STANDARD-LIBRARY-NOTICES.html +8266 -0
  4. package/ReactNativeProseEditor.podspec +59 -0
  5. package/THIRD_PARTY_NOTICES.md +31155 -0
  6. package/android/build.gradle +188 -0
  7. package/android/consumer-rules.pro +18 -0
  8. package/android/expo/build.gradle +17 -0
  9. package/android/src/debug/java/com/apollohg/editor/viewer/PreparedProseDrawInstrumentation.kt +7 -0
  10. package/android/src/main/assets/editor-icons/MaterialIcons.json +2236 -0
  11. package/android/src/main/assets/editor-icons/MaterialIcons.ttf +0 -0
  12. package/android/src/main/java/com/apollohg/editor/AndroidApiCompat.kt +19 -0
  13. package/android/src/main/java/com/apollohg/editor/AndroidCollaborationTransport.kt +860 -0
  14. package/android/src/main/java/com/apollohg/editor/CaretGeometry.kt +91 -0
  15. package/android/src/main/java/com/apollohg/editor/CodeHighlighting.kt +141 -0
  16. package/android/src/main/java/com/apollohg/editor/DecodedBitmapBudget.kt +250 -0
  17. package/android/src/main/java/com/apollohg/editor/EditorAddons.kt +133 -0
  18. package/android/src/main/java/com/apollohg/editor/EditorBlockSpacing.kt +51 -0
  19. package/android/src/main/java/com/apollohg/editor/EditorBoxDrawable.kt +16 -0
  20. package/android/src/main/java/com/apollohg/editor/EditorBoxDrawing.kt +234 -0
  21. package/android/src/main/java/com/apollohg/editor/EditorCheckboxSpan.kt +87 -0
  22. package/android/src/main/java/com/apollohg/editor/EditorCodeHighlighting.kt +120 -0
  23. package/android/src/main/java/com/apollohg/editor/EditorDecorationLayer.java +18 -0
  24. package/android/src/main/java/com/apollohg/editor/EditorDocumentLayout.kt +626 -0
  25. package/android/src/main/java/com/apollohg/editor/EditorEditText.kt +1042 -0
  26. package/android/src/main/java/com/apollohg/editor/EditorEditTextAnnotations.kt +191 -0
  27. package/android/src/main/java/com/apollohg/editor/EditorEditTextCaret.kt +100 -0
  28. package/android/src/main/java/com/apollohg/editor/EditorEditTextClipboard.kt +101 -0
  29. package/android/src/main/java/com/apollohg/editor/EditorEditTextComposition.kt +220 -0
  30. package/android/src/main/java/com/apollohg/editor/EditorEditTextConfiguration.kt +316 -0
  31. package/android/src/main/java/com/apollohg/editor/EditorEditTextCorrection.kt +303 -0
  32. package/android/src/main/java/com/apollohg/editor/EditorEditTextDeferredUpdates.kt +327 -0
  33. package/android/src/main/java/com/apollohg/editor/EditorEditTextDeletion.kt +258 -0
  34. package/android/src/main/java/com/apollohg/editor/EditorEditTextDiagnostics.kt +83 -0
  35. package/android/src/main/java/com/apollohg/editor/EditorEditTextDragDrop.kt +67 -0
  36. package/android/src/main/java/com/apollohg/editor/EditorEditTextExternalComposition.kt +357 -0
  37. package/android/src/main/java/com/apollohg/editor/EditorEditTextHardwareInput.kt +277 -0
  38. package/android/src/main/java/com/apollohg/editor/EditorEditTextImages.kt +280 -0
  39. package/android/src/main/java/com/apollohg/editor/EditorEditTextInputLifecycle.kt +183 -0
  40. package/android/src/main/java/com/apollohg/editor/EditorEditTextInputPreparation.kt +79 -0
  41. package/android/src/main/java/com/apollohg/editor/EditorEditTextInputTraits.kt +384 -0
  42. package/android/src/main/java/com/apollohg/editor/EditorEditTextLayout.kt +100 -0
  43. package/android/src/main/java/com/apollohg/editor/EditorEditTextNativeMutation.kt +243 -0
  44. package/android/src/main/java/com/apollohg/editor/EditorEditTextPlaceholder.kt +154 -0
  45. package/android/src/main/java/com/apollohg/editor/EditorEditTextRenderPatch.kt +347 -0
  46. package/android/src/main/java/com/apollohg/editor/EditorEditTextRendering.kt +313 -0
  47. package/android/src/main/java/com/apollohg/editor/EditorEditTextRustCommands.kt +281 -0
  48. package/android/src/main/java/com/apollohg/editor/EditorEditTextSelection.kt +352 -0
  49. package/android/src/main/java/com/apollohg/editor/EditorEditTextState.kt +113 -0
  50. package/android/src/main/java/com/apollohg/editor/EditorEditTextTaskMarkers.kt +92 -0
  51. package/android/src/main/java/com/apollohg/editor/EditorEditTextTextCommit.kt +327 -0
  52. package/android/src/main/java/com/apollohg/editor/EditorEditTextToolbar.kt +84 -0
  53. package/android/src/main/java/com/apollohg/editor/EditorEditTextUpdates.kt +245 -0
  54. package/android/src/main/java/com/apollohg/editor/EditorGlyphHeightCursorDrawable.kt +42 -0
  55. package/android/src/main/java/com/apollohg/editor/EditorHeightBehavior.kt +13 -0
  56. package/android/src/main/java/com/apollohg/editor/EditorInputConnection.kt +923 -0
  57. package/android/src/main/java/com/apollohg/editor/EditorInputCoordinates.kt +191 -0
  58. package/android/src/main/java/com/apollohg/editor/EditorInputCorrection.kt +107 -0
  59. package/android/src/main/java/com/apollohg/editor/EditorInputCursorGeometry.kt +128 -0
  60. package/android/src/main/java/com/apollohg/editor/EditorInputSnapshots.kt +134 -0
  61. package/android/src/main/java/com/apollohg/editor/EditorInputSurroundingDeletion.kt +321 -0
  62. package/android/src/main/java/com/apollohg/editor/EditorKeyboardToolbarView.kt +851 -0
  63. package/android/src/main/java/com/apollohg/editor/EditorMentionStyle.kt +192 -0
  64. package/android/src/main/java/com/apollohg/editor/EditorReconciliationWatcher.kt +46 -0
  65. package/android/src/main/java/com/apollohg/editor/EditorStyleSheet.kt +303 -0
  66. package/android/src/main/java/com/apollohg/editor/EditorStyleSheetSpans.kt +253 -0
  67. package/android/src/main/java/com/apollohg/editor/EditorSurfaceInputConnection.kt +67 -0
  68. package/android/src/main/java/com/apollohg/editor/EditorTextDecorationDrawing.kt +143 -0
  69. package/android/src/main/java/com/apollohg/editor/EditorTextSurface.kt +712 -0
  70. package/android/src/main/java/com/apollohg/editor/EditorTextSurfaceDragDrop.kt +94 -0
  71. package/android/src/main/java/com/apollohg/editor/EditorTextSurfaceInteraction.kt +708 -0
  72. package/android/src/main/java/com/apollohg/editor/EditorTheme.kt +650 -0
  73. package/android/src/main/java/com/apollohg/editor/EditorV2Adapter.kt +912 -0
  74. package/android/src/main/java/com/apollohg/editor/EditorV2AdapterMutations.kt +402 -0
  75. package/android/src/main/java/com/apollohg/editor/EditorV2AdapterRendering.kt +123 -0
  76. package/android/src/main/java/com/apollohg/editor/EditorV2AdapterSelection.kt +203 -0
  77. package/android/src/main/java/com/apollohg/editor/EditorV2Backend.kt +120 -0
  78. package/android/src/main/java/com/apollohg/editor/EditorV2Driver.kt +87 -0
  79. package/android/src/main/java/com/apollohg/editor/EditorV2Registry.kt +97 -0
  80. package/android/src/main/java/com/apollohg/editor/EditorV2RenderValidation.kt +409 -0
  81. package/android/src/main/java/com/apollohg/editor/ExpoAutoGrowStyleSizePublisher.kt +34 -0
  82. package/android/src/main/java/com/apollohg/editor/ImageResizeOverlayView.kt +266 -0
  83. package/android/src/main/java/com/apollohg/editor/ImeTextCoordinateMapper.kt +102 -0
  84. package/android/src/main/java/com/apollohg/editor/KeyboardToolbarImeAnimationController.kt +101 -0
  85. package/android/src/main/java/com/apollohg/editor/MentionSuggestionChipView.kt +234 -0
  86. package/android/src/main/java/com/apollohg/editor/NativeCollaborationTransportRegistry.kt +493 -0
  87. package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +813 -0
  88. package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewAtoms.kt +68 -0
  89. package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewBinding.kt +126 -0
  90. package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewDiagnostics.kt +208 -0
  91. package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewErrors.kt +146 -0
  92. package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewEvents.kt +150 -0
  93. package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewExternalComposition.kt +39 -0
  94. package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewFocus.kt +176 -0
  95. package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewLifecycle.kt +302 -0
  96. package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewMentions.kt +259 -0
  97. package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewNativeActions.kt +179 -0
  98. package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewOutsideTap.kt +214 -0
  99. package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewPendingUpdates.kt +376 -0
  100. package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewProperties.kt +234 -0
  101. package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewPropertyRetries.kt +197 -0
  102. package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewToolbar.kt +285 -0
  103. package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewTypes.kt +28 -0
  104. package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewUpdates.kt +271 -0
  105. package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +687 -0
  106. package/android/src/main/java/com/apollohg/editor/NativeEditorOutsideTapDispatcher.kt +380 -0
  107. package/android/src/main/java/com/apollohg/editor/NativeEditorV2Numbers.kt +45 -0
  108. package/android/src/main/java/com/apollohg/editor/NativeEditorViewRegistry.kt +372 -0
  109. package/android/src/main/java/com/apollohg/editor/NativeImagePipeline.kt +22 -0
  110. package/android/src/main/java/com/apollohg/editor/NativeToolbar.kt +193 -0
  111. package/android/src/main/java/com/apollohg/editor/NativeToolbarIcons.kt +177 -0
  112. package/android/src/main/java/com/apollohg/editor/NativeToolbarItem.kt +274 -0
  113. package/android/src/main/java/com/apollohg/editor/OkHttpCollaborationSocket.kt +102 -0
  114. package/android/src/main/java/com/apollohg/editor/OrderedListMarkerFormatter.kt +130 -0
  115. package/android/src/main/java/com/apollohg/editor/PointerTransparentView.java +18 -0
  116. package/android/src/main/java/com/apollohg/editor/PositionBridge.kt +128 -0
  117. package/android/src/main/java/com/apollohg/editor/ProseViewerView.kt +854 -0
  118. package/android/src/main/java/com/apollohg/editor/RemoteSelectionOverlayView.kt +314 -0
  119. package/android/src/main/java/com/apollohg/editor/RenderBlockSpans.kt +314 -0
  120. package/android/src/main/java/com/apollohg/editor/RenderBridge.kt +224 -0
  121. package/android/src/main/java/com/apollohg/editor/RenderBridgeBlockStyles.kt +407 -0
  122. package/android/src/main/java/com/apollohg/editor/RenderBridgeElements.kt +544 -0
  123. package/android/src/main/java/com/apollohg/editor/RenderBridgeInlineAndAtoms.kt +631 -0
  124. package/android/src/main/java/com/apollohg/editor/RenderBridgeParagraphs.kt +215 -0
  125. package/android/src/main/java/com/apollohg/editor/RenderImageDecoder.kt +748 -0
  126. package/android/src/main/java/com/apollohg/editor/RenderImageLoader.kt +900 -0
  127. package/android/src/main/java/com/apollohg/editor/RenderLayoutConstants.kt +117 -0
  128. package/android/src/main/java/com/apollohg/editor/RenderMediaSpans.kt +337 -0
  129. package/android/src/main/java/com/apollohg/editor/RenderParagraphSpans.kt +182 -0
  130. package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +951 -0
  131. package/android/src/main/java/com/apollohg/editor/SharedNativeImagePipeline.kt +78 -0
  132. package/android/src/main/java/com/apollohg/editor/UniffiEditorV2Backend.kt +261 -0
  133. package/android/src/main/java/com/apollohg/editor/viewer/AndroidProseLayoutEngine.kt +1656 -0
  134. package/android/src/main/java/com/apollohg/editor/viewer/FabricAttachmentSidecars.kt +80 -0
  135. package/android/src/main/java/com/apollohg/editor/viewer/FabricLeaseHandleBridge.kt +59 -0
  136. package/android/src/main/java/com/apollohg/editor/viewer/FallbackSelectionGeometry.kt +450 -0
  137. package/android/src/main/java/com/apollohg/editor/viewer/PreparedProseDrawingView.kt +678 -0
  138. package/android/src/main/java/com/apollohg/editor/viewer/PreparedProseInstrumentation.kt +608 -0
  139. package/android/src/main/java/com/apollohg/editor/viewer/PreparedProseLayout.kt +186 -0
  140. package/android/src/main/java/com/apollohg/editor/viewer/PreparedProseLayoutCache.kt +393 -0
  141. package/android/src/main/java/com/apollohg/editor/viewer/PreparedProseLayoutRegistry.kt +828 -0
  142. package/android/src/main/java/com/apollohg/editor/viewer/PreparedProseTheme.kt +316 -0
  143. package/android/src/main/java/com/apollohg/editor/viewer/PreparedProseViewerManager.kt +900 -0
  144. package/android/src/main/java/com/apollohg/editor/viewer/PreparedProseViewerPackage.kt +16 -0
  145. package/android/src/main/java/com/apollohg/editor/viewer/PreparedViewerAtoms.kt +89 -0
  146. package/android/src/main/java/com/apollohg/editor/viewer/ViewerCodeHighlighting.kt +91 -0
  147. package/android/src/main/java/com/apollohg/editor/viewer/ViewerDocument.kt +453 -0
  148. package/android/src/main/java/com/apollohg/editor/viewer/ViewerFontEnvironment.kt +282 -0
  149. package/android/src/main/java/com/apollohg/editor/viewer/ViewerImagePipeline.kt +460 -0
  150. package/android/src/main/jni/CMakeLists.txt +85 -0
  151. package/android/src/main/jni/PreparedProseViewerMeasurementsManager.cpp +334 -0
  152. package/android/src/main/jni/ReactNativeProseEditorSpec.h +17 -0
  153. package/android/src/main/jni/reactnativeproseeditor.h +6 -0
  154. package/android/src/main/res/values/attrs.xml +6 -0
  155. package/android/src/release/java/com/apollohg/editor/viewer/PreparedProseDrawInstrumentation.kt +9 -0
  156. package/app.plugin.js +62 -0
  157. package/common/cpp/react/renderer/components/PreparedProseViewer/PreparedProseMeasurementsManager.h +62 -0
  158. package/common/cpp/react/renderer/components/PreparedProseViewer/PreparedProseViewerComponentDescriptor.h +30 -0
  159. package/common/cpp/react/renderer/components/PreparedProseViewer/PreparedProseViewerShadowNode.cpp +192 -0
  160. package/common/cpp/react/renderer/components/PreparedProseViewer/PreparedProseViewerShadowNode.h +46 -0
  161. package/common/cpp/react/renderer/components/PreparedProseViewer/PreparedProseViewerState.h +145 -0
  162. package/dist/AtomHost.d.ts +17 -0
  163. package/dist/AtomHost.js +135 -0
  164. package/dist/DefaultAtomChip.d.ts +2 -0
  165. package/dist/DefaultAtomChip.js +22 -0
  166. package/dist/EditorAddon.d.ts +32 -0
  167. package/dist/EditorAddon.js +29 -0
  168. package/dist/EditorAddonNormalization.d.ts +6 -0
  169. package/dist/EditorAddonNormalization.js +48 -0
  170. package/dist/EditorMentionThemeNormalization.d.ts +8 -0
  171. package/dist/EditorMentionThemeNormalization.js +22 -0
  172. package/dist/EditorMentionThemeValidation.d.ts +12 -0
  173. package/dist/EditorMentionThemeValidation.js +95 -0
  174. package/dist/EditorStyleSheet.d.ts +4 -0
  175. package/dist/EditorStyleSheet.js +10 -0
  176. package/dist/EditorStyleSheetNormalization.d.ts +3 -0
  177. package/dist/EditorStyleSheetNormalization.js +226 -0
  178. package/dist/EditorStyleSheetTypes.d.ts +151 -0
  179. package/dist/EditorStyleSheetTypes.js +2 -0
  180. package/dist/EditorTheme.d.ts +117 -0
  181. package/dist/EditorTheme.js +41 -0
  182. package/dist/EditorToolbar.d.ts +4 -0
  183. package/dist/EditorToolbar.js +17 -0
  184. package/dist/EditorToolbarComponent.d.ts +13 -0
  185. package/dist/EditorToolbarComponent.js +24 -0
  186. package/dist/EditorToolbarItems.d.ts +9 -0
  187. package/dist/EditorToolbarItems.js +58 -0
  188. package/dist/EditorToolbarRegistry.d.ts +59 -0
  189. package/dist/EditorToolbarRegistry.js +175 -0
  190. package/dist/EditorToolbarTypes.d.ts +273 -0
  191. package/dist/EditorToolbarTypes.js +2 -0
  192. package/dist/EditorToolbarVisuals.d.ts +154 -0
  193. package/dist/EditorToolbarVisuals.js +228 -0
  194. package/dist/EditorUpdateRevision.d.ts +8 -0
  195. package/dist/EditorUpdateRevision.js +22 -0
  196. package/dist/ExternalTextComposition.d.ts +45 -0
  197. package/dist/ExternalTextComposition.js +537 -0
  198. package/dist/ImageLoadingPolicy.d.ts +54 -0
  199. package/dist/ImageLoadingPolicy.js +60 -0
  200. package/dist/NativeEditorBoundaryError.d.ts +111 -0
  201. package/dist/NativeEditorBoundaryError.js +268 -0
  202. package/dist/NativeEditorBridge.d.ts +9 -0
  203. package/dist/NativeEditorBridge.js +37 -0
  204. package/dist/NativeEditorCollaborationTransport.d.ts +154 -0
  205. package/dist/NativeEditorCollaborationTransport.js +275 -0
  206. package/dist/NativeEditorCreateConfig.d.ts +16 -0
  207. package/dist/NativeEditorCreateConfig.js +249 -0
  208. package/dist/NativeEditorCreateJson.d.ts +76 -0
  209. package/dist/NativeEditorCreateJson.js +474 -0
  210. package/dist/NativeEditorCreateValidation.d.ts +32 -0
  211. package/dist/NativeEditorCreateValidation.js +150 -0
  212. package/dist/NativeEditorDocumentBridge.d.ts +72 -0
  213. package/dist/NativeEditorDocumentBridge.js +305 -0
  214. package/dist/NativeEditorDocumentHandle.d.ts +70 -0
  215. package/dist/NativeEditorDocumentHandle.js +89 -0
  216. package/dist/NativeEditorLocalAwareness.d.ts +34 -0
  217. package/dist/NativeEditorLocalAwareness.js +153 -0
  218. package/dist/NativeEditorNativeModule.d.ts +48 -0
  219. package/dist/NativeEditorNativeModule.js +34 -0
  220. package/dist/NativeEditorRenderNormalization.d.ts +22 -0
  221. package/dist/NativeEditorRenderNormalization.js +274 -0
  222. package/dist/NativeEditorResultNormalization.d.ts +108 -0
  223. package/dist/NativeEditorResultNormalization.js +400 -0
  224. package/dist/NativeEditorTypes.d.ts +318 -0
  225. package/dist/NativeEditorTypes.js +15 -0
  226. package/dist/NativeEditorV2Decimal.d.ts +5 -0
  227. package/dist/NativeEditorV2Decimal.js +18 -0
  228. package/dist/NativeProseViewer.d.ts +122 -0
  229. package/dist/NativeProseViewer.js +130 -0
  230. package/dist/NativeRichTextEditor.d.ts +3 -0
  231. package/dist/NativeRichTextEditor.js +6 -0
  232. package/dist/ResourceLimits.d.ts +100 -0
  233. package/dist/ResourceLimits.js +80 -0
  234. package/dist/RichTextEditor.d.ts +4 -0
  235. package/dist/RichTextEditor.js +25 -0
  236. package/dist/RichTextEditorNativeTypes.d.ts +122 -0
  237. package/dist/RichTextEditorNativeTypes.js +2 -0
  238. package/dist/RichTextEditorNativeView.d.ts +12 -0
  239. package/dist/RichTextEditorNativeView.js +15 -0
  240. package/dist/RichTextEditorSerialization.d.ts +53 -0
  241. package/dist/RichTextEditorSerialization.js +283 -0
  242. package/dist/RichTextEditorTypes.d.ts +281 -0
  243. package/dist/RichTextEditorTypes.js +2 -0
  244. package/dist/ViewerConfiguration.d.ts +22 -0
  245. package/dist/ViewerConfiguration.js +6 -0
  246. package/dist/YjsCollaboration.d.ts +146 -0
  247. package/dist/YjsCollaboration.js +496 -0
  248. package/dist/addons.d.ts +193 -0
  249. package/dist/addons.js +122 -0
  250. package/dist/atomConstants.d.ts +3 -0
  251. package/dist/atomConstants.js +6 -0
  252. package/dist/atomInstances.d.ts +35 -0
  253. package/dist/atomInstances.js +79 -0
  254. package/dist/atomPolicy.d.ts +4 -0
  255. package/dist/atomPolicy.js +47 -0
  256. package/dist/atomUpdates.d.ts +2 -0
  257. package/dist/atomUpdates.js +38 -0
  258. package/dist/atoms.d.ts +92 -0
  259. package/dist/atoms.js +222 -0
  260. package/dist/attributeValidation.d.ts +4 -0
  261. package/dist/attributeValidation.js +97 -0
  262. package/dist/contentExpression.d.ts +6 -0
  263. package/dist/contentExpression.js +335 -0
  264. package/dist/index.d.ts +21 -0
  265. package/dist/index.js +78 -0
  266. package/dist/listMarkers.d.ts +15 -0
  267. package/dist/listMarkers.js +21 -0
  268. package/dist/schemaDefinition.d.ts +143 -0
  269. package/dist/schemaDefinition.js +206 -0
  270. package/dist/schemaDocument.d.ts +17 -0
  271. package/dist/schemaDocument.js +69 -0
  272. package/dist/schemaDocumentConstruction.d.ts +5 -0
  273. package/dist/schemaDocumentConstruction.js +125 -0
  274. package/dist/schemaNormalization.d.ts +29 -0
  275. package/dist/schemaNormalization.js +313 -0
  276. package/dist/schemaPresets.d.ts +68 -0
  277. package/dist/schemaPresets.js +198 -0
  278. package/dist/schemaResolution.d.ts +9 -0
  279. package/dist/schemaResolution.js +211 -0
  280. package/dist/schemas.d.ts +5 -0
  281. package/dist/schemas.js +28 -0
  282. package/dist/specs/PreparedProseViewerNativeComponent.d.ts +37 -0
  283. package/dist/specs/PreparedProseViewerNativeComponent.js +6 -0
  284. package/dist/useEditorToolbarInteractions.d.ts +12 -0
  285. package/dist/useEditorToolbarInteractions.js +218 -0
  286. package/dist/useEditorToolbarItems.d.ts +8 -0
  287. package/dist/useEditorToolbarItems.js +280 -0
  288. package/dist/useEditorToolbarPresentation.d.ts +4 -0
  289. package/dist/useEditorToolbarPresentation.js +203 -0
  290. package/dist/useEditorToolbarState.d.ts +63 -0
  291. package/dist/useEditorToolbarState.js +108 -0
  292. package/dist/useFocusPreservingFrames.d.ts +17 -0
  293. package/dist/useFocusPreservingFrames.js +108 -0
  294. package/dist/useNativeEditor.d.ts +66 -0
  295. package/dist/useNativeEditor.js +370 -0
  296. package/dist/useRichTextEditorCommands.d.ts +21 -0
  297. package/dist/useRichTextEditorCommands.js +299 -0
  298. package/dist/useRichTextEditorEvents.d.ts +18 -0
  299. package/dist/useRichTextEditorEvents.js +216 -0
  300. package/dist/useRichTextEditorMentions.d.ts +11 -0
  301. package/dist/useRichTextEditorMentions.js +316 -0
  302. package/dist/useRichTextEditorPresentation.d.ts +5 -0
  303. package/dist/useRichTextEditorPresentation.js +178 -0
  304. package/dist/useRichTextEditorState.d.ts +180 -0
  305. package/dist/useRichTextEditorState.js +405 -0
  306. package/dist/useRichTextEditorUpdates.d.ts +10 -0
  307. package/dist/useRichTextEditorUpdates.js +308 -0
  308. package/dist/useViewerAtoms.d.ts +38 -0
  309. package/dist/useViewerAtoms.js +288 -0
  310. package/expo-module.config.json +13 -0
  311. package/ios/EditorAddons.swift +354 -0
  312. package/ios/EditorCodeHighlighting.swift +184 -0
  313. package/ios/EditorCore.xcframework/Info.plist +44 -0
  314. package/ios/EditorCore.xcframework/ios-arm64/libeditor_core.a +0 -0
  315. package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a +0 -0
  316. package/ios/EditorInteractionOverlays.swift +697 -0
  317. package/ios/EditorLayoutManager.swift +971 -0
  318. package/ios/EditorNodeTypes.swift +37 -0
  319. package/ios/EditorStyleSheet.swift +461 -0
  320. package/ios/EditorTextView+AttributedPatch.swift +305 -0
  321. package/ios/EditorTextView+Binding.swift +61 -0
  322. package/ios/EditorTextView+Caret.swift +333 -0
  323. package/ios/EditorTextView+Commands.swift +203 -0
  324. package/ios/EditorTextView+Delegate.swift +65 -0
  325. package/ios/EditorTextView+Diagnostics.swift +225 -0
  326. package/ios/EditorTextView+DragDrop.swift +290 -0
  327. package/ios/EditorTextView+ExternalComposition.swift +364 -0
  328. package/ios/EditorTextView+Images.swift +354 -0
  329. package/ios/EditorTextView+InputTraits.swift +200 -0
  330. package/ios/EditorTextView+KeyboardInput.swift +318 -0
  331. package/ios/EditorTextView+Layout.swift +245 -0
  332. package/ios/EditorTextView+MarkedText.swift +267 -0
  333. package/ios/EditorTextView+NativeInputLifecycle.swift +372 -0
  334. package/ios/EditorTextView+NativeMutation.swift +471 -0
  335. package/ios/EditorTextView+Reconciliation.swift +97 -0
  336. package/ios/EditorTextView+RenderPatch.swift +573 -0
  337. package/ios/EditorTextView+Rendering.swift +539 -0
  338. package/ios/EditorTextView+Selection.swift +542 -0
  339. package/ios/EditorTextView.swift +953 -0
  340. package/ios/EditorTheme.swift +626 -0
  341. package/ios/EditorV2Adapter+Commands.swift +204 -0
  342. package/ios/EditorV2Adapter+Envelope.swift +238 -0
  343. package/ios/EditorV2Adapter+Mutation.swift +392 -0
  344. package/ios/EditorV2Adapter+Registry.swift +185 -0
  345. package/ios/EditorV2Adapter+RenderState.swift +360 -0
  346. package/ios/EditorV2Adapter+RenderValidation.swift +420 -0
  347. package/ios/EditorV2Adapter+Selection.swift +329 -0
  348. package/ios/EditorV2Adapter+TextCommands.swift +430 -0
  349. package/ios/EditorV2Adapter.swift +376 -0
  350. package/ios/EditorV2Shadow.swift +353 -0
  351. package/ios/Generated_editor_core.swift +2378 -0
  352. package/ios/NativeCodeHighlighting.swift +159 -0
  353. package/ios/NativeCollaborationSocket.swift +144 -0
  354. package/ios/NativeCollaborationTransport+Protocol.swift +258 -0
  355. package/ios/NativeCollaborationTransport+Socket.swift +379 -0
  356. package/ios/NativeCollaborationTransport+Validation.swift +103 -0
  357. package/ios/NativeCollaborationTransport.swift +322 -0
  358. package/ios/NativeCollaborationTransportRegistry.swift +521 -0
  359. package/ios/NativeEditorExpoView+AccessoryRetry.swift +219 -0
  360. package/ios/NativeEditorExpoView+Binding.swift +268 -0
  361. package/ios/NativeEditorExpoView+Diagnostics.swift +66 -0
  362. package/ios/NativeEditorExpoView+Events.swift +131 -0
  363. package/ios/NativeEditorExpoView+Focus.swift +169 -0
  364. package/ios/NativeEditorExpoView+MentionRetry.swift +288 -0
  365. package/ios/NativeEditorExpoView+Mentions.swift +288 -0
  366. package/ios/NativeEditorExpoView+Properties.swift +300 -0
  367. package/ios/NativeEditorExpoView+Toolbar.swift +86 -0
  368. package/ios/NativeEditorExpoView+Updates.swift +312 -0
  369. package/ios/NativeEditorExpoView.swift +349 -0
  370. package/ios/NativeEditorModule.swift +856 -0
  371. package/ios/NativeEditorViewRegistry.swift +226 -0
  372. package/ios/NativeToolbar+ButtonStyle.swift +214 -0
  373. package/ios/NativeToolbar+Buttons.swift +228 -0
  374. package/ios/NativeToolbar+Diagnostics.swift +114 -0
  375. package/ios/NativeToolbar+Menu.swift +101 -0
  376. package/ios/NativeToolbar+Models.swift +519 -0
  377. package/ios/NativeToolbar+Placeholder.swift +39 -0
  378. package/ios/NativeToolbar.swift +612 -0
  379. package/ios/OrderedListMarkerFormatter.swift +103 -0
  380. package/ios/PositionBridge.swift +596 -0
  381. package/ios/ProseViewerView.swift +462 -0
  382. package/ios/RenderBridge+Atoms.swift +288 -0
  383. package/ios/RenderBridge+Attachments.swift +246 -0
  384. package/ios/RenderBridge+BlockBoundaries.swift +313 -0
  385. package/ios/RenderBridge+Style.swift +343 -0
  386. package/ios/RenderBridge.swift +704 -0
  387. package/ios/RichTextEditorView.swift +930 -0
  388. package/ios/SharedNativeImagePipeline+Cache.swift +189 -0
  389. package/ios/SharedNativeImagePipeline+Transport.swift +328 -0
  390. package/ios/SharedNativeImagePipeline.swift +533 -0
  391. package/ios/Viewer/CoreTextProseLayoutEngine+AttributedText.swift +436 -0
  392. package/ios/Viewer/CoreTextProseLayoutEngine+Models.swift +322 -0
  393. package/ios/Viewer/CoreTextProseLayoutEngine.swift +635 -0
  394. package/ios/Viewer/Fabric/PREPPreparedProseViewerComponentView.h +4 -0
  395. package/ios/Viewer/Fabric/PREPPreparedProseViewerComponentView.mm +767 -0
  396. package/ios/Viewer/Fabric/PreparedProseMeasurementsManager.mm +118 -0
  397. package/ios/Viewer/NativeSVGImageDecoder.h +9 -0
  398. package/ios/Viewer/NativeSVGImageDecoder.mm +106 -0
  399. package/ios/Viewer/NativeSVGPreflight.h +11 -0
  400. package/ios/Viewer/NativeSVGPreflight.mm +219 -0
  401. package/ios/Viewer/PreparedProseDrawingView.swift +639 -0
  402. package/ios/Viewer/PreparedProseInstrumentation.swift +387 -0
  403. package/ios/Viewer/PreparedProseLayout.swift +326 -0
  404. package/ios/Viewer/PreparedProseLayoutCache.swift +766 -0
  405. package/ios/Viewer/PreparedProseLayoutRegistry+Artifacts.swift +377 -0
  406. package/ios/Viewer/PreparedProseLayoutRegistry+Fabric.swift +582 -0
  407. package/ios/Viewer/PreparedProseLayoutRegistry+FabricOwnership.swift +128 -0
  408. package/ios/Viewer/PreparedProseLayoutRegistry.swift +381 -0
  409. package/ios/Viewer/ViewerDocument.swift +575 -0
  410. package/ios/Viewer/ViewerFontEnvironment.swift +283 -0
  411. package/ios/Viewer/ViewerImagePipeline.swift +478 -0
  412. package/ios/editor_coreFFI/editor_coreFFI.h +1023 -0
  413. package/ios/editor_coreFFI/module.modulemap +7 -0
  414. package/package.json +151 -0
  415. package/react-native.config.js +17 -0
  416. package/rust/android/arm64-v8a/libeditor_core.so +0 -0
  417. package/rust/android/armeabi-v7a/libeditor_core.so +0 -0
  418. package/rust/android/x86/libeditor_core.so +0 -0
  419. package/rust/android/x86_64/libeditor_core.so +0 -0
  420. package/rust/bindings/kotlin/uniffi/editor_core/editor_core.kt +3146 -0
  421. package/src/AtomHost.tsx +180 -0
  422. package/src/DefaultAtomChip.tsx +27 -0
  423. package/src/EditorAddon.ts +65 -0
  424. package/src/EditorAddonNormalization.ts +49 -0
  425. package/src/EditorMentionThemeNormalization.ts +25 -0
  426. package/src/EditorMentionThemeValidation.ts +133 -0
  427. package/src/EditorStyleSheet.ts +9 -0
  428. package/src/EditorStyleSheetNormalization.ts +219 -0
  429. package/src/EditorStyleSheetTypes.ts +189 -0
  430. package/src/EditorTheme.ts +196 -0
  431. package/src/EditorToolbar.tsx +31 -0
  432. package/src/EditorToolbarComponent.tsx +23 -0
  433. package/src/EditorToolbarItems.tsx +64 -0
  434. package/src/EditorToolbarRegistry.tsx +254 -0
  435. package/src/EditorToolbarTypes.tsx +322 -0
  436. package/src/EditorToolbarVisuals.tsx +270 -0
  437. package/src/EditorUpdateRevision.ts +26 -0
  438. package/src/ExternalTextComposition.ts +658 -0
  439. package/src/ImageLoadingPolicy.ts +125 -0
  440. package/src/NativeEditorBoundaryError.ts +345 -0
  441. package/src/NativeEditorBridge.ts +93 -0
  442. package/src/NativeEditorCollaborationTransport.ts +493 -0
  443. package/src/NativeEditorCreateConfig.ts +326 -0
  444. package/src/NativeEditorCreateJson.ts +579 -0
  445. package/src/NativeEditorCreateValidation.ts +178 -0
  446. package/src/NativeEditorDocumentBridge.ts +485 -0
  447. package/src/NativeEditorDocumentHandle.ts +168 -0
  448. package/src/NativeEditorLocalAwareness.ts +168 -0
  449. package/src/NativeEditorNativeModule.ts +97 -0
  450. package/src/NativeEditorRenderNormalization.ts +320 -0
  451. package/src/NativeEditorResultNormalization.ts +469 -0
  452. package/src/NativeEditorTypes.ts +366 -0
  453. package/src/NativeEditorV2Decimal.ts +13 -0
  454. package/src/NativeProseViewer.tsx +356 -0
  455. package/src/NativeRichTextEditor.tsx +33 -0
  456. package/src/ResourceLimits.ts +187 -0
  457. package/src/RichTextEditor.tsx +27 -0
  458. package/src/RichTextEditorNativeTypes.tsx +144 -0
  459. package/src/RichTextEditorNativeView.tsx +21 -0
  460. package/src/RichTextEditorSerialization.tsx +381 -0
  461. package/src/RichTextEditorTypes.tsx +327 -0
  462. package/src/ViewerConfiguration.ts +23 -0
  463. package/src/YjsCollaboration.ts +692 -0
  464. package/src/addons.ts +311 -0
  465. package/src/atomConstants.ts +3 -0
  466. package/src/atomInstances.ts +123 -0
  467. package/src/atomPolicy.ts +47 -0
  468. package/src/atomUpdates.ts +52 -0
  469. package/src/atoms.ts +379 -0
  470. package/src/attributeValidation.ts +116 -0
  471. package/src/contentExpression.ts +338 -0
  472. package/src/index.ts +295 -0
  473. package/src/listMarkers.ts +18 -0
  474. package/src/schemaDefinition.ts +395 -0
  475. package/src/schemaDocument.ts +92 -0
  476. package/src/schemaDocumentConstruction.ts +148 -0
  477. package/src/schemaNormalization.ts +367 -0
  478. package/src/schemaPresets.ts +250 -0
  479. package/src/schemaResolution.ts +242 -0
  480. package/src/schemas.ts +43 -0
  481. package/src/specs/PreparedProseViewerNativeComponent.ts +40 -0
  482. package/src/useEditorToolbarInteractions.tsx +329 -0
  483. package/src/useEditorToolbarItems.tsx +390 -0
  484. package/src/useEditorToolbarPresentation.tsx +483 -0
  485. package/src/useEditorToolbarState.tsx +172 -0
  486. package/src/useFocusPreservingFrames.ts +171 -0
  487. package/src/useNativeEditor.ts +534 -0
  488. package/src/useRichTextEditorCommands.tsx +478 -0
  489. package/src/useRichTextEditorEvents.tsx +342 -0
  490. package/src/useRichTextEditorMentions.tsx +441 -0
  491. package/src/useRichTextEditorPresentation.tsx +563 -0
  492. package/src/useRichTextEditorState.tsx +620 -0
  493. package/src/useRichTextEditorUpdates.tsx +403 -0
  494. package/src/useViewerAtoms.tsx +427 -0
@@ -0,0 +1,953 @@
1
+ import os
2
+ import UIKit
3
+
4
+ /// UITextView that intercepts input and routes it through editor-core.
5
+ ///
6
+ /// The text view is a rendering surface, not a text engine: intent (typing,
7
+ /// deleting, pasting, autocorrect) goes to Rust, which returns render elements
8
+ /// that RenderBridge converts back to NSAttributedString.
9
+ ///
10
+ /// IME: UITextView owns marked text during composition so the user sees it;
11
+ /// `unmarkText` commits through Rust at the Rust-authorized range.
12
+ ///
13
+ /// Every UITextView method runs on the main thread, and the UniFFI calls are
14
+ /// synchronous.
15
+ final class EditorTextView: UITextView, UIGestureRecognizerDelegate, UITextDragDelegate, UITextDropDelegate {
16
+ static let emptyBlockPlaceholderScalar = UnicodeScalar(0x200B)!
17
+
18
+ lazy var internalTextViewDelegate = EditorTextViewInternalDelegate(editor: self)
19
+ var imageLoadOwner: RenderImageLoadOwner?
20
+
21
+ override var undoManager: UndoManager? { nil }
22
+
23
+ // MARK: - Properties
24
+
25
+ /// The Rust editor instance ID (from editor_create / editor_create_with_max_length).
26
+ /// Set to 0 when no editor is bound.
27
+ var editorId: UInt64 = 0
28
+ let nativeBindingToken = UUID()
29
+
30
+ /// Guard flag to prevent re-entrant input interception while we're
31
+ /// applying state from Rust (calling replaceCharacters on the text storage).
32
+ var isApplyingRustState = false
33
+ var localTextDragState = LocalTextDragState.idle
34
+ var visibleSelectionTintColor: UIColor = .systemBlue
35
+ var hidesNativeSelectionChrome = false
36
+ var isPreviewingImageResize = false
37
+ var allowImageResizing = true
38
+
39
+ override var isEditable: Bool {
40
+ didSet {
41
+ if oldValue, !isEditable {
42
+ _ = finishExternalTextComposition(
43
+ cause: "lifecycle",
44
+ finalText: nil,
45
+ cancel: true
46
+ )
47
+ }
48
+ }
49
+ }
50
+
51
+ /// The base font used for unstyled text. Configurable from React props.
52
+ var baseFont: UIFont = .systemFont(ofSize: 16) {
53
+ didSet {
54
+ placeholderLabel.font = resolvedDefaultFont()
55
+ renderAppearanceRevision &+= 1
56
+ invalidateAutoGrowHeightMeasurement()
57
+ }
58
+ }
59
+
60
+ /// The base text color. Configurable from React props.
61
+ var baseTextColor: UIColor = .label {
62
+ didSet {
63
+ renderAppearanceRevision &+= 1
64
+ }
65
+ }
66
+
67
+ /// The base background color before theme overrides.
68
+ var baseBackgroundColor: UIColor = .systemBackground
69
+ var baseTextContainerInset: UIEdgeInsets = .zero
70
+ var baseLineFragmentPadding: CGFloat = 0
71
+
72
+ /// Optional render theme supplied by React.
73
+ lazy var styleContentView: EditorStyleBoxView = {
74
+ let view = EditorStyleBoxView()
75
+ view.isUserInteractionEnabled = false
76
+ view.backgroundColor = .clear
77
+ insertSubview(view, at: 0)
78
+ return view
79
+ }()
80
+
81
+ let codeHighlightingSession = NativeCodeHighlightingSession()
82
+ var onCodeHighlightingError: ((Error) -> Void)?
83
+ var codeHighlighting: NativeCodeHighlightConfiguration? {
84
+ didSet {
85
+ guard oldValue != codeHighlighting else { return }
86
+ codeHighlightingSession.cancel()
87
+ restoreCodeHighlighting()
88
+ scheduleCodeHighlighting()
89
+ }
90
+ }
91
+
92
+ var theme: EditorTheme? {
93
+ didSet {
94
+ renderAppearanceRevision &+= 1
95
+ placeholderLabel.font = resolvedDefaultFont()
96
+ placeholderLabel.textColor = theme?.placeholderColor ?? .placeholderText
97
+ if let sheet = theme?.styleSheet {
98
+ var attributes: [NSAttributedString.Key: Any] = [.font: resolvedDefaultFont(), .foregroundColor: theme?.placeholderColor ?? UIColor.placeholderText]
99
+ EditorStyleSheet.applyText(sheet["placeholder"], to: &attributes)
100
+ placeholderLabel.attributedText = NSAttributedString(string: placeholder, attributes: attributes)
101
+ } else { placeholderLabel.attributedText = nil; placeholderLabel.text = placeholder }
102
+ styleContentView.box = theme?.styleSheet?.box("content")
103
+ backgroundColor = theme?.backgroundColor ?? baseBackgroundColor
104
+ if let contentInsets = theme?.contentInsets {
105
+ textContainerInset = UIEdgeInsets(
106
+ top: contentInsets.top ?? 0,
107
+ left: contentInsets.left ?? 0,
108
+ bottom: contentInsets.bottom ?? 0,
109
+ right: contentInsets.right ?? 0
110
+ )
111
+ textContainer.lineFragmentPadding = 0
112
+ } else {
113
+ textContainerInset = baseTextContainerInset
114
+ textContainer.lineFragmentPadding = baseLineFragmentPadding
115
+ }
116
+ invalidateAutoGrowHeightMeasurement()
117
+ setNeedsLayout()
118
+ }
119
+ }
120
+
121
+ var atomRenderConfiguration: AtomRenderConfiguration? {
122
+ didSet {
123
+ guard oldValue != atomRenderConfiguration else { return }
124
+ renderAppearanceRevision &+= 1
125
+ invalidateAutoGrowHeightMeasurement()
126
+ }
127
+ }
128
+
129
+ var heightBehavior: EditorHeightBehavior = .fixed {
130
+ didSet {
131
+ guard oldValue != heightBehavior else { return }
132
+ isScrollEnabled = heightBehavior == .fixed
133
+ invalidateAutoGrowHeightMeasurement()
134
+ invalidateIntrinsicContentSize()
135
+ notifyHeightChangeIfNeeded(force: true)
136
+ }
137
+ }
138
+
139
+ var onHeightMayChange: ((CGFloat) -> Void)?
140
+ var onViewportMayChange: (() -> Void)?
141
+ var onSelectionOrContentMayChange: (() -> Void)?
142
+ var onExternalUpdateReadinessMayChange: (() -> Void)?
143
+ var lastAutoGrowMeasuredHeight: CGFloat = 0
144
+ var lastAutoGrowMeasuredWidth: CGFloat = 0
145
+ var autoGrowHostHeight: CGFloat = 0
146
+ var autoGrowHeightCheckIsDirty = true
147
+ var lastHeightNotifyMeasureNanosForTesting: UInt64 = 0
148
+ var lastHeightNotifyCallbackNanosForTesting: UInt64 = 0
149
+ var lastHeightNotifyEnsureLayoutNanosForTesting: UInt64 = 0
150
+ var lastHeightNotifyUsedRectNanosForTesting: UInt64 = 0
151
+ var lastHeightNotifyContentSizeNanosForTesting: UInt64 = 0
152
+ var lastHeightNotifySizeThatFitsNanosForTesting: UInt64 = 0
153
+
154
+ /// Delegate for editor events.
155
+ weak var editorDelegate: EditorTextViewDelegate?
156
+
157
+ /// The plain text from the last Rust render, used by the reconciliation
158
+ /// fallback to detect unauthorized text storage mutations.
159
+ var lastAuthorizedTextStorage = NSMutableString()
160
+ var lastAuthorizedAttributedTextStorage = NSMutableAttributedString()
161
+ var lastAuthorizedText: String {
162
+ lastAuthorizedTextStorage as String
163
+ }
164
+ var lastRenderAppliedPatchForTesting: Bool = false
165
+ var onApplyingRustTextForTesting: (() -> Void)?
166
+ var captureApplyUpdateTraceForTesting = false
167
+ var lastApplyUpdateTraceForTesting: ApplyUpdateTrace?
168
+ var currentRenderBlocks: [[[String: Any]]]?
169
+ var currentRenderBlocksDocumentVersion: UInt64?
170
+ var recoveringRenderPatchBaseMismatch = false
171
+ var currentTopLevelChildMetadata: [TopLevelChildMetadata]?
172
+ var renderAppearanceRevision: UInt64 = 1
173
+ var lastAppliedRenderAppearanceRevision: UInt64 = 0
174
+
175
+ /// Number of times the reconciliation fallback has fired. Exposed for
176
+ /// monitoring / kill-condition telemetry.
177
+ var reconciliationCount: Int = 0
178
+
179
+ /// Logger for reconciliation events (visible in Console.app / device logs).
180
+ static let reconciliationLog = Logger(
181
+ subsystem: "com.apollohg.prose-editor",
182
+ category: "reconciliation"
183
+ )
184
+ static let inputLog = Logger(
185
+ subsystem: "com.apollohg.prose-editor",
186
+ category: "input"
187
+ )
188
+ static let updateLog = Logger(
189
+ subsystem: "com.apollohg.prose-editor",
190
+ category: "update"
191
+ )
192
+ static let selectionLog = Logger(
193
+ subsystem: "com.apollohg.prose-editor",
194
+ category: "selection"
195
+ )
196
+
197
+ /// Tracks whether we're in a composition session (CJK / IME input).
198
+ var isComposing = false
199
+ var hasPendingCompositionForExternalRefresh: Bool { isComposing }
200
+ lazy var caretPlacementTapRecognizer: CaretPlacementTapRecognizer = {
201
+ let recognizer = CaretPlacementTapRecognizer(target: self, action: #selector(handleCaretPlacementTap(_:)))
202
+ recognizer.cancelsTouchesInView = false
203
+ recognizer.delaysTouchesBegan = false
204
+ recognizer.delaysTouchesEnded = false
205
+ recognizer.delegate = self
206
+ return recognizer
207
+ }()
208
+ lazy var imageSelectionTapRecognizer: UITapGestureRecognizer = {
209
+ let recognizer = UITapGestureRecognizer(target: self, action: #selector(handleImageSelectionTap(_:)))
210
+ recognizer.cancelsTouchesInView = true
211
+ recognizer.delaysTouchesBegan = false
212
+ recognizer.delaysTouchesEnded = false
213
+ recognizer.delegate = self
214
+ return recognizer
215
+ }()
216
+
217
+ /// Guards against reconciliation firing while we're already intercepting
218
+ /// and replaying a user input operation through Rust, including the
219
+ /// trailing UIKit text-storage callbacks that arrive on the next run loop.
220
+ var interceptedInputDepth = 0
221
+ var deferredInsertTexts: [String] = []
222
+ var deferredInsertDrainScheduled = false
223
+ var isReplayingDeferredInsertText = false
224
+ var reconciliationWorkScheduled = false
225
+ var nativeTextMutationCommitScheduled = false
226
+ var pendingNativeTextMutation: NativeTextMutation?
227
+ var nativeTextMutationGeneration: UInt64 = 0
228
+ var nativeTextMutationAfterBlurDeadline: TimeInterval?
229
+ var nativeTextMutationAfterBlurGeneration: UInt64?
230
+ private let nativeTextMutationAfterBlurGraceInterval: TimeInterval = 1.0
231
+ /// Last selection known to match `lastAuthorizedText`, stored in that text's UTF-16 coordinates.
232
+ var lastAuthorizedSelectedUtf16Range: NSRange?
233
+ var lastAuthorizedSelectionIsBackward = false
234
+ var logicalSelectionScalarRange: (anchor: UInt32, head: UInt32)?
235
+ /// The UIKit selection `logicalSelectionScalarRange` was resolved to, after
236
+ /// any empty-block autocapitalization nudge. Lets a deliberate nudge be
237
+ /// told apart from the user moving the caret.
238
+ var logicalSelectionUtf16Range: NSRange?
239
+ var selectionRevision: UInt64 = 0
240
+ var desiredInputTraitState = InputTraitState()
241
+ var appliedInputTraitState = InputTraitState()
242
+ var pendingInputTraitChange = PendingInputTraitChange()
243
+ var pendingInputTraitRetryScheduled = false
244
+ var pendingInputTraitRetryGeneration: UInt64 = 0
245
+
246
+ /// Coalesces selection sync until UIKit has finished resolving the
247
+ /// current tap/drag gesture's final caret position.
248
+ var pendingSelectionSyncGeneration: UInt64 = 0
249
+ var pendingDeferredImageSelectionRange: NSRange?
250
+ var pendingDeferredImageSelectionGeneration: UInt64 = 0
251
+
252
+ /// Stores the Rust-authorized scalar range replaced by the active marked
253
+ /// text session. UIKit mutates visible TextKit state during composition,
254
+ /// so final commits must not infer their range from the transient cursor.
255
+ var markedTextReplacementScalarRange: (from: UInt32, to: UInt32)?
256
+ var markedTextReplacementUtf16Range: NSRange?
257
+ var markedTextCompositionText: String?
258
+ var markedTextCompositionIsExplicitlyEmpty = false
259
+
260
+ var externalTextComposition: ExternalTextCompositionState?
261
+ var externalTextCompositionTerminalResults: [String: String] = [:]
262
+
263
+ let editorLayoutManager: EditorLayoutManager
264
+
265
+ // MARK: - Placeholder
266
+
267
+ lazy var placeholderLabel: UILabel = {
268
+ let label = UILabel()
269
+ label.textColor = .placeholderText
270
+ label.font = baseFont
271
+ label.numberOfLines = 0
272
+ label.isUserInteractionEnabled = false
273
+ return label
274
+ }()
275
+
276
+ var placeholder: String = "" {
277
+ didSet {
278
+ placeholderLabel.text = placeholder
279
+ refreshPlaceholderVisibility()
280
+ setNeedsLayout()
281
+ }
282
+ }
283
+
284
+ // MARK: - Initialization
285
+
286
+ override init(frame: CGRect, textContainer: NSTextContainer?) {
287
+ let layoutManager = EditorLayoutManager()
288
+ let container = textContainer ?? NSTextContainer(
289
+ size: CGSize(width: 0, height: CGFloat.greatestFiniteMagnitude)
290
+ )
291
+ let textStorage = NSTextStorage()
292
+ layoutManager.addTextContainer(container)
293
+ textStorage.addLayoutManager(layoutManager)
294
+ editorLayoutManager = layoutManager
295
+ super.init(frame: frame, textContainer: container)
296
+ commonInit()
297
+ }
298
+
299
+ required init?(coder: NSCoder) {
300
+ return nil
301
+ }
302
+
303
+ private func commonInit() {
304
+ textContainer.widthTracksTextView = true
305
+ // Large documents edit more smoothly when TextKit can invalidate and
306
+ // relayout only the touched region instead of forcing contiguous layout.
307
+ editorLayoutManager.allowsNonContiguousLayout = true
308
+ NotificationCenter.default.addObserver(
309
+ self,
310
+ selector: #selector(handleImageAttachmentDidLoad(_:)),
311
+ name: .editorImageAttachmentDidLoad,
312
+ object: nil
313
+ )
314
+
315
+ for name in [UIResponder.keyboardWillChangeFrameNotification, UIResponder.keyboardWillHideNotification] {
316
+ NotificationCenter.default.addObserver(
317
+ self,
318
+ selector: #selector(handleKeyboardFrameChange(_:)),
319
+ name: name,
320
+ object: nil
321
+ )
322
+ }
323
+
324
+ // Configure the text view as a Rust-controlled editor surface.
325
+ // UIKit smart-edit features mutate text storage outside our transaction
326
+ // pipeline and can race with stored-mark typing after toolbar actions.
327
+ setAutoCorrect(nil)
328
+ setAutoCapitalize(nil)
329
+ setKeyboardType(nil)
330
+ smartQuotesType = .no
331
+ smartDashesType = .no
332
+ smartInsertDeleteType = .no
333
+
334
+ isScrollEnabled = heightBehavior == .fixed
335
+ isEditable = true
336
+ isSelectable = true
337
+
338
+ font = baseFont
339
+ textColor = baseTextColor
340
+ backgroundColor = baseBackgroundColor
341
+ baseTextContainerInset = textContainerInset
342
+ baseLineFragmentPadding = textContainer.lineFragmentPadding
343
+ visibleSelectionTintColor = tintColor
344
+
345
+ // Register as the text storage delegate so we can detect unauthorized
346
+ // mutations (reconciliation fallback).
347
+ textStorage.delegate = self
348
+ ensureInternalTextViewDelegate()
349
+ textDragDelegate = self
350
+ textDropDelegate = self
351
+ addGestureRecognizer(caretPlacementTapRecognizer)
352
+ addGestureRecognizer(imageSelectionTapRecognizer)
353
+ installImageSelectionTapDependencies()
354
+
355
+ addSubview(placeholderLabel)
356
+ refreshPlaceholderVisibility()
357
+ refreshNativeSelectionChromeVisibility()
358
+ }
359
+
360
+ override func didMoveToWindow() {
361
+ super.didMoveToWindow()
362
+ if window == nil {
363
+ codeHighlightingSession.cancel()
364
+ keyboardFrameInScreen = nil
365
+ updateKeyboardInset()
366
+ } else {
367
+ scheduleCodeHighlighting()
368
+ }
369
+ installImageSelectionTapDependencies()
370
+ }
371
+
372
+ override func didAddSubview(_ subview: UIView) {
373
+ super.didAddSubview(subview)
374
+ installImageSelectionTapDependencies()
375
+ }
376
+
377
+ override func tintColorDidChange() {
378
+ super.tintColorDidChange()
379
+ if !hidesNativeSelectionChrome, tintColor.cgColor.alpha > 0 {
380
+ visibleSelectionTintColor = tintColor
381
+ }
382
+ }
383
+
384
+ // MARK: - Layout
385
+
386
+ override func layoutSubviews() {
387
+ super.layoutSubviews()
388
+ updateKeyboardInset()
389
+ styleContentView.frame = CGRect(x: 0, y: 0, width: bounds.width, height: max(bounds.height, contentSize.height))
390
+ let placeholderX = textContainerInset.left + textContainer.lineFragmentPadding
391
+ let placeholderY = textContainerInset.top
392
+ let placeholderWidth = max(
393
+ 0,
394
+ bounds.width - textContainerInset.left - textContainerInset.right - 2 * textContainer.lineFragmentPadding
395
+ )
396
+ if placeholderLabel.isHidden {
397
+ placeholderLabel.frame = CGRect(
398
+ x: placeholderX,
399
+ y: placeholderY,
400
+ width: placeholderWidth,
401
+ height: 0
402
+ )
403
+ } else {
404
+ let maxPlaceholderHeight = max(
405
+ 0,
406
+ bounds.height - textContainerInset.top - textContainerInset.bottom
407
+ )
408
+ let fittedHeight = placeholderLabel.sizeThatFits(
409
+ CGSize(width: placeholderWidth, height: CGFloat.greatestFiniteMagnitude)
410
+ ).height
411
+ placeholderLabel.frame = CGRect(
412
+ x: placeholderX,
413
+ y: placeholderY,
414
+ width: placeholderWidth,
415
+ height: min(maxPlaceholderHeight, ceil(fittedHeight))
416
+ )
417
+ }
418
+ if heightBehavior == .autoGrow, !isPreviewingImageResize {
419
+ let currentWidth = ceil(bounds.width)
420
+ if abs(currentWidth - lastAutoGrowMeasuredWidth) > 0.5 {
421
+ autoGrowHeightCheckIsDirty = true
422
+ lastAutoGrowMeasuredWidth = currentWidth
423
+ }
424
+ if autoGrowHeightCheckIsDirty {
425
+ notifyHeightChangeIfNeeded()
426
+ }
427
+ }
428
+ if !isPreviewingImageResize {
429
+ onViewportMayChange?()
430
+ }
431
+ }
432
+
433
+ deinit {
434
+ codeHighlightingSession.cancel()
435
+ NotificationCenter.default.removeObserver(self)
436
+ }
437
+
438
+ var keyboardFrameInScreen: CGRect?
439
+ var keyboardBottomInset: CGFloat = 0
440
+
441
+ override var contentOffset: CGPoint {
442
+ didSet {
443
+ if !isPreviewingImageResize {
444
+ onViewportMayChange?()
445
+ }
446
+ }
447
+ }
448
+
449
+ override func becomeFirstResponder() -> Bool {
450
+ let didBecomeFirstResponder = super.becomeFirstResponder()
451
+ if didBecomeFirstResponder {
452
+ ensureInternalTextViewDelegate()
453
+ clearNativeTextMutationAfterBlurWindow()
454
+ DispatchQueue.main.async { [weak self] in
455
+ self?.ensureInternalTextViewDelegate()
456
+ }
457
+ _ = normalizeSelectionForEmptyBlockAutocapitalizationIfNeeded()
458
+ recordAuthorizedSelectionIfPossible()
459
+ refreshTypingAttributesForSelection()
460
+ }
461
+ return didBecomeFirstResponder
462
+ }
463
+
464
+ override func resignFirstResponder() -> Bool {
465
+ ensureInternalTextViewDelegate()
466
+ _ = drainPendingNativeTextMutation(allowAfterBlur: false, allowWhileIntercepting: true)
467
+
468
+ let wasFirstResponder = isFirstResponder
469
+ if wasFirstResponder {
470
+ nativeTextMutationAfterBlurGeneration = nativeTextMutationGeneration
471
+ nativeTextMutationAfterBlurDeadline = ProcessInfo.processInfo.systemUptime
472
+ + nativeTextMutationAfterBlurGraceInterval
473
+ }
474
+
475
+ let didResignFirstResponder = super.resignFirstResponder()
476
+ if wasFirstResponder || didResignFirstResponder {
477
+ _ = drainPendingNativeTextMutation(allowAfterBlur: true, allowWhileIntercepting: true)
478
+ DispatchQueue.main.async { [weak self] in
479
+ guard let self else { return }
480
+ _ = self.drainPendingNativeTextMutation(
481
+ allowAfterBlur: true,
482
+ allowWhileIntercepting: true
483
+ )
484
+ }
485
+ }
486
+ return didResignFirstResponder
487
+ }
488
+
489
+ /// The core's `documentIsEmpty` from the most recent editor update, or nil
490
+ /// when the current render arrived without one.
491
+ var coreReportedDocumentIsEmpty: Bool?
492
+
493
+ override func caretRect(for position: UITextPosition) -> CGRect {
494
+ if hidesNativeSelectionChrome {
495
+ return .zero
496
+ }
497
+ let utf16Offset = offset(from: beginningOfDocument, to: position)
498
+ if isAtomBoundaryCaretOffset(utf16Offset) {
499
+ return .zero
500
+ }
501
+ // Noncontiguous layout can report estimated positions until the preceding text is laid out.
502
+ let layoutEnd = min(textStorage.length, max(0, utf16Offset) + 2)
503
+ layoutManager.ensureLayout(forCharacterRange: NSRange(location: 0, length: layoutEnd))
504
+ let rect = resolvedCaretReferenceRect(for: position)
505
+ guard rect.height > 0 else { return rect }
506
+
507
+ let caretFont = resolvedCaretFont(for: position)
508
+ let screenScale = window?.screen.scale ?? UIScreen.main.scale
509
+ let targetHeight = ceil(caretFont.lineHeight)
510
+ guard targetHeight > 0, targetHeight < rect.height else { return rect }
511
+
512
+ if let baselineY = caretBaselineY(for: position, referenceRect: rect) {
513
+ return Self.adjustedCaretRect(
514
+ from: rect,
515
+ baselineY: baselineY,
516
+ font: caretFont,
517
+ screenScale: screenScale
518
+ )
519
+ }
520
+
521
+ return Self.adjustedCaretRect(
522
+ from: rect,
523
+ font: caretFont,
524
+ screenScale: screenScale
525
+ )
526
+ }
527
+
528
+ override func closestPosition(to point: CGPoint) -> UITextPosition? {
529
+ if atomAttachmentRange(at: point) != nil,
530
+ let selectedTextRange,
531
+ selectedTextRange.isEmpty {
532
+ return selectedTextRange.start
533
+ }
534
+ return super.closestPosition(to: point)
535
+ }
536
+
537
+ override func closestPosition(
538
+ to point: CGPoint,
539
+ within range: UITextRange
540
+ ) -> UITextPosition? {
541
+ if atomAttachmentRange(at: point) != nil,
542
+ let selectedTextRange,
543
+ selectedTextRange.isEmpty {
544
+ return selectedTextRange.start
545
+ }
546
+ return super.closestPosition(to: point, within: range)
547
+ }
548
+
549
+ // MARK: - Input Interception: Text Insertion
550
+
551
+ /// Intercept text insertion. This is called for:
552
+ /// - Single character typing (including autocomplete insertions)
553
+ /// - Return/Enter key
554
+ /// - Dictation results
555
+ ///
556
+ /// Instead of calling `super.insertText()` (which would modify the
557
+ /// underlying text storage directly), we route through Rust.
558
+ override func insertText(_ text: String) {
559
+ ensureInternalTextViewDelegate()
560
+ if isApplyingRustState
561
+ || (!isReplayingDeferredInsertText && !deferredInsertTexts.isEmpty) {
562
+ enqueueDeferredInsertText(text)
563
+ return
564
+ }
565
+ guard editorId != 0 else {
566
+ super.insertText(text)
567
+ return
568
+ }
569
+ guard finishExternalTextCompositionBeforeInteractionIfNeeded() else { return }
570
+ guard flushPendingNativeTextMutationCommitIfNeeded() else { return }
571
+ if !isReplayingDeferredInsertText, !deferredInsertTexts.isEmpty {
572
+ enqueueDeferredInsertText(text)
573
+ return
574
+ }
575
+ guard !isCollapsedAtomBoundary(selectedUtf16Range()) else { return }
576
+
577
+ if interceptReturnInput(text) { return }
578
+
579
+ if markedTextReplacementScalarRange != nil || markedTextRange != nil {
580
+ let replacementRange = trackedMarkedTextReplacementRange()
581
+ finishTransientMarkedTextMutation()
582
+ _ = commitMarkedText(text, replacementRange: replacementRange)
583
+ return
584
+ }
585
+
586
+ let scalarPos = PositionBridge.cursorScalarOffset(in: self)
587
+ Self.inputLog.debug(
588
+ "[insertText] text=\(self.preview(text), privacy: .public) scalarPos=\(scalarPos) selection=\(self.selectionSummary(), privacy: .public) textState=\(self.textSnapshotSummary(), privacy: .public)"
589
+ )
590
+
591
+ if let selectedRange = selectedTextRange, !selectedRange.isEmpty {
592
+ let range = PositionBridge.textRangeToScalarRange(selectedRange, in: self)
593
+ performInterceptedInput {
594
+ let updateJSON = EditorV2Shadow.replaceTextScalar(
595
+ id: editorId,
596
+ scalarFrom: range.from,
597
+ scalarTo: range.to,
598
+ text: text
599
+ )
600
+ applyUpdateJSON(updateJSON)
601
+ }
602
+ } else {
603
+ performInterceptedInput {
604
+ insertTextInRust(text, at: scalarPos)
605
+ }
606
+ }
607
+ }
608
+
609
+ override var keyCommands: [UIKeyCommand]? {
610
+ [
611
+ UIKeyCommand(
612
+ input: "\r",
613
+ modifierFlags: [.shift],
614
+ action: #selector(handleHardBreakKeyCommand)
615
+ ),
616
+ UIKeyCommand(
617
+ input: "\t",
618
+ modifierFlags: [],
619
+ action: #selector(handleIndentKeyCommand)
620
+ ),
621
+ UIKeyCommand(
622
+ input: "\t",
623
+ modifierFlags: [.shift],
624
+ action: #selector(handleOutdentKeyCommand)
625
+ )
626
+ ]
627
+ }
628
+
629
+ // MARK: - Input Interception: Deletion
630
+
631
+ /// Intercept backward deletion (Backspace key).
632
+ ///
633
+ /// If there's a range selection, delete the range. If it's a cursor,
634
+ /// delete the character (grapheme cluster) before the cursor.
635
+ override func deleteBackward() {
636
+ ensureInternalTextViewDelegate()
637
+ guard !isApplyingRustState else {
638
+ super.deleteBackward()
639
+ return
640
+ }
641
+ guard editorId != 0 else {
642
+ super.deleteBackward()
643
+ return
644
+ }
645
+ guard finishExternalTextCompositionBeforeInteractionIfNeeded() else { return }
646
+ guard flushPendingNativeTextMutationCommitIfNeeded() else { return }
647
+ guard !isCollapsedAtomBoundary(selectedUtf16Range()) else { return }
648
+
649
+ if markedTextReplacementScalarRange != nil || markedTextRange != nil {
650
+ performTransientTextMutation {
651
+ super.deleteBackward()
652
+ }
653
+ refreshMarkedTextCompositionText()
654
+ isComposing = markedTextRange != nil || markedTextReplacementScalarRange != nil
655
+ return
656
+ }
657
+
658
+ guard let selectedRange = selectedTextRange else { return }
659
+ Self.inputLog.debug(
660
+ "[deleteBackward] selection=\(self.selectionSummary(), privacy: .public) textState=\(self.textSnapshotSummary(), privacy: .public)"
661
+ )
662
+
663
+ if !selectedRange.isEmpty {
664
+ let range = PositionBridge.textRangeToScalarRange(selectedRange, in: self)
665
+ performInterceptedInput {
666
+ deleteScalarRangeInRust(from: range.from, to: range.to)
667
+ }
668
+ } else {
669
+ // Cursor: delete one grapheme cluster backward. The engine's caret
670
+ // is the authority — in an empty block UIKit's own caret is parked
671
+ // ahead of the block placeholder for autocapitalization and does
672
+ // not address the same position.
673
+ let cursorPos = currentLogicalScalarSelection()?.head
674
+ ?? PositionBridge.cursorScalarOffset(in: self)
675
+ if cursorPos == 0 {
676
+ performInterceptedInput {
677
+ deleteBackwardAtSelectionScalarInRust(anchor: cursorPos, head: cursorPos)
678
+ }
679
+ return
680
+ }
681
+
682
+ let cursorUtf16Offset = offset(from: beginningOfDocument, to: selectedRange.start)
683
+ if cursorUtf16Offset <= 0 {
684
+ performInterceptedInput {
685
+ deleteBackwardAtSelectionScalarInRust(anchor: cursorPos, head: cursorPos)
686
+ }
687
+ return
688
+ }
689
+ if let marker = PositionBridge.virtualListMarker(
690
+ atUtf16Offset: cursorUtf16Offset,
691
+ in: self
692
+ ), marker.paragraphStartUtf16 == cursorUtf16Offset {
693
+ performInterceptedInput {
694
+ deleteScalarRangeInRust(from: cursorPos - 1, to: cursorPos)
695
+ }
696
+ return
697
+ }
698
+
699
+ if let deleteRange = trailingVoidBlockDeleteRangeForBackwardDelete(
700
+ cursorUtf16Offset: cursorUtf16Offset
701
+ ) {
702
+ performInterceptedInput {
703
+ deleteScalarRangeInRust(from: deleteRange.from, to: deleteRange.to)
704
+ }
705
+ return
706
+ }
707
+
708
+ if let deleteRange = adjacentVoidBlockDeleteRangeForBackwardDelete(
709
+ cursorUtf16Offset: cursorUtf16Offset,
710
+ cursorScalar: cursorPos
711
+ ) {
712
+ performInterceptedInput {
713
+ deleteScalarRangeInRust(from: deleteRange.from, to: deleteRange.to)
714
+ }
715
+ return
716
+ }
717
+
718
+ if cursorUtf16Offset > 0,
719
+ (textStorage.string as NSString).character(at: cursorUtf16Offset - 1) == 0x200B {
720
+ performInterceptedInput {
721
+ deleteBackwardAtSelectionScalarInRust(anchor: cursorPos, head: cursorPos)
722
+ }
723
+ return
724
+ }
725
+
726
+ // Find the start of the previous grapheme cluster.
727
+ // We need to figure out how many scalars the previous grapheme occupies.
728
+ // Use UITextView's tokenizer to find the previous grapheme boundary.
729
+ guard let prevPos = position(from: selectedRange.start, offset: -1) else { return }
730
+ let prevScalar = PositionBridge.textViewToScalar(prevPos, in: self)
731
+
732
+ performInterceptedInput {
733
+ if prevScalar < cursorPos {
734
+ deleteScalarRangeInRust(from: prevScalar, to: cursorPos)
735
+ } else {
736
+ deleteBackwardAtSelectionScalarInRust(anchor: cursorPos, head: cursorPos)
737
+ }
738
+ }
739
+ }
740
+ }
741
+
742
+ // MARK: - Input Interception: Replace (Autocorrect)
743
+
744
+ /// Intercept text replacement. This is called when:
745
+ /// - Autocorrect replaces a word
746
+ /// - User accepts a spelling suggestion
747
+ /// - Programmatic text replacement
748
+ ///
749
+ /// We route the replacement through Rust to keep the document model in sync.
750
+ override func replace(_ range: UITextRange, withText text: String) {
751
+ ensureInternalTextViewDelegate()
752
+ guard !isApplyingRustState else {
753
+ super.replace(range, withText: text)
754
+ return
755
+ }
756
+ guard editorId != 0 else {
757
+ super.replace(range, withText: text)
758
+ return
759
+ }
760
+ guard finishExternalTextCompositionBeforeInteractionIfNeeded() else { return }
761
+ guard flushPendingNativeTextMutationCommitIfNeeded() else { return }
762
+ let replacementUtf16Range = NSRange(
763
+ location: offset(from: beginningOfDocument, to: range.start),
764
+ length: offset(from: range.start, to: range.end)
765
+ )
766
+ guard !isCollapsedAtomBoundary(replacementUtf16Range) else { return }
767
+
768
+ if interceptReturnInput(text, replacing: range) { return }
769
+
770
+ if markedTextReplacementScalarRange != nil || markedTextRange != nil {
771
+ let replacementRange = trackedMarkedTextReplacementRange()
772
+ finishTransientMarkedTextMutation()
773
+ _ = commitMarkedText(text, replacementRange: replacementRange)
774
+ return
775
+ }
776
+
777
+ let scalarRange = PositionBridge.textRangeToScalarRange(range, in: self)
778
+ let replacementStartUtf16 = replacementUtf16Range.location
779
+ let replacementEndUtf16 = NSMaxRange(replacementUtf16Range)
780
+ let preservesAcceptedSpace = textStorage.string == lastAuthorizedText
781
+ && shouldPreserveAcceptedAutocorrectSpace(
782
+ authorizedText: lastAuthorizedText as NSString,
783
+ replacementStartUtf16: replacementStartUtf16,
784
+ authorizedEndUtf16: replacementEndUtf16,
785
+ replacementText: text,
786
+ rawSelectionUtf16Range: selectedUtf16Range(),
787
+ authorizedSelectionUtf16Range: lastAuthorizedSelectedUtf16Range,
788
+ acceptedCaretUtf16Offset: replacementEndUtf16
789
+ )
790
+ let replacementText = preservesAcceptedSpace ? text + " " : text
791
+ Self.inputLog.debug(
792
+ "[replace] text=\(self.preview(replacementText), privacy: .public) scalarRange=\(scalarRange.from)-\(scalarRange.to) selection=\(self.selectionSummary(), privacy: .public) textState=\(self.textSnapshotSummary(), privacy: .public)"
793
+ )
794
+
795
+ performInterceptedInput {
796
+ let updateJSON = EditorV2Shadow.replaceTextScalar(
797
+ id: editorId,
798
+ scalarFrom: scalarRange.from,
799
+ scalarTo: scalarRange.to,
800
+ text: replacementText
801
+ )
802
+ applyUpdateJSON(updateJSON)
803
+ }
804
+ }
805
+
806
+ // MARK: - Composition Handling (CJK / IME)
807
+
808
+ /// Called when the input method sets marked (composing) text.
809
+ ///
810
+ /// During CJK input, the user composes characters incrementally. We let
811
+ /// UITextView display the composing text normally (with its underline
812
+ /// decoration). The text is NOT sent to Rust during composition.
813
+ override func setMarkedText(_ markedText: String?, selectedRange: NSRange) {
814
+ ensureInternalTextViewDelegate()
815
+ guard finishExternalTextCompositionBeforeInteractionIfNeeded() else { return }
816
+ if markedText != nil {
817
+ guard flushPendingNativeTextMutationCommitIfNeeded() else { return }
818
+ captureMarkedTextReplacementRangeIfNeeded()
819
+ } else if markedTextReplacementScalarRange == nil, markedTextRange == nil {
820
+ guard flushPendingNativeTextMutationCommitIfNeeded() else { return }
821
+ }
822
+ isComposing = markedText != nil || markedTextReplacementScalarRange != nil
823
+ Self.inputLog.debug(
824
+ "[setMarkedText] marked=\(self.preview(markedText ?? ""), privacy: .public) nsRange=\(selectedRange.location),\(selectedRange.length) selection=\(self.selectionSummary(), privacy: .public)"
825
+ )
826
+ if markedText == nil {
827
+ // Some keyboard paths finalize composition by clearing marked text
828
+ // instead of calling unmarkText().
829
+ let composedText = validatedTrackedMarkedTextForCommit()
830
+ let replacementRange = trackedMarkedTextReplacementRange()
831
+ performTransientTextMutation {
832
+ super.setMarkedText(nil, selectedRange: selectedRange)
833
+ }
834
+ clearMarkedTextTracking()
835
+ if shouldCommitMarkedText(composedText, replacementRange: replacementRange) {
836
+ _ = commitMarkedText(composedText ?? "", replacementRange: replacementRange)
837
+ } else {
838
+ restoreAuthorizedTextAfterCancelledCompositionIfNeeded()
839
+ }
840
+ return
841
+ }
842
+
843
+ performTransientTextMutation {
844
+ super.setMarkedText(markedText, selectedRange: selectedRange)
845
+ }
846
+ refreshMarkedTextCompositionText(fallback: markedText)
847
+ }
848
+
849
+ /// Called when composition is finalized (user selects a candidate or
850
+ /// presses space/enter to commit).
851
+ ///
852
+ /// At this point, the composed text is final. We capture it and commit it
853
+ /// to Rust at the original replacement range captured before UIKit mutated
854
+ /// the transient text storage.
855
+ override func unmarkText() {
856
+ ensureInternalTextViewDelegate()
857
+ if externalTextComposition != nil {
858
+ guard finishExternalTextCompositionBeforeInteractionIfNeeded() else { return }
859
+ return
860
+ }
861
+ let composedText = currentMarkedTextForCommit()
862
+ let replacementRange = trackedMarkedTextReplacementRange()
863
+
864
+ finishTransientMarkedTextMutation()
865
+
866
+ if let composed = composedText, !composed.isEmpty {
867
+ Self.inputLog.debug(
868
+ "[unmarkText] composed=\(self.preview(composed), privacy: .public) replacement=\(self.previewMarkedTextReplacementRange(replacementRange), privacy: .public) selection=\(self.selectionSummary(), privacy: .public)"
869
+ )
870
+ _ = commitMarkedText(composed, replacementRange: replacementRange)
871
+ } else if shouldCommitMarkedText(composedText, replacementRange: replacementRange) {
872
+ _ = commitMarkedText(composedText ?? "", replacementRange: replacementRange)
873
+ } else {
874
+ restoreAuthorizedTextAfterCancelledCompositionIfNeeded()
875
+ }
876
+ }
877
+
878
+ // MARK: - Paste Handling
879
+
880
+ /// Intercept paste operations to route content through Rust.
881
+ ///
882
+ /// Attempts to extract HTML from the pasteboard first (for rich text paste),
883
+ /// falling back to plain text.
884
+ override func paste(_ sender: Any?) {
885
+ ensureInternalTextViewDelegate()
886
+ guard editorId != 0 else {
887
+ super.paste(sender)
888
+ return
889
+ }
890
+ guard finishExternalTextCompositionBeforeInteractionIfNeeded() else { return }
891
+ guard prepareForExternalEditorUpdate() else { return }
892
+
893
+ Self.inputLog.debug(
894
+ "[paste] selection=\(self.selectionSummary(), privacy: .public) textState=\(self.textSnapshotSummary(), privacy: .public)"
895
+ )
896
+
897
+ let pasteboard = UIPasteboard.general
898
+
899
+ if let htmlData = pasteboard.data(forPasteboardType: "public.html"),
900
+ let html = String(data: htmlData, encoding: .utf8) {
901
+ performInterceptedInput {
902
+ pasteHTML(html)
903
+ }
904
+ return
905
+ }
906
+
907
+ if let rtfData = pasteboard.data(forPasteboardType: "public.rtf") {
908
+ if let attrStr = try? NSAttributedString(
909
+ data: rtfData,
910
+ options: [.documentType: NSAttributedString.DocumentType.rtf],
911
+ documentAttributes: nil
912
+ ) {
913
+ if let htmlData = try? attrStr.data(
914
+ from: NSRange(location: 0, length: attrStr.length),
915
+ documentAttributes: [.documentType: NSAttributedString.DocumentType.html]
916
+ ), let html = String(data: htmlData, encoding: .utf8) {
917
+ performInterceptedInput {
918
+ if !pasteHTML(html, detectContentChange: true),
919
+ !attrStr.string.isEmpty {
920
+ pastePlainText(attrStr.string)
921
+ }
922
+ }
923
+ return
924
+ }
925
+ }
926
+ }
927
+
928
+ if let text = pasteboard.string {
929
+ performInterceptedInput {
930
+ pastePlainText(text)
931
+ }
932
+ return
933
+ }
934
+ }
935
+
936
+ // MARK: - Private: Rust Integration
937
+
938
+ var isInterceptingInput: Bool {
939
+ interceptedInputDepth > 0
940
+ }
941
+
942
+ // MARK: - Applying Rust State
943
+
944
+ /// Whether the document is the lone empty block whose caret the
945
+ /// autocapitalization nudge parks ahead of the placeholder. That block
946
+ /// holds exactly one caret position, which is what makes a cached scalar
947
+ /// safe to trust there even though the UIKit offset disagrees with it.
948
+ var isLoneEmptyPlaceholderBlock: Bool {
949
+ textStorage.length == 1
950
+ && textStorage.string.unicodeScalars.elementsEqual([Self.emptyBlockPlaceholderScalar])
951
+ }
952
+
953
+ }