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

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 +1445 -0
  30. package/ios/NativeEditorExpoView.swift +3 -3
  31. package/ios/NativeEditorModule.swift +31 -46
  32. package/ios/NativeInputSupport.swift +396 -0
  33. package/ios/PositionBridge.swift +4 -4
  34. package/ios/RichTextEditorView.swift +352 -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,276 +0,0 @@
1
- import ExpoModulesCore
2
- import UIKit
3
-
4
- final class NativeProseViewerExpoView: ExpoView {
5
- let onContentHeightChange = EventDispatcher()
6
- let onPressLink = EventDispatcher()
7
- let onPressMention = EventDispatcher()
8
-
9
- private let textView = EditorTextView(frame: .zero, textContainer: nil)
10
- private var lastRenderJSON: String?
11
- private var lastThemeJSON: String?
12
- private var lastEmittedContentHeight: CGFloat = 0
13
- private var lastMeasuredWidth: CGFloat = 0
14
- private var collapsesWhenEmpty = true
15
- private var isCollapsedEmptyContent = false
16
- private var enableLinkTaps = true
17
- private var interceptLinkTaps = false
18
-
19
- private lazy var interactiveTapRecognizer: UITapGestureRecognizer = {
20
- let recognizer = UITapGestureRecognizer(
21
- target: self,
22
- action: #selector(handleInteractiveTap(_:))
23
- )
24
- recognizer.cancelsTouchesInView = false
25
- return recognizer
26
- }()
27
-
28
- required init(appContext: AppContext? = nil) {
29
- super.init(appContext: appContext)
30
- setupView()
31
- }
32
-
33
- private func setupView() {
34
- textView.baseBackgroundColor = .clear
35
- textView.backgroundColor = .clear
36
- textView.isEditable = false
37
- textView.isSelectable = false
38
- textView.allowImageResizing = false
39
- textView.heightBehavior = .autoGrow
40
- textView.onHeightMayChange = { [weak self] measuredHeight in
41
- self?.emitContentHeightIfNeeded(measuredHeight: measuredHeight, force: true)
42
- }
43
- textView.addGestureRecognizer(interactiveTapRecognizer)
44
- addSubview(textView)
45
- }
46
-
47
- func setEnableLinkTaps(_ enabled: Bool?) {
48
- enableLinkTaps = enabled ?? true
49
- }
50
-
51
- func setInterceptLinkTaps(_ intercept: Bool?) {
52
- interceptLinkTaps = intercept ?? false
53
- }
54
-
55
- func setCollapsesWhenEmpty(_ collapses: Bool?) {
56
- let nextValue = collapses ?? true
57
- guard collapsesWhenEmpty != nextValue else { return }
58
- collapsesWhenEmpty = nextValue
59
- updateCollapsedEmptyState()
60
- setNeedsLayout()
61
- emitContentHeightIfNeeded(force: true)
62
- }
63
-
64
- func setRenderJson(_ renderJson: String?) {
65
- guard lastRenderJSON != renderJson else { return }
66
- lastRenderJSON = renderJson
67
- applyRenderJSON()
68
- }
69
-
70
- func setThemeJson(_ themeJson: String?) {
71
- guard lastThemeJSON != themeJson else { return }
72
- lastThemeJSON = themeJson
73
- let theme = EditorTheme.from(json: themeJson)
74
- textView.applyTheme(theme)
75
- let cornerRadius = theme?.borderRadius ?? 0
76
- layer.cornerRadius = cornerRadius
77
- clipsToBounds = cornerRadius > 0
78
- applyRenderJSON()
79
- }
80
-
81
- override var intrinsicContentSize: CGSize {
82
- if isCollapsedEmptyContent {
83
- return CGSize(width: UIView.noIntrinsicMetric, height: 0)
84
- }
85
- guard lastEmittedContentHeight > 0 else {
86
- return CGSize(width: UIView.noIntrinsicMetric, height: UIView.noIntrinsicMetric)
87
- }
88
- return CGSize(width: UIView.noIntrinsicMetric, height: lastEmittedContentHeight)
89
- }
90
-
91
- override func layoutSubviews() {
92
- super.layoutSubviews()
93
- if isCollapsedEmptyContent {
94
- textView.frame = CGRect(x: 0, y: 0, width: bounds.width, height: 0)
95
- textView.updateAutoGrowHostHeight(0)
96
- } else {
97
- textView.frame = bounds
98
- textView.updateAutoGrowHostHeight(bounds.height)
99
- }
100
-
101
- let currentWidth = ceil(bounds.width)
102
- guard abs(currentWidth - lastMeasuredWidth) > 0.5 else { return }
103
- lastMeasuredWidth = currentWidth
104
- emitContentHeightIfNeeded(force: true)
105
- }
106
-
107
- private func applyRenderJSON() {
108
- updateCollapsedEmptyState()
109
- textView.applyRenderJSON(lastRenderJSON ?? "[]")
110
- textView.isHidden = isCollapsedEmptyContent
111
- lastMeasuredWidth = 0
112
- invalidateIntrinsicContentSize()
113
- setNeedsLayout()
114
- }
115
-
116
- private func updateCollapsedEmptyState() {
117
- isCollapsedEmptyContent = collapsesWhenEmpty
118
- && Self.renderJsonContainsOnlyEmptyParagraphs(lastRenderJSON ?? "[]")
119
- textView.isHidden = isCollapsedEmptyContent
120
- }
121
-
122
- private func emitContentHeightIfNeeded(
123
- measuredHeight: CGFloat? = nil,
124
- force: Bool = false
125
- ) {
126
- let contentHeight: CGFloat
127
- if isCollapsedEmptyContent {
128
- contentHeight = 0
129
- } else {
130
- guard bounds.width > 0 else { return }
131
- let fittedHeight = measuredHeight
132
- ?? textView.measuredAutoGrowHeightForTesting(width: bounds.width)
133
- contentHeight = ceil(fittedHeight)
134
- guard contentHeight > 0 else { return }
135
- }
136
- guard force || abs(contentHeight - lastEmittedContentHeight) > 0.5 else { return }
137
- lastEmittedContentHeight = contentHeight
138
- invalidateIntrinsicContentSize()
139
- onContentHeightChange(["contentHeight": contentHeight])
140
- }
141
-
142
- @objc private func handleInteractiveTap(_ recognizer: UITapGestureRecognizer) {
143
- guard recognizer.state == .ended else {
144
- return
145
- }
146
-
147
- let location = recognizer.location(in: textView)
148
- if enableLinkTaps, let link = linkHit(at: location) {
149
- if interceptLinkTaps {
150
- onPressLink([
151
- "href": link.href,
152
- "text": link.text,
153
- ])
154
- } else {
155
- openLink(link.href)
156
- }
157
- return
158
- }
159
-
160
- guard let mention = mentionHit(at: location) else { return }
161
- onPressMention([
162
- "docPos": mention.docPos,
163
- "label": mention.label,
164
- ])
165
- }
166
-
167
- private func characterIndex(at location: CGPoint) -> Int? {
168
- let textStorage = textView.textStorage
169
- guard textStorage.length > 0 else { return nil }
170
-
171
- let layoutManager = textView.layoutManager
172
- let textContainer = textView.textContainer
173
- var containerPoint = location
174
- containerPoint.x -= textView.textContainerInset.left
175
- containerPoint.y -= textView.textContainerInset.top
176
-
177
- let usedRect = layoutManager.usedRect(for: textContainer)
178
- guard usedRect.insetBy(dx: -6, dy: -6).contains(containerPoint) else {
179
- return nil
180
- }
181
-
182
- let glyphIndex = layoutManager.glyphIndex(for: containerPoint, in: textContainer)
183
- guard glyphIndex < layoutManager.numberOfGlyphs else { return nil }
184
- let characterIndex = layoutManager.characterIndexForGlyph(at: glyphIndex)
185
- guard characterIndex < textStorage.length else { return nil }
186
- return characterIndex
187
- }
188
-
189
- private func linkHit(at location: CGPoint) -> (href: String, text: String)? {
190
- let textStorage = textView.textStorage
191
- guard let characterIndex = characterIndex(at: location) else { return nil }
192
-
193
- var effectiveRange = NSRange(location: 0, length: 0)
194
- let attrs = textStorage.attributes(at: characterIndex, effectiveRange: &effectiveRange)
195
- guard let href = attrs[RenderBridgeAttributes.linkHref] as? String, !href.isEmpty else {
196
- return nil
197
- }
198
-
199
- let text = (textStorage.string as NSString).substring(with: effectiveRange)
200
- return (href: href, text: text)
201
- }
202
-
203
- private func mentionHit(at location: CGPoint) -> (docPos: Int, label: String)? {
204
- let textStorage = textView.textStorage
205
- guard let characterIndex = characterIndex(at: location) else { return nil }
206
-
207
- var effectiveRange = NSRange(location: 0, length: 0)
208
- let attrs = textStorage.attributes(at: characterIndex, effectiveRange: &effectiveRange)
209
- guard (attrs[RenderBridgeAttributes.voidNodeType] as? String) == "mention" else {
210
- return nil
211
- }
212
-
213
- let docPos =
214
- (attrs[RenderBridgeAttributes.docPos] as? NSNumber)?.intValue
215
- ?? Int((attrs[RenderBridgeAttributes.docPos] as? UInt32) ?? 0)
216
- let label = (textStorage.string as NSString).substring(with: effectiveRange)
217
- return (docPos: docPos, label: label)
218
- }
219
-
220
- private func openLink(_ href: String) {
221
- guard let url = URL(string: href) else { return }
222
- UIApplication.shared.open(url, options: [:], completionHandler: nil)
223
- }
224
-
225
- static func renderJsonContainsOnlyEmptyParagraphs(_ renderJson: String) -> Bool {
226
- guard let data = renderJson.data(using: .utf8),
227
- let elements = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]]
228
- else {
229
- return false
230
- }
231
-
232
- if elements.isEmpty {
233
- return true
234
- }
235
-
236
- var hasParagraph = false
237
- var paragraphIsOpen = false
238
-
239
- for element in elements {
240
- guard let type = element["type"] as? String else {
241
- return false
242
- }
243
-
244
- switch type {
245
- case "blockStart":
246
- guard !paragraphIsOpen,
247
- element["nodeType"] as? String == "paragraph",
248
- (element["depth"] as? NSNumber)?.intValue == 0
249
- else {
250
- return false
251
- }
252
- paragraphIsOpen = true
253
- hasParagraph = true
254
-
255
- case "textRun":
256
- guard paragraphIsOpen,
257
- let text = element["text"] as? String,
258
- text.allSatisfy({ $0 == "\u{200B}" })
259
- else {
260
- return false
261
- }
262
-
263
- case "blockEnd":
264
- guard paragraphIsOpen else {
265
- return false
266
- }
267
- paragraphIsOpen = false
268
-
269
- default:
270
- return false
271
- }
272
- }
273
-
274
- return hasParagraph && !paragraphIsOpen
275
- }
276
- }