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

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 (68) hide show
  1. package/android/build.gradle +35 -0
  2. package/android/src/main/java/com/apollohg/editor/CaretGeometry.kt +2 -2
  3. package/android/src/main/java/com/apollohg/editor/EditorEditText.kt +138 -597
  4. package/android/src/main/java/com/apollohg/editor/EditorInputConnection.kt +13 -3
  5. package/android/src/main/java/com/apollohg/editor/InputSnapshotSupport.kt +6 -0
  6. package/android/src/main/java/com/apollohg/editor/NativeBlockEditorSurface.kt +1086 -0
  7. package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +1 -1
  8. package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +26 -50
  9. package/android/src/main/java/com/apollohg/editor/NativeImageLoader.kt +89 -0
  10. package/android/src/main/java/com/apollohg/editor/RemoteSelectionOverlayView.kt +31 -66
  11. package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +98 -33
  12. package/dist/EditorToolbar.d.ts +1 -1
  13. package/dist/EditorToolbar.js +19 -0
  14. package/dist/NativeEditorBridge.d.ts +62 -35
  15. package/dist/NativeEditorBridge.js +122 -79
  16. package/dist/NativeProseViewer.d.ts +13 -34
  17. package/dist/NativeProseViewer.js +40 -465
  18. package/dist/NativeRichTextEditor.d.ts +10 -1
  19. package/dist/NativeRichTextEditor.js +72 -34
  20. package/dist/index.d.ts +2 -2
  21. package/dist/index.js +2 -1
  22. package/dist/schemas.d.ts +11 -0
  23. package/dist/schemas.js +149 -0
  24. package/dist/useNativeEditor.d.ts +3 -3
  25. package/dist/useNativeEditor.js +3 -3
  26. package/ios/EditorCore.xcframework/ios-arm64/libeditor_core.a +0 -0
  27. package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a +0 -0
  28. package/ios/Generated_editor_core.swift +97 -2
  29. package/ios/NativeBlockEditorSurface.swift +1393 -0
  30. package/ios/NativeEditorExpoView.swift +3 -3
  31. package/ios/NativeEditorModule.swift +30 -45
  32. package/ios/NativeInputSupport.swift +396 -0
  33. package/ios/PositionBridge.swift +4 -4
  34. package/ios/RichTextEditorView.swift +333 -786
  35. package/ios/editor_coreFFI/editor_coreFFI.h +147 -70
  36. package/package.json +1 -1
  37. package/rust/android/arm64-v8a/libeditor_core.so +0 -0
  38. package/rust/android/armeabi-v7a/libeditor_core.so +0 -0
  39. package/rust/android/x86_64/libeditor_core.so +0 -0
  40. package/rust/bindings/kotlin/uniffi/editor_core/editor_core.kt +2087 -1399
  41. package/android/src/androidTest/AndroidManifest.xml +0 -8
  42. package/android/src/androidTest/java/com/apollohg/editor/MeasureHeightInstrumentedTest.kt +0 -53
  43. package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceCollaborationInitialSyncTest.kt +0 -241
  44. package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceImeRegressionTest.kt +0 -338
  45. package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceOutsideTapTest.kt +0 -789
  46. package/android/src/androidTest/java/com/apollohg/editor/NativeDevicePerformanceTest.kt +0 -350
  47. package/android/src/androidTest/java/com/apollohg/editor/NativeEditorOutsideTapActivity.kt +0 -5
  48. package/android/src/main/java/com/apollohg/editor/NativeProseViewerExpoView.kt +0 -315
  49. package/android/src/main/java/com/apollohg/editor/RenderBridge.kt +0 -2172
  50. package/android/src/sharedTest/java/com/apollohg/editor/NativePerformanceSupport.kt +0 -482
  51. package/android/src/test/java/com/apollohg/editor/CaretGeometryTest.kt +0 -137
  52. package/android/src/test/java/com/apollohg/editor/EditorEditTextHardwareKeyTest.kt +0 -321
  53. package/android/src/test/java/com/apollohg/editor/EditorInputConnectionTest.kt +0 -4202
  54. package/android/src/test/java/com/apollohg/editor/NativeEditorExpoViewTest.kt +0 -2581
  55. package/android/src/test/java/com/apollohg/editor/NativeEditorModuleTest.kt +0 -27
  56. package/android/src/test/java/com/apollohg/editor/NativePerformanceTest.kt +0 -197
  57. package/android/src/test/java/com/apollohg/editor/NativeProseViewerExpoViewTest.kt +0 -84
  58. package/android/src/test/java/com/apollohg/editor/NativeToolbarTest.kt +0 -544
  59. package/android/src/test/java/com/apollohg/editor/PositionBridgeTest.kt +0 -461
  60. package/android/src/test/java/com/apollohg/editor/RenderBridgeTest.kt +0 -2054
  61. package/android/src/test/java/com/apollohg/editor/RichTextEditorViewTest.kt +0 -1367
  62. package/ios/EditorLayoutManager.swift +0 -774
  63. package/ios/NativeProseViewerExpoView.swift +0 -276
  64. package/ios/RenderBridge.swift +0 -1708
  65. package/ios/Tests/NativePerformanceTests.swift +0 -687
  66. package/ios/Tests/PositionBridgeTests.swift +0 -706
  67. package/ios/Tests/RenderBridgeTests.swift +0 -2236
  68. package/ios/Tests/RichTextEditorViewTests.swift +0 -6548
@@ -1,27 +0,0 @@
1
- package com.openeditor.editor
2
-
3
- import org.junit.Assert.assertEquals
4
- import org.junit.Assert.assertNull
5
- import org.junit.Test
6
-
7
- class NativeEditorModuleTest {
8
- @Test
9
- fun `native unsigned argument helpers reject negative values`() {
10
- assertNull(nativeULong(-1))
11
- assertNull(nativeUInt(-1))
12
- }
13
-
14
- @Test
15
- fun `native unsigned argument helpers keep non-negative values`() {
16
- assertEquals(0UL, nativeULong(0))
17
- assertEquals(42UL, nativeULong(42))
18
- assertEquals(0U, nativeUInt(0))
19
- assertEquals(42U, nativeUInt(42))
20
- }
21
-
22
- @Test
23
- fun `native argument error returns bridge parseable json`() {
24
- assertEquals("{\"error\":\"invalid editor id\"}", nativeArgumentError("editor id"))
25
- assertEquals("{\"error\":\"invalid position\"}", nativeArgumentError("position"))
26
- }
27
- }
@@ -1,197 +0,0 @@
1
- package com.openeditor.editor
2
-
3
- import android.graphics.Bitmap
4
- import android.graphics.Canvas
5
- import android.graphics.Color
6
- import android.view.View
7
- import android.view.ViewGroup
8
- import org.junit.Assert.assertEquals
9
- import org.junit.Assert.assertFalse
10
- import org.junit.Assert.assertNotNull
11
- import org.junit.Assert.assertTrue
12
- import org.junit.Test
13
- import org.junit.runner.RunWith
14
- import org.robolectric.RobolectricTestRunner
15
- import org.robolectric.RuntimeEnvironment
16
- import org.robolectric.annotation.Config
17
-
18
- @RunWith(RobolectricTestRunner::class)
19
- @Config(sdk = [34])
20
- class NativePerformanceTest {
21
- private val context = RuntimeEnvironment.getApplication()
22
- private val baseFontSize = 16f
23
- private val textColor = Color.BLACK
24
-
25
- @Test
26
- fun `performance - render bridge large document`() {
27
- val renderJson = NativePerformanceFixtureFactory.largeRenderJson()
28
-
29
- val stats = measureOperation("renderBridgeLargeDocument") {
30
- val spannable = RenderBridge.buildSpannable(
31
- renderJson,
32
- baseFontSize,
33
- textColor,
34
- density = context.resources.displayMetrics.density
35
- )
36
-
37
- assertTrue("rendered spannable should not be empty", spannable.isNotEmpty())
38
- if (spannable.isNotEmpty()) {
39
- spannable.getSpans(
40
- 0,
41
- minOf(spannable.length, 1),
42
- Any::class.java
43
- )
44
- }
45
- }
46
-
47
- assertTrue("average render time should be positive", stats.averageMillis > 0.0)
48
- }
49
-
50
- @Test
51
- fun `performance - apply update json large document`() {
52
- val updateJson = NativePerformanceFixtureFactory.largeUpdateJson()
53
- val editText = EditorEditText(context).apply {
54
- layoutParams = ViewGroup.LayoutParams(
55
- ViewGroup.LayoutParams.MATCH_PARENT,
56
- ViewGroup.LayoutParams.WRAP_CONTENT
57
- )
58
- setBaseStyle(baseFontSize, textColor, Color.WHITE)
59
- captureApplyUpdateTraceForTesting = true
60
- }
61
- val traceSamples = mutableListOf<EditorEditText.ApplyUpdateTrace>()
62
-
63
- editText.applyUpdateJSON(updateJson, notifyListener = false)
64
- layoutView(editText, widthPx = 1080, heightPx = 2400, heightMode = View.MeasureSpec.AT_MOST)
65
-
66
- val stats = measureOperation("applyUpdateJsonLargeDocument") {
67
- editText.applyUpdateJSON(updateJson, notifyListener = false)
68
- layoutView(editText, widthPx = 1080, heightPx = 2400, heightMode = View.MeasureSpec.AT_MOST)
69
- editText.lastApplyUpdateTrace()?.let(traceSamples::add)
70
-
71
- assertFalse("edit text should contain rendered content", editText.text.isNullOrEmpty())
72
- assertNotNull("edit text should have a layout after applying update", editText.layout)
73
- }
74
- println(
75
- ApplyUpdateTraceStats(
76
- name = "applyUpdateJsonLargeDocument.breakdown",
77
- traces = traceSamples.takeLast(5)
78
- ).summaryString()
79
- )
80
-
81
- assertTrue("average applyUpdateJSON time should be positive", stats.averageMillis > 0.0)
82
- }
83
-
84
- @Test
85
- fun `performance - apply render patch large document`() {
86
- val initialUpdateJson = NativePerformanceFixtureFactory.largeUpdateJson()
87
- val patchedUpdateJson = NativePerformanceFixtureFactory.largePatchedUpdateJson()
88
- val editText = EditorEditText(context).apply {
89
- layoutParams = ViewGroup.LayoutParams(
90
- ViewGroup.LayoutParams.MATCH_PARENT,
91
- ViewGroup.LayoutParams.WRAP_CONTENT
92
- )
93
- setBaseStyle(baseFontSize, textColor, Color.WHITE)
94
- captureApplyUpdateTraceForTesting = true
95
- }
96
- val traceSamples = mutableListOf<EditorEditText.ApplyUpdateTrace>()
97
-
98
- val stats = measureOperation(
99
- name = "applyRenderPatchLargeDocument",
100
- beforeEach = {
101
- editText.applyUpdateJSON(initialUpdateJson, notifyListener = false)
102
- layoutView(editText, widthPx = 1080, heightPx = 2400, heightMode = View.MeasureSpec.AT_MOST)
103
- }
104
- ) {
105
- editText.applyUpdateJSON(patchedUpdateJson, notifyListener = false)
106
- layoutView(editText, widthPx = 1080, heightPx = 2400, heightMode = View.MeasureSpec.AT_MOST)
107
- editText.lastApplyUpdateTrace()?.let(traceSamples::add)
108
-
109
- assertFalse("edit text should contain rendered content", editText.text.isNullOrEmpty())
110
- }
111
- println(
112
- ApplyUpdateTraceStats(
113
- name = "applyRenderPatchLargeDocument.breakdown",
114
- traces = traceSamples.takeLast(5)
115
- ).summaryString()
116
- )
117
-
118
- assertTrue("average patch apply time should be positive", stats.averageMillis > 0.0)
119
- }
120
-
121
- @Test
122
- fun `performance - remote selection overlay refresh multi peer large document`() {
123
- val updateJson = NativePerformanceFixtureFactory.largeUpdateJson()
124
- val richTextView = RichTextEditorView(context).apply {
125
- configure(textSizePx = baseFontSize, textColor = textColor, backgroundColor = Color.WHITE)
126
- setRemoteSelectionEditorIdForTesting(1L)
127
- setRemoteSelectionScalarResolverForTesting { _, docPos -> docPos }
128
- }
129
-
130
- richTextView.editorEditText.applyUpdateJSON(updateJson, notifyListener = false)
131
- layoutView(richTextView, widthPx = 1080, heightPx = 1600, heightMode = View.MeasureSpec.EXACTLY)
132
-
133
- val totalScalar = richTextView.editorEditText.text?.length ?: 0
134
- val selections = NativePerformanceFixtureFactory.remoteSelections(
135
- totalScalar,
136
- peerCount = 24,
137
- selectionWidth = 24
138
- )
139
- richTextView.setRemoteSelections(selections)
140
- layoutView(richTextView, widthPx = 1080, heightPx = 1600, heightMode = View.MeasureSpec.EXACTLY)
141
-
142
- val bitmap = Bitmap.createBitmap(1080, 1600, Bitmap.Config.ARGB_8888)
143
- val canvas = Canvas(bitmap)
144
-
145
- val stats = measureOperation("remoteSelectionOverlayRefreshMultiPeerLargeDocument") {
146
- bitmap.eraseColor(Color.TRANSPARENT)
147
- richTextView.setRemoteSelections(selections)
148
- layoutView(richTextView, widthPx = 1080, heightPx = 1600, heightMode = View.MeasureSpec.EXACTLY)
149
- richTextView.draw(canvas)
150
-
151
- val snapshots = richTextView.remoteSelectionDebugSnapshotsForTesting()
152
- assertTrue(
153
- "remote selection overlay should resolve visible carets",
154
- snapshots.any { it.caretRect != null }
155
- )
156
- assertEquals("expected one snapshot per peer", selections.size, snapshots.size)
157
- }
158
-
159
- assertTrue("average remote selection refresh time should be positive", stats.averageMillis > 0.0)
160
- }
161
-
162
- private fun layoutView(
163
- view: View,
164
- widthPx: Int,
165
- heightPx: Int,
166
- heightMode: Int
167
- ) {
168
- val widthSpec = View.MeasureSpec.makeMeasureSpec(widthPx, View.MeasureSpec.EXACTLY)
169
- val heightSpec = View.MeasureSpec.makeMeasureSpec(heightPx, heightMode)
170
- view.measure(widthSpec, heightSpec)
171
- view.layout(0, 0, view.measuredWidth, view.measuredHeight)
172
- }
173
-
174
- private fun measureOperation(
175
- name: String,
176
- warmupIterations: Int = 3,
177
- measuredIterations: Int = 5,
178
- beforeEach: (() -> Unit)? = null,
179
- block: () -> Unit
180
- ): TimingStats {
181
- repeat(warmupIterations) {
182
- beforeEach?.invoke()
183
- block()
184
- }
185
-
186
- val samples = MutableList(measuredIterations) {
187
- beforeEach?.invoke()
188
- val startedAt = System.nanoTime()
189
- block()
190
- System.nanoTime() - startedAt
191
- }
192
-
193
- val stats = TimingStats(name, samples)
194
- println(stats.summaryString())
195
- return stats
196
- }
197
- }
@@ -1,84 +0,0 @@
1
- package com.openeditor.editor
2
-
3
- import android.content.Context
4
- import android.view.View
5
- import expo.modules.core.ModuleRegistry
6
- import expo.modules.kotlin.AppContext
7
- import expo.modules.kotlin.ModulesProvider
8
- import expo.modules.kotlin.modules.Module
9
- import org.junit.Assert.assertEquals
10
- import org.junit.Assert.assertTrue
11
- import org.junit.Test
12
- import org.junit.runner.RunWith
13
- import org.robolectric.RobolectricTestRunner
14
- import org.robolectric.RuntimeEnvironment
15
- import org.robolectric.annotation.Config
16
- import java.lang.ref.WeakReference
17
-
18
- @RunWith(RobolectricTestRunner::class)
19
- @Config(sdk = [34])
20
- class NativeProseViewerExpoViewTest {
21
- @Test
22
- fun `viewer measure ignores stale exact parent height`() {
23
- val expoContext = testExpoContext(RuntimeEnvironment.getApplication())
24
- val view = NativeProseViewerExpoView(expoContext.context, expoContext.appContext)
25
- view.suppressContentHeightEventsForTesting = true
26
- view.setRenderJson(paragraphRenderJson(LONG_MESSAGE_TEXT))
27
-
28
- val widthSpec = View.MeasureSpec.makeMeasureSpec(600, View.MeasureSpec.EXACTLY)
29
- val wrapHeightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
30
- view.measure(widthSpec, wrapHeightSpec)
31
- view.layout(0, 0, view.measuredWidth, view.measuredHeight)
32
- val contentHeight = view.measuredHeight
33
-
34
- assertTrue(contentHeight > 0)
35
-
36
- val staleExactHeightSpec = View.MeasureSpec.makeMeasureSpec(
37
- contentHeight + 480,
38
- View.MeasureSpec.EXACTLY
39
- )
40
- view.measure(widthSpec, staleExactHeightSpec)
41
-
42
- assertEquals(contentHeight, view.measuredHeight)
43
- }
44
-
45
- private data class TestExpoContext(
46
- val context: Context,
47
- val appContext: AppContext
48
- )
49
-
50
- private fun testExpoContext(context: Context): TestExpoContext {
51
- val reactContext = Class
52
- .forName("com.facebook.react.bridge.BridgeReactContext")
53
- .getConstructor(Context::class.java)
54
- .newInstance(context) as Context
55
-
56
- val modulesProvider = object : ModulesProvider {
57
- override fun getModulesList(): List<Class<out Module>> = emptyList()
58
- }
59
- val constructor = AppContext::class.java.constructors.first { constructor ->
60
- constructor.parameterTypes.size == 3
61
- }
62
- val appContext = constructor.newInstance(
63
- modulesProvider,
64
- ModuleRegistry(emptyList(), emptyList()),
65
- WeakReference(reactContext)
66
- ) as AppContext
67
- return TestExpoContext(reactContext, appContext)
68
- }
69
-
70
- private fun paragraphRenderJson(text: String): String =
71
- """
72
- [
73
- {"type":"blockStart","nodeType":"paragraph","depth":0},
74
- {"type":"textRun","text":"$text","marks":[]},
75
- {"type":"blockEnd"}
76
- ]
77
- """.trimIndent()
78
-
79
- private companion object {
80
- private val LONG_MESSAGE_TEXT = List(80) {
81
- "Long Android viewer message"
82
- }.joinToString(" ")
83
- }
84
- }