@apollohg/react-native-prose-editor 0.5.11 → 0.5.12
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.
- package/android/src/main/java/com/apollohg/editor/EditorEditText.kt +2 -2
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +58 -4
- package/dist/EditorToolbar.js +37 -1
- package/dist/NativeRichTextEditor.js +3 -3
- package/ios/EditorCore.xcframework/ios-arm64/libeditor_core.a +0 -0
- package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a +0 -0
- package/package.json +1 -1
- package/rust/android/arm64-v8a/libeditor_core.so +0 -0
- package/rust/android/armeabi-v7a/libeditor_core.so +0 -0
- package/rust/android/x86_64/libeditor_core.so +0 -0
- package/rust/bindings/kotlin/uniffi/editor_core/editor_core.kt +1892 -1391
|
@@ -669,7 +669,7 @@ class EditorEditText @JvmOverloads constructor(
|
|
|
669
669
|
cursor > 0 &&
|
|
670
670
|
currentText.getOrNull(cursor - 1) == EMPTY_BLOCK_PLACEHOLDER
|
|
671
671
|
) {
|
|
672
|
-
val scalarCursor = PositionBridge.utf16ToScalar(cursor
|
|
672
|
+
val scalarCursor = PositionBridge.utf16ToScalar(cursor, currentText)
|
|
673
673
|
deleteBackwardAtSelectionScalarInRust(scalarCursor, scalarCursor)
|
|
674
674
|
return
|
|
675
675
|
}
|
|
@@ -722,7 +722,7 @@ class EditorEditText @JvmOverloads constructor(
|
|
|
722
722
|
deleteRangeInRust(scalarStart, scalarEnd)
|
|
723
723
|
} else if (start > 0) {
|
|
724
724
|
if (currentText.getOrNull(start - 1) == EMPTY_BLOCK_PLACEHOLDER) {
|
|
725
|
-
val scalarCursor = PositionBridge.utf16ToScalar(start
|
|
725
|
+
val scalarCursor = PositionBridge.utf16ToScalar(start, currentText)
|
|
726
726
|
deleteBackwardAtSelectionScalarInRust(scalarCursor, scalarCursor)
|
|
727
727
|
return
|
|
728
728
|
}
|
|
@@ -64,6 +64,8 @@ class NativeEditorExpoView(
|
|
|
64
64
|
private var previousWindowCallback: Window.Callback? = null
|
|
65
65
|
private var toolbarFramesInWindow: List<RectF> = emptyList()
|
|
66
66
|
private var lastToolbarTouchUptimeMs: Long? = null
|
|
67
|
+
private var pendingOutsideTapBlur: Runnable? = null
|
|
68
|
+
private var pendingKeyboardDismiss: Runnable? = null
|
|
67
69
|
private var addons = NativeEditorAddons(null)
|
|
68
70
|
private var mentionQueryState: MentionQueryState? = null
|
|
69
71
|
private var lastMentionEventJson: String? = null
|
|
@@ -266,6 +268,8 @@ class NativeEditorExpoView(
|
|
|
266
268
|
}
|
|
267
269
|
|
|
268
270
|
fun focus() {
|
|
271
|
+
cancelPendingOutsideTapBlur()
|
|
272
|
+
cancelPendingKeyboardDismiss()
|
|
269
273
|
richTextView.editorEditText.requestFocus()
|
|
270
274
|
richTextView.editorEditText.post {
|
|
271
275
|
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
|
@@ -274,12 +278,55 @@ class NativeEditorExpoView(
|
|
|
274
278
|
}
|
|
275
279
|
|
|
276
280
|
fun blur() {
|
|
281
|
+
cancelPendingOutsideTapBlur()
|
|
282
|
+
cancelPendingKeyboardDismiss()
|
|
277
283
|
clearRecentToolbarTouch()
|
|
278
284
|
richTextView.editorEditText.clearFocus()
|
|
279
285
|
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
|
280
286
|
imm?.hideSoftInputFromWindow(richTextView.editorEditText.windowToken, 0)
|
|
281
287
|
}
|
|
282
288
|
|
|
289
|
+
private fun blurWithDeferredKeyboardDismiss() {
|
|
290
|
+
cancelPendingKeyboardDismiss()
|
|
291
|
+
clearRecentToolbarTouch()
|
|
292
|
+
richTextView.editorEditText.clearFocus()
|
|
293
|
+
val dismiss = Runnable {
|
|
294
|
+
pendingKeyboardDismiss = null
|
|
295
|
+
if (!richTextView.editorEditText.hasFocus()) {
|
|
296
|
+
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
|
297
|
+
imm?.hideSoftInputFromWindow(richTextView.editorEditText.windowToken, 0)
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
pendingKeyboardDismiss = dismiss
|
|
301
|
+
richTextView.editorEditText.post(dismiss)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private fun scheduleOutsideTapBlur() {
|
|
305
|
+
cancelPendingOutsideTapBlur()
|
|
306
|
+
val blur = Runnable {
|
|
307
|
+
pendingOutsideTapBlur = null
|
|
308
|
+
if (richTextView.editorEditText.hasFocus()) {
|
|
309
|
+
blurWithDeferredKeyboardDismiss()
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
pendingOutsideTapBlur = blur
|
|
313
|
+
richTextView.editorEditText.postDelayed(blur, OUTSIDE_TAP_BLUR_DELAY_MS)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private fun cancelPendingOutsideTapBlur() {
|
|
317
|
+
pendingOutsideTapBlur?.let {
|
|
318
|
+
richTextView.editorEditText.removeCallbacks(it)
|
|
319
|
+
pendingOutsideTapBlur = null
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private fun cancelPendingKeyboardDismiss() {
|
|
324
|
+
pendingKeyboardDismiss?.let {
|
|
325
|
+
richTextView.editorEditText.removeCallbacks(it)
|
|
326
|
+
pendingKeyboardDismiss = null
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
283
330
|
fun getCaretRectJson(): String? {
|
|
284
331
|
if (width <= 0 || height <= 0) return null
|
|
285
332
|
val rect = richTextView.caretRect() ?: return null
|
|
@@ -296,6 +343,8 @@ class NativeEditorExpoView(
|
|
|
296
343
|
|
|
297
344
|
override fun onDetachedFromWindow() {
|
|
298
345
|
super.onDetachedFromWindow()
|
|
346
|
+
cancelPendingOutsideTapBlur()
|
|
347
|
+
cancelPendingKeyboardDismiss()
|
|
299
348
|
uninstallOutsideTapBlurHandler()
|
|
300
349
|
detachKeyboardToolbarIfNeeded()
|
|
301
350
|
}
|
|
@@ -430,14 +479,17 @@ class NativeEditorExpoView(
|
|
|
430
479
|
|
|
431
480
|
val wrappedCallback = object : Window.Callback by currentCallback {
|
|
432
481
|
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
|
|
433
|
-
|
|
482
|
+
val shouldBlur =
|
|
434
483
|
event.action == MotionEvent.ACTION_DOWN &&
|
|
435
484
|
richTextView.editorEditText.hasFocus() &&
|
|
436
485
|
isTouchOutsideEditor(event)
|
|
437
|
-
)
|
|
438
|
-
|
|
486
|
+
val result = currentCallback.dispatchTouchEvent(event)
|
|
487
|
+
if (shouldBlur) {
|
|
488
|
+
scheduleOutsideTapBlur()
|
|
489
|
+
} else if (event.action == MotionEvent.ACTION_DOWN) {
|
|
490
|
+
cancelPendingOutsideTapBlur()
|
|
439
491
|
}
|
|
440
|
-
return
|
|
492
|
+
return result
|
|
441
493
|
}
|
|
442
494
|
}
|
|
443
495
|
|
|
@@ -458,6 +510,7 @@ class NativeEditorExpoView(
|
|
|
458
510
|
|
|
459
511
|
private fun isTouchOutsideEditor(event: MotionEvent): Boolean {
|
|
460
512
|
if (isTouchInsideKeyboardToolbar(event)) {
|
|
513
|
+
markRecentToolbarTouch()
|
|
461
514
|
return false
|
|
462
515
|
}
|
|
463
516
|
if (isTouchInsideStandaloneToolbar(event)) {
|
|
@@ -553,6 +606,7 @@ class NativeEditorExpoView(
|
|
|
553
606
|
private companion object {
|
|
554
607
|
private const val TOOLBAR_HIT_SLOP_DP = 8f
|
|
555
608
|
private const val TOOLBAR_FOCUS_PRESERVE_MS = 750L
|
|
609
|
+
private const val OUTSIDE_TAP_BLUR_DELAY_MS = 100L
|
|
556
610
|
}
|
|
557
611
|
|
|
558
612
|
private fun resolveActivity(context: Context): Activity? {
|
package/dist/EditorToolbar.js
CHANGED
|
@@ -136,6 +136,7 @@ const TOOLBAR_PADDING_H = 12;
|
|
|
136
136
|
const TOOLBAR_PADDING_V = 4;
|
|
137
137
|
const MENU_MARGIN = 8;
|
|
138
138
|
const MENU_WIDTH = 192;
|
|
139
|
+
const KEYBOARD_FRAME_REMEASURE_DELAYS_MS = [50, 150, 300];
|
|
139
140
|
const ACTIVE_BG = 'rgba(0, 122, 255, 0.12)';
|
|
140
141
|
const ACTIVE_COLOR = '#007AFF';
|
|
141
142
|
const DEFAULT_COLOR = '#666666';
|
|
@@ -205,6 +206,8 @@ function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic
|
|
|
205
206
|
const [expandedGroupKey, setExpandedGroupKey] = (0, react_1.useState)(null);
|
|
206
207
|
const [menuState, setMenuState] = (0, react_1.useState)(null);
|
|
207
208
|
const toolbarInteractionActiveRef = (0, react_1.useRef)(false);
|
|
209
|
+
const framePublishAnimationFramesRef = (0, react_1.useRef)([]);
|
|
210
|
+
const framePublishTimeoutsRef = (0, react_1.useRef)([]);
|
|
208
211
|
const registrationIdRef = (0, react_1.useRef)(null);
|
|
209
212
|
if (registrationIdRef.current == null) {
|
|
210
213
|
registrationIdRef.current = nextEditorToolbarRegistrationId++;
|
|
@@ -478,6 +481,23 @@ function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic
|
|
|
478
481
|
registerEditorToolbarFrame(registrationId, { x, y, width, height });
|
|
479
482
|
});
|
|
480
483
|
}, [preserveEditorFocus]);
|
|
484
|
+
const cancelScheduledFramePublishes = (0, react_1.useCallback)(() => {
|
|
485
|
+
framePublishAnimationFramesRef.current.forEach((frame) => cancelAnimationFrame(frame));
|
|
486
|
+
framePublishAnimationFramesRef.current = [];
|
|
487
|
+
framePublishTimeoutsRef.current.forEach((timeout) => clearTimeout(timeout));
|
|
488
|
+
framePublishTimeoutsRef.current = [];
|
|
489
|
+
}, []);
|
|
490
|
+
const scheduleToolbarFramePublish = (0, react_1.useCallback)(() => {
|
|
491
|
+
if (!preserveEditorFocus) {
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
cancelScheduledFramePublishes();
|
|
495
|
+
publishToolbarFrame();
|
|
496
|
+
framePublishAnimationFramesRef.current.push(requestAnimationFrame(publishToolbarFrame));
|
|
497
|
+
KEYBOARD_FRAME_REMEASURE_DELAYS_MS.forEach((delay) => {
|
|
498
|
+
framePublishTimeoutsRef.current.push(setTimeout(publishToolbarFrame, delay));
|
|
499
|
+
});
|
|
500
|
+
}, [cancelScheduledFramePublishes, preserveEditorFocus, publishToolbarFrame]);
|
|
481
501
|
const handleToolbarLayout = (0, react_1.useCallback)(() => {
|
|
482
502
|
requestAnimationFrame(publishToolbarFrame);
|
|
483
503
|
}, [publishToolbarFrame]);
|
|
@@ -503,6 +523,7 @@ function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic
|
|
|
503
523
|
(0, react_1.useEffect)(() => {
|
|
504
524
|
const registrationId = registrationIdRef.current;
|
|
505
525
|
return () => {
|
|
526
|
+
cancelScheduledFramePublishes();
|
|
506
527
|
if (toolbarInteractionActiveRef.current) {
|
|
507
528
|
toolbarInteractionActiveRef.current = false;
|
|
508
529
|
endEditorToolbarInteraction();
|
|
@@ -511,7 +532,22 @@ function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic
|
|
|
511
532
|
unregisterEditorToolbarFrame(registrationId);
|
|
512
533
|
}
|
|
513
534
|
};
|
|
514
|
-
}, []);
|
|
535
|
+
}, [cancelScheduledFramePublishes]);
|
|
536
|
+
(0, react_1.useEffect)(() => {
|
|
537
|
+
if (!preserveEditorFocus) {
|
|
538
|
+
cancelScheduledFramePublishes();
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
const subscriptions = [
|
|
542
|
+
react_native_1.Keyboard.addListener('keyboardDidShow', scheduleToolbarFramePublish),
|
|
543
|
+
react_native_1.Keyboard.addListener('keyboardDidHide', scheduleToolbarFramePublish),
|
|
544
|
+
react_native_1.Keyboard.addListener('keyboardDidChangeFrame', scheduleToolbarFramePublish),
|
|
545
|
+
];
|
|
546
|
+
return () => {
|
|
547
|
+
subscriptions.forEach((subscription) => subscription.remove());
|
|
548
|
+
cancelScheduledFramePublishes();
|
|
549
|
+
};
|
|
550
|
+
}, [cancelScheduledFramePublishes, preserveEditorFocus, scheduleToolbarFramePublish]);
|
|
515
551
|
(0, react_1.useEffect)(() => {
|
|
516
552
|
if (expandedGroupKey != null && !groupsByKey.has(expandedGroupKey)) {
|
|
517
553
|
setExpandedGroupKey(null);
|
|
@@ -798,7 +798,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
798
798
|
});
|
|
799
799
|
}, []);
|
|
800
800
|
(0, react_1.useEffect)(() => {
|
|
801
|
-
if (!(showToolbar && toolbarPlacement === 'inline' &&
|
|
801
|
+
if (!(showToolbar && toolbarPlacement === 'inline' && editable)) {
|
|
802
802
|
setInlineToolbarFrame(null);
|
|
803
803
|
return;
|
|
804
804
|
}
|
|
@@ -806,7 +806,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
806
806
|
updateToolbarFrame();
|
|
807
807
|
});
|
|
808
808
|
return () => cancelAnimationFrame(frame);
|
|
809
|
-
}, [editable,
|
|
809
|
+
}, [editable, showToolbar, toolbarPlacement, updateToolbarFrame]);
|
|
810
810
|
(0, react_1.useEffect)(() => {
|
|
811
811
|
if (heightBehavior !== 'autoGrow') {
|
|
812
812
|
setAutoGrowHeight(null);
|
|
@@ -1287,7 +1287,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
1287
1287
|
nativeViewStyleParts.push({ height: autoGrowHeight });
|
|
1288
1288
|
}
|
|
1289
1289
|
const nativeViewStyle = nativeViewStyleParts.length <= 1 ? nativeViewStyleParts[0] : nativeViewStyleParts;
|
|
1290
|
-
const toolbarFrameJson = serializeToolbarFrames(
|
|
1290
|
+
const toolbarFrameJson = serializeToolbarFrames(editable
|
|
1291
1291
|
? [
|
|
1292
1292
|
...(toolbarPlacement === 'inline' && inlineToolbarFrame != null
|
|
1293
1293
|
? [inlineToolbarFrame]
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apollohg/react-native-prose-editor",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.12",
|
|
4
4
|
"description": "Native rich text editor with Rust core for React Native",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/apollohg/react-native-prose-editor",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|