@apollohg/react-native-prose-editor 0.5.10 → 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 +14 -2
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +208 -27
- package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +4 -0
- package/android/src/main/java/com/apollohg/editor/NativeToolbar.kt +2 -1
- package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +10 -0
- package/dist/EditorToolbar.d.ts +18 -1
- package/dist/EditorToolbar.js +192 -4
- package/dist/NativeRichTextEditor.d.ts +16 -0
- package/dist/NativeRichTextEditor.js +89 -13
- package/dist/index.d.ts +1 -1
- package/ios/EditorCore.xcframework/Info.plist +5 -5
- 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/ios/NativeEditorExpoView.swift +70 -8
- package/ios/NativeEditorModule.swift +3 -0
- package/ios/RichTextEditorView.swift +7 -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
package/dist/EditorToolbar.js
CHANGED
|
@@ -1,11 +1,95 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DEFAULT_EDITOR_TOOLBAR_ITEMS = void 0;
|
|
4
|
+
exports.isEditorToolbarFocusPreservationActive = isEditorToolbarFocusPreservationActive;
|
|
5
|
+
exports.useEditorToolbarFrames = useEditorToolbarFrames;
|
|
6
|
+
exports._setEditorToolbarFrameForTests = _setEditorToolbarFrameForTests;
|
|
7
|
+
exports._resetEditorToolbarFrameRegistryForTests = _resetEditorToolbarFrameRegistryForTests;
|
|
8
|
+
exports._beginEditorToolbarInteractionForTests = _beginEditorToolbarInteractionForTests;
|
|
9
|
+
exports._endEditorToolbarInteractionForTests = _endEditorToolbarInteractionForTests;
|
|
4
10
|
exports.EditorToolbar = EditorToolbar;
|
|
5
11
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
12
|
const vector_icons_1 = require("@expo/vector-icons");
|
|
7
13
|
const react_1 = require("react");
|
|
8
14
|
const react_native_1 = require("react-native");
|
|
15
|
+
const editorToolbarFrames = new Map();
|
|
16
|
+
const editorToolbarFrameListeners = new Set();
|
|
17
|
+
let nextEditorToolbarRegistrationId = 1;
|
|
18
|
+
let activeEditorToolbarInteractions = 0;
|
|
19
|
+
let editorToolbarFocusPreserveUntil = 0;
|
|
20
|
+
const EDITOR_TOOLBAR_FOCUS_PRESERVE_MS = 750;
|
|
21
|
+
function areToolbarFramesEqual(left, right) {
|
|
22
|
+
return (left?.x === right?.x &&
|
|
23
|
+
left?.y === right?.y &&
|
|
24
|
+
left?.width === right?.width &&
|
|
25
|
+
left?.height === right?.height);
|
|
26
|
+
}
|
|
27
|
+
function notifyEditorToolbarFrameListeners() {
|
|
28
|
+
editorToolbarFrameListeners.forEach((listener) => listener());
|
|
29
|
+
}
|
|
30
|
+
function getEditorToolbarFramesSnapshot() {
|
|
31
|
+
return Array.from(editorToolbarFrames.values());
|
|
32
|
+
}
|
|
33
|
+
function registerEditorToolbarFrame(id, frame) {
|
|
34
|
+
if (frame == null || frame.width <= 0 || frame.height <= 0) {
|
|
35
|
+
if (editorToolbarFrames.delete(id)) {
|
|
36
|
+
notifyEditorToolbarFrameListeners();
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const currentFrame = editorToolbarFrames.get(id);
|
|
41
|
+
if (areToolbarFramesEqual(currentFrame, frame)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
editorToolbarFrames.set(id, frame);
|
|
45
|
+
notifyEditorToolbarFrameListeners();
|
|
46
|
+
}
|
|
47
|
+
function unregisterEditorToolbarFrame(id) {
|
|
48
|
+
if (editorToolbarFrames.delete(id)) {
|
|
49
|
+
notifyEditorToolbarFrameListeners();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function preserveEditorToolbarFocusForNextBlur() {
|
|
53
|
+
editorToolbarFocusPreserveUntil = Date.now() + EDITOR_TOOLBAR_FOCUS_PRESERVE_MS;
|
|
54
|
+
}
|
|
55
|
+
function beginEditorToolbarInteraction() {
|
|
56
|
+
activeEditorToolbarInteractions += 1;
|
|
57
|
+
preserveEditorToolbarFocusForNextBlur();
|
|
58
|
+
}
|
|
59
|
+
function endEditorToolbarInteraction() {
|
|
60
|
+
activeEditorToolbarInteractions = Math.max(0, activeEditorToolbarInteractions - 1);
|
|
61
|
+
preserveEditorToolbarFocusForNextBlur();
|
|
62
|
+
}
|
|
63
|
+
function isEditorToolbarFocusPreservationActive() {
|
|
64
|
+
return activeEditorToolbarInteractions > 0 || Date.now() <= editorToolbarFocusPreserveUntil;
|
|
65
|
+
}
|
|
66
|
+
function useEditorToolbarFrames() {
|
|
67
|
+
const [frames, setFrames] = (0, react_1.useState)(getEditorToolbarFramesSnapshot);
|
|
68
|
+
(0, react_1.useEffect)(() => {
|
|
69
|
+
const listener = () => setFrames(getEditorToolbarFramesSnapshot());
|
|
70
|
+
editorToolbarFrameListeners.add(listener);
|
|
71
|
+
listener();
|
|
72
|
+
return () => {
|
|
73
|
+
editorToolbarFrameListeners.delete(listener);
|
|
74
|
+
};
|
|
75
|
+
}, []);
|
|
76
|
+
return frames;
|
|
77
|
+
}
|
|
78
|
+
function _setEditorToolbarFrameForTests(id, frame) {
|
|
79
|
+
registerEditorToolbarFrame(id, frame);
|
|
80
|
+
}
|
|
81
|
+
function _resetEditorToolbarFrameRegistryForTests() {
|
|
82
|
+
editorToolbarFrames.clear();
|
|
83
|
+
activeEditorToolbarInteractions = 0;
|
|
84
|
+
editorToolbarFocusPreserveUntil = 0;
|
|
85
|
+
notifyEditorToolbarFrameListeners();
|
|
86
|
+
}
|
|
87
|
+
function _beginEditorToolbarInteractionForTests() {
|
|
88
|
+
beginEditorToolbarInteraction();
|
|
89
|
+
}
|
|
90
|
+
function _endEditorToolbarInteractionForTests() {
|
|
91
|
+
endEditorToolbarInteraction();
|
|
92
|
+
}
|
|
9
93
|
function defaultIcon(id) {
|
|
10
94
|
return { type: 'default', id };
|
|
11
95
|
}
|
|
@@ -52,6 +136,7 @@ const TOOLBAR_PADDING_H = 12;
|
|
|
52
136
|
const TOOLBAR_PADDING_V = 4;
|
|
53
137
|
const MENU_MARGIN = 8;
|
|
54
138
|
const MENU_WIDTH = 192;
|
|
139
|
+
const KEYBOARD_FRAME_REMEASURE_DELAYS_MS = [50, 150, 300];
|
|
55
140
|
const ACTIVE_BG = 'rgba(0, 122, 255, 0.12)';
|
|
56
141
|
const ACTIVE_COLOR = '#007AFF';
|
|
57
142
|
const DEFAULT_COLOR = '#666666';
|
|
@@ -109,16 +194,24 @@ const DEFAULT_MATERIAL_ICONS = {
|
|
|
109
194
|
undo: 'undo',
|
|
110
195
|
redo: 'redo',
|
|
111
196
|
};
|
|
112
|
-
function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic, onToggleUnderline, onToggleStrike, onToggleBulletList, onToggleHeading, onToggleBlockquote, onToggleOrderedList, onIndentList, onOutdentList, onInsertHorizontalRule, onInsertLineBreak, onUndo, onRedo, onToggleMark, onToggleListType, onInsertNodeType, onRunCommand, onToolbarAction, onRequestLink, onRequestImage, toolbarItems = exports.DEFAULT_EDITOR_TOOLBAR_ITEMS, theme, showTopBorder, }) {
|
|
197
|
+
function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic, onToggleUnderline, onToggleStrike, onToggleBulletList, onToggleHeading, onToggleBlockquote, onToggleOrderedList, onIndentList, onOutdentList, onInsertHorizontalRule, onInsertLineBreak, onUndo, onRedo, onToggleMark, onToggleListType, onInsertNodeType, onRunCommand, onToolbarAction, onRequestLink, onRequestImage, toolbarItems = exports.DEFAULT_EDITOR_TOOLBAR_ITEMS, theme, showTopBorder, preserveEditorFocus = true, }) {
|
|
113
198
|
const marks = activeState.marks ?? {};
|
|
114
199
|
const nodes = activeState.nodes ?? {};
|
|
115
200
|
const commands = activeState.commands ?? {};
|
|
116
201
|
const allowedMarks = activeState.allowedMarks ?? [];
|
|
117
202
|
const insertableNodes = activeState.insertableNodes ?? [];
|
|
203
|
+
const rootRef = (0, react_1.useRef)(null);
|
|
118
204
|
const groupButtonRefs = (0, react_1.useRef)(new Map());
|
|
119
205
|
const { width: windowWidth, height: windowHeight } = (0, react_native_1.useWindowDimensions)();
|
|
120
206
|
const [expandedGroupKey, setExpandedGroupKey] = (0, react_1.useState)(null);
|
|
121
207
|
const [menuState, setMenuState] = (0, react_1.useState)(null);
|
|
208
|
+
const toolbarInteractionActiveRef = (0, react_1.useRef)(false);
|
|
209
|
+
const framePublishAnimationFramesRef = (0, react_1.useRef)([]);
|
|
210
|
+
const framePublishTimeoutsRef = (0, react_1.useRef)([]);
|
|
211
|
+
const registrationIdRef = (0, react_1.useRef)(null);
|
|
212
|
+
if (registrationIdRef.current == null) {
|
|
213
|
+
registrationIdRef.current = nextEditorToolbarRegistrationId++;
|
|
214
|
+
}
|
|
122
215
|
const isMarkActive = (0, react_1.useCallback)((mark) => !!marks[mark], [marks]);
|
|
123
216
|
const isInList = !!nodes['bulletList'] || !!nodes['orderedList'];
|
|
124
217
|
const canIndentList = isInList && !!commands['indentList'];
|
|
@@ -372,6 +465,89 @@ function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic
|
|
|
372
465
|
};
|
|
373
466
|
}, [expandedGroupKey, menuState?.groupKey, resolveButton, toolbarItems]);
|
|
374
467
|
const resolvedShowTopBorder = showTopBorder ?? theme?.showTopBorder ?? true;
|
|
468
|
+
const publishToolbarFrame = (0, react_1.useCallback)(() => {
|
|
469
|
+
const registrationId = registrationIdRef.current;
|
|
470
|
+
const toolbar = rootRef.current;
|
|
471
|
+
if (!preserveEditorFocus || registrationId == null || !toolbar) {
|
|
472
|
+
if (registrationId != null) {
|
|
473
|
+
unregisterEditorToolbarFrame(registrationId);
|
|
474
|
+
}
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
if (typeof toolbar.measureInWindow !== 'function') {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
toolbar.measureInWindow((x, y, width, height) => {
|
|
481
|
+
registerEditorToolbarFrame(registrationId, { x, y, width, height });
|
|
482
|
+
});
|
|
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]);
|
|
501
|
+
const handleToolbarLayout = (0, react_1.useCallback)(() => {
|
|
502
|
+
requestAnimationFrame(publishToolbarFrame);
|
|
503
|
+
}, [publishToolbarFrame]);
|
|
504
|
+
(0, react_1.useEffect)(() => {
|
|
505
|
+
if (!preserveEditorFocus) {
|
|
506
|
+
const registrationId = registrationIdRef.current;
|
|
507
|
+
if (registrationId != null) {
|
|
508
|
+
unregisterEditorToolbarFrame(registrationId);
|
|
509
|
+
}
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
const frame = requestAnimationFrame(publishToolbarFrame);
|
|
513
|
+
return () => cancelAnimationFrame(frame);
|
|
514
|
+
}, [
|
|
515
|
+
expandedGroupKey,
|
|
516
|
+
menuState?.groupKey,
|
|
517
|
+
preserveEditorFocus,
|
|
518
|
+
publishToolbarFrame,
|
|
519
|
+
renderedItems.length,
|
|
520
|
+
windowHeight,
|
|
521
|
+
windowWidth,
|
|
522
|
+
]);
|
|
523
|
+
(0, react_1.useEffect)(() => {
|
|
524
|
+
const registrationId = registrationIdRef.current;
|
|
525
|
+
return () => {
|
|
526
|
+
cancelScheduledFramePublishes();
|
|
527
|
+
if (toolbarInteractionActiveRef.current) {
|
|
528
|
+
toolbarInteractionActiveRef.current = false;
|
|
529
|
+
endEditorToolbarInteraction();
|
|
530
|
+
}
|
|
531
|
+
if (registrationId != null) {
|
|
532
|
+
unregisterEditorToolbarFrame(registrationId);
|
|
533
|
+
}
|
|
534
|
+
};
|
|
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]);
|
|
375
551
|
(0, react_1.useEffect)(() => {
|
|
376
552
|
if (expandedGroupKey != null && !groupsByKey.has(expandedGroupKey)) {
|
|
377
553
|
setExpandedGroupKey(null);
|
|
@@ -389,6 +565,18 @@ function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic
|
|
|
389
565
|
}
|
|
390
566
|
setMenuState(null);
|
|
391
567
|
}, []);
|
|
568
|
+
const handleToolbarPressIn = (0, react_1.useCallback)(() => {
|
|
569
|
+
if (preserveEditorFocus && !toolbarInteractionActiveRef.current) {
|
|
570
|
+
toolbarInteractionActiveRef.current = true;
|
|
571
|
+
beginEditorToolbarInteraction();
|
|
572
|
+
}
|
|
573
|
+
}, [preserveEditorFocus]);
|
|
574
|
+
const handleToolbarPressOut = (0, react_1.useCallback)(() => {
|
|
575
|
+
if (preserveEditorFocus && toolbarInteractionActiveRef.current) {
|
|
576
|
+
toolbarInteractionActiveRef.current = false;
|
|
577
|
+
endEditorToolbarInteraction();
|
|
578
|
+
}
|
|
579
|
+
}, [preserveEditorFocus]);
|
|
392
580
|
const handleGroupPress = (0, react_1.useCallback)((group) => {
|
|
393
581
|
if (group.isDisabled) {
|
|
394
582
|
return;
|
|
@@ -442,7 +630,7 @@ function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic
|
|
|
442
630
|
else {
|
|
443
631
|
groupButtonRefs.current.delete(anchorGroupKey);
|
|
444
632
|
}
|
|
445
|
-
}, collapsable: false, style: styles.buttonAnchor, children: [(0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { onPress: onPress, disabled: button.isDisabled, style: [
|
|
633
|
+
}, collapsable: false, style: styles.buttonAnchor, children: [(0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { onPressIn: handleToolbarPressIn, onPressOut: handleToolbarPressOut, onPress: onPress, disabled: button.isDisabled, style: [
|
|
446
634
|
styles.button,
|
|
447
635
|
{
|
|
448
636
|
borderRadius: theme?.buttonBorderRadius ?? BUTTON_RADIUS,
|
|
@@ -460,7 +648,7 @@ function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic
|
|
|
460
648
|
styles.separator,
|
|
461
649
|
theme?.separatorColor != null ? { backgroundColor: theme.separatorColor } : null,
|
|
462
650
|
] }, key));
|
|
463
|
-
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
|
|
651
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { ref: rootRef, collapsable: false, onLayout: handleToolbarLayout, style: [
|
|
464
652
|
styles.container,
|
|
465
653
|
!resolvedShowTopBorder && styles.containerWithoutTopBorder,
|
|
466
654
|
theme?.backgroundColor != null ? { backgroundColor: theme.backgroundColor } : null,
|
|
@@ -512,7 +700,7 @@ function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic
|
|
|
512
700
|
: button.isDisabled
|
|
513
701
|
? disabledColor
|
|
514
702
|
: defaultColor;
|
|
515
|
-
return ((0, jsx_runtime_1.jsxs)(react_native_1.Pressable, { onPress: () => handleButtonPress(button), disabled: button.isDisabled, style: ({ pressed }) => [
|
|
703
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.Pressable, { onPressIn: handleToolbarPressIn, onPressOut: handleToolbarPressOut, onPress: () => handleButtonPress(button), disabled: button.isDisabled, style: ({ pressed }) => [
|
|
516
704
|
styles.menuItem,
|
|
517
705
|
button.isActive && {
|
|
518
706
|
backgroundColor: theme?.buttonActiveBackgroundColor ?? ACTIVE_BG,
|
|
@@ -135,6 +135,8 @@ export interface NativeRichTextEditorRef {
|
|
|
135
135
|
getContentJson(): DocumentJSON;
|
|
136
136
|
/** Get the plain text content (no markup). */
|
|
137
137
|
getTextContent(): string;
|
|
138
|
+
/** Get the current caret rectangle in editor-local layout coordinates. */
|
|
139
|
+
getCaretRect(): Promise<NativeRichTextEditorCaretRect | null>;
|
|
138
140
|
/** Undo the last operation. */
|
|
139
141
|
undo(): void;
|
|
140
142
|
/** Redo the last undone operation. */
|
|
@@ -144,4 +146,18 @@ export interface NativeRichTextEditorRef {
|
|
|
144
146
|
/** Check if redo is available. */
|
|
145
147
|
canRedo(): boolean;
|
|
146
148
|
}
|
|
149
|
+
export interface NativeRichTextEditorCaretRect {
|
|
150
|
+
/** Left edge of the caret, relative to the editor root view. */
|
|
151
|
+
x: number;
|
|
152
|
+
/** Top edge of the caret, relative to the editor root view. */
|
|
153
|
+
y: number;
|
|
154
|
+
/** Caret width. */
|
|
155
|
+
width: number;
|
|
156
|
+
/** Caret height. */
|
|
157
|
+
height: number;
|
|
158
|
+
/** Current editor root view width. */
|
|
159
|
+
editorWidth: number;
|
|
160
|
+
/** Current editor root view height. */
|
|
161
|
+
editorHeight: number;
|
|
162
|
+
}
|
|
147
163
|
export declare const NativeRichTextEditor: React.ForwardRefExoticComponent<NativeRichTextEditorProps & React.RefAttributes<NativeRichTextEditorRef>>;
|
|
@@ -423,6 +423,44 @@ function serializeRemoteSelections(remoteSelections) {
|
|
|
423
423
|
}
|
|
424
424
|
return stringifyCachedJson(remoteSelections);
|
|
425
425
|
}
|
|
426
|
+
function areToolbarFramesEqual(left, right) {
|
|
427
|
+
return (left?.x === right?.x &&
|
|
428
|
+
left?.y === right?.y &&
|
|
429
|
+
left?.width === right?.width &&
|
|
430
|
+
left?.height === right?.height);
|
|
431
|
+
}
|
|
432
|
+
function serializeToolbarFrames(frames) {
|
|
433
|
+
if (!frames || frames.length === 0) {
|
|
434
|
+
return undefined;
|
|
435
|
+
}
|
|
436
|
+
return JSON.stringify(frames.length === 1 ? frames[0] : { frames });
|
|
437
|
+
}
|
|
438
|
+
function parseCaretRectJson(raw) {
|
|
439
|
+
if (!raw) {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
try {
|
|
443
|
+
const parsed = JSON.parse(raw);
|
|
444
|
+
const x = typeof parsed.x === 'number' ? parsed.x : null;
|
|
445
|
+
const y = typeof parsed.y === 'number' ? parsed.y : null;
|
|
446
|
+
const width = typeof parsed.width === 'number' ? parsed.width : null;
|
|
447
|
+
const height = typeof parsed.height === 'number' ? parsed.height : null;
|
|
448
|
+
const editorWidth = typeof parsed.editorWidth === 'number' ? parsed.editorWidth : null;
|
|
449
|
+
const editorHeight = typeof parsed.editorHeight === 'number' ? parsed.editorHeight : null;
|
|
450
|
+
if (x == null ||
|
|
451
|
+
y == null ||
|
|
452
|
+
width == null ||
|
|
453
|
+
height == null ||
|
|
454
|
+
editorWidth == null ||
|
|
455
|
+
editorHeight == null) {
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
458
|
+
return { x, y, width, height, editorWidth, editorHeight };
|
|
459
|
+
}
|
|
460
|
+
catch {
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
426
464
|
const serializedJsonCache = new WeakMap();
|
|
427
465
|
function stringifyCachedJson(value) {
|
|
428
466
|
if (value != null && typeof value === 'object') {
|
|
@@ -463,7 +501,9 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
463
501
|
const [isReady, setIsReady] = (0, react_1.useState)(false);
|
|
464
502
|
const [editorInstanceId, setEditorInstanceId] = (0, react_1.useState)(0);
|
|
465
503
|
const [isFocused, setIsFocused] = (0, react_1.useState)(false);
|
|
466
|
-
const
|
|
504
|
+
const isFocusedRef = (0, react_1.useRef)(false);
|
|
505
|
+
const [inlineToolbarFrame, setInlineToolbarFrame] = (0, react_1.useState)(null);
|
|
506
|
+
const registeredToolbarFrames = (0, EditorToolbar_1.useEditorToolbarFrames)();
|
|
467
507
|
const [pendingNativeUpdate, setPendingNativeUpdate] = (0, react_1.useState)({
|
|
468
508
|
json: undefined,
|
|
469
509
|
revision: 0,
|
|
@@ -745,28 +785,28 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
745
785
|
const updateToolbarFrame = (0, react_1.useCallback)(() => {
|
|
746
786
|
const toolbar = toolbarRef.current;
|
|
747
787
|
if (!toolbar) {
|
|
748
|
-
|
|
788
|
+
setInlineToolbarFrame(null);
|
|
749
789
|
return;
|
|
750
790
|
}
|
|
751
791
|
toolbar.measureInWindow((x, y, width, height) => {
|
|
752
792
|
if (width <= 0 || height <= 0) {
|
|
753
|
-
|
|
793
|
+
setInlineToolbarFrame(null);
|
|
754
794
|
return;
|
|
755
795
|
}
|
|
756
|
-
const
|
|
757
|
-
|
|
796
|
+
const nextFrame = { x, y, width, height };
|
|
797
|
+
setInlineToolbarFrame((prev) => areToolbarFramesEqual(prev, nextFrame) ? prev : nextFrame);
|
|
758
798
|
});
|
|
759
799
|
}, []);
|
|
760
800
|
(0, react_1.useEffect)(() => {
|
|
761
|
-
if (!(showToolbar && toolbarPlacement === 'inline' &&
|
|
762
|
-
|
|
801
|
+
if (!(showToolbar && toolbarPlacement === 'inline' && editable)) {
|
|
802
|
+
setInlineToolbarFrame(null);
|
|
763
803
|
return;
|
|
764
804
|
}
|
|
765
805
|
const frame = requestAnimationFrame(() => {
|
|
766
806
|
updateToolbarFrame();
|
|
767
807
|
});
|
|
768
808
|
return () => cancelAnimationFrame(frame);
|
|
769
|
-
}, [editable,
|
|
809
|
+
}, [editable, showToolbar, toolbarPlacement, updateToolbarFrame]);
|
|
770
810
|
(0, react_1.useEffect)(() => {
|
|
771
811
|
if (heightBehavior !== 'autoGrow') {
|
|
772
812
|
setAutoGrowHeight(null);
|
|
@@ -836,19 +876,40 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
836
876
|
}
|
|
837
877
|
onSelectionChangeRef.current?.(nextSelection);
|
|
838
878
|
}, [syncSelectionStateFromUpdate]);
|
|
879
|
+
const refocusAfterToolbarInteraction = (0, react_1.useCallback)(() => {
|
|
880
|
+
nativeViewRef.current?.focus?.();
|
|
881
|
+
requestAnimationFrame(() => {
|
|
882
|
+
nativeViewRef.current?.focus?.();
|
|
883
|
+
});
|
|
884
|
+
setTimeout(() => {
|
|
885
|
+
nativeViewRef.current?.focus?.();
|
|
886
|
+
}, 50);
|
|
887
|
+
}, []);
|
|
839
888
|
const handleFocusChange = (0, react_1.useCallback)((event) => {
|
|
840
889
|
const { isFocused: focused } = event.nativeEvent;
|
|
890
|
+
if (!focused &&
|
|
891
|
+
editable &&
|
|
892
|
+
isFocusedRef.current &&
|
|
893
|
+
(0, EditorToolbar_1.isEditorToolbarFocusPreservationActive)()) {
|
|
894
|
+
setIsFocused(true);
|
|
895
|
+
refocusAfterToolbarInteraction();
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
const wasFocused = isFocusedRef.current;
|
|
899
|
+
isFocusedRef.current = focused;
|
|
841
900
|
setIsFocused(focused);
|
|
842
901
|
if (!focused) {
|
|
843
902
|
setMentionQueryEvent(null);
|
|
844
903
|
}
|
|
845
904
|
if (focused) {
|
|
846
|
-
|
|
905
|
+
if (!wasFocused) {
|
|
906
|
+
onFocusRef.current?.();
|
|
907
|
+
}
|
|
847
908
|
}
|
|
848
|
-
else {
|
|
909
|
+
else if (wasFocused) {
|
|
849
910
|
onBlurRef.current?.();
|
|
850
911
|
}
|
|
851
|
-
}, []);
|
|
912
|
+
}, [editable, refocusAfterToolbarInteraction]);
|
|
852
913
|
(0, react_1.useEffect)(() => {
|
|
853
914
|
if (addons?.mentions != null) {
|
|
854
915
|
return;
|
|
@@ -1130,6 +1191,13 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
1130
1191
|
return '';
|
|
1131
1192
|
return bridgeRef.current.getHtml().replace(/<[^>]+>/g, '');
|
|
1132
1193
|
},
|
|
1194
|
+
async getCaretRect() {
|
|
1195
|
+
const nativeView = nativeViewRef.current;
|
|
1196
|
+
if (!nativeView?.getCaretRect)
|
|
1197
|
+
return null;
|
|
1198
|
+
const raw = await Promise.resolve(nativeView.getCaretRect());
|
|
1199
|
+
return parseCaretRectJson(raw);
|
|
1200
|
+
},
|
|
1133
1201
|
undo() {
|
|
1134
1202
|
runAndApply(() => bridgeRef.current?.undo() ?? null);
|
|
1135
1203
|
},
|
|
@@ -1219,6 +1287,14 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
1219
1287
|
nativeViewStyleParts.push({ height: autoGrowHeight });
|
|
1220
1288
|
}
|
|
1221
1289
|
const nativeViewStyle = nativeViewStyleParts.length <= 1 ? nativeViewStyleParts[0] : nativeViewStyleParts;
|
|
1290
|
+
const toolbarFrameJson = serializeToolbarFrames(editable
|
|
1291
|
+
? [
|
|
1292
|
+
...(toolbarPlacement === 'inline' && inlineToolbarFrame != null
|
|
1293
|
+
? [inlineToolbarFrame]
|
|
1294
|
+
: []),
|
|
1295
|
+
...registeredToolbarFrames,
|
|
1296
|
+
]
|
|
1297
|
+
: undefined);
|
|
1222
1298
|
const jsToolbar = ((0, jsx_runtime_1.jsx)(react_native_1.View, { ref: toolbarRef, testID: 'native-editor-js-toolbar', style: [
|
|
1223
1299
|
styles.inlineToolbar,
|
|
1224
1300
|
{ marginTop: inlineToolbarMarginTop },
|
|
@@ -1270,7 +1346,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
1270
1346
|
'#8E8E93',
|
|
1271
1347
|
},
|
|
1272
1348
|
], children: suggestion.subtitle })) : null] })) }, suggestion.key));
|
|
1273
|
-
}) }) })) : ((0, jsx_runtime_1.jsx)(EditorToolbar_1.EditorToolbar, { activeState: activeState, historyState: historyState, toolbarItems: toolbarItems, theme: theme?.toolbar, showTopBorder: inlineToolbarShowTopBorder, onToggleMark: (mark) => runAndApply(() => bridgeRef.current?.toggleMark(mark) ?? null, {
|
|
1349
|
+
}) }) })) : ((0, jsx_runtime_1.jsx)(EditorToolbar_1.EditorToolbar, { activeState: activeState, historyState: historyState, toolbarItems: toolbarItems, theme: theme?.toolbar, showTopBorder: inlineToolbarShowTopBorder, preserveEditorFocus: false, onToggleMark: (mark) => runAndApply(() => bridgeRef.current?.toggleMark(mark) ?? null, {
|
|
1274
1350
|
skipNativeApplyIfContentUnchanged: true,
|
|
1275
1351
|
}), onToggleListType: (listType) => runAndApply(() => bridgeRef.current?.toggleList(listType) ?? null), onToggleHeading: (level) => runAndApply(() => bridgeRef.current?.toggleHeading(level) ?? null), onToggleBlockquote: () => runAndApply(() => bridgeRef.current?.toggleBlockquote() ?? null), onInsertNodeType: (nodeType) => runAndApply(() => bridgeRef.current?.insertNode(nodeType) ?? null), onRunCommand: (command) => {
|
|
1276
1352
|
switch (command) {
|
|
@@ -1296,7 +1372,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
1296
1372
|
}), onToggleStrike: () => runAndApply(() => bridgeRef.current?.toggleMark('strike') ?? null, {
|
|
1297
1373
|
skipNativeApplyIfContentUnchanged: true,
|
|
1298
1374
|
}), onToggleBulletList: () => runAndApply(() => bridgeRef.current?.toggleList('bulletList') ?? null), onToggleOrderedList: () => runAndApply(() => bridgeRef.current?.toggleList('orderedList') ?? null), onIndentList: () => runAndApply(() => bridgeRef.current?.indentListItem() ?? null), onOutdentList: () => runAndApply(() => bridgeRef.current?.outdentListItem() ?? null), onInsertHorizontalRule: () => runAndApply(() => bridgeRef.current?.insertNode('horizontalRule') ?? null), onInsertLineBreak: () => runAndApply(() => bridgeRef.current?.insertNode('hardBreak') ?? null), onUndo: () => runAndApply(() => bridgeRef.current?.undo() ?? null), onRedo: () => runAndApply(() => bridgeRef.current?.redo() ?? null) })) }));
|
|
1299
|
-
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.container, containerStyle], children: [(0, jsx_runtime_1.jsx)(NativeEditorView, { ref: nativeViewRef, style: nativeViewStyle, editorId: editorInstanceId, placeholder: placeholder, editable: editable, autoFocus: autoFocus, showToolbar: showToolbar, toolbarPlacement: toolbarPlacement, heightBehavior: heightBehavior, allowImageResizing: allowImageResizing, themeJson: themeJson, addonsJson: addonsJson, toolbarItemsJson: toolbarItemsJson, remoteSelectionsJson: remoteSelectionsJson, toolbarFrameJson:
|
|
1375
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.container, containerStyle], children: [(0, jsx_runtime_1.jsx)(NativeEditorView, { ref: nativeViewRef, style: nativeViewStyle, editorId: editorInstanceId, placeholder: placeholder, editable: editable, autoFocus: autoFocus, showToolbar: showToolbar, toolbarPlacement: toolbarPlacement, heightBehavior: heightBehavior, allowImageResizing: allowImageResizing, themeJson: themeJson, addonsJson: addonsJson, toolbarItemsJson: toolbarItemsJson, remoteSelectionsJson: remoteSelectionsJson, toolbarFrameJson: toolbarFrameJson, editorUpdateJson: pendingNativeUpdate.json, editorUpdateRevision: pendingNativeUpdate.revision, onEditorUpdate: handleUpdate, onSelectionChange: handleSelectionChange, onFocusChange: handleFocusChange, onContentHeightChange: handleContentHeightChange, onToolbarAction: handleToolbarAction, onAddonEvent: handleAddonEvent }, DEV_NATIVE_VIEW_KEY), shouldRenderJsToolbar && jsToolbar] }));
|
|
1300
1376
|
});
|
|
1301
1377
|
const styles = react_native_1.StyleSheet.create({
|
|
1302
1378
|
container: {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { NativeRichTextEditor, type NativeRichTextEditorProps, type NativeRichTextEditorRef, type NativeRichTextEditorHeightBehavior, type NativeRichTextEditorToolbarPlacement, type RemoteSelectionDecoration, type LinkRequestContext, type ImageRequestContext, } from './NativeRichTextEditor';
|
|
1
|
+
export { NativeRichTextEditor, type NativeRichTextEditorProps, type NativeRichTextEditorRef, type NativeRichTextEditorCaretRect, type NativeRichTextEditorHeightBehavior, type NativeRichTextEditorToolbarPlacement, type RemoteSelectionDecoration, type LinkRequestContext, type ImageRequestContext, } from './NativeRichTextEditor';
|
|
2
2
|
export { NativeProseViewer, type NativeProseViewerProps, type NativeProseViewerAddons, type NativeProseViewerMentionsAddonConfig, type NativeProseViewerMentionPrefix, type NativeProseViewerLinkPressEvent, type NativeProseViewerMentionRenderContext, type NativeProseViewerMentionPressEvent, } from './NativeProseViewer';
|
|
3
3
|
export { EditorToolbar, DEFAULT_EDITOR_TOOLBAR_ITEMS, type EditorToolbarProps, type EditorToolbarItem, type EditorToolbarLeafItem, type EditorToolbarGroupChildItem, type EditorToolbarGroupItem, type EditorToolbarGroupPresentation, type EditorToolbarIcon, type EditorToolbarDefaultIconId, type EditorToolbarSFSymbolIcon, type EditorToolbarMaterialIcon, type EditorToolbarCommand, type EditorToolbarHeadingLevel, type EditorToolbarListType, } from './EditorToolbar';
|
|
4
4
|
export type { EditorContentInsets, EditorTheme, EditorTextStyle, EditorLinkTheme, EditorHeadingTheme, EditorListTheme, EditorHorizontalRuleTheme, EditorMentionTheme, EditorToolbarTheme, EditorToolbarAppearance, EditorFontStyle, EditorFontWeight, } from './EditorTheme';
|
|
@@ -8,32 +8,32 @@
|
|
|
8
8
|
<key>BinaryPath</key>
|
|
9
9
|
<string>libeditor_core.a</string>
|
|
10
10
|
<key>LibraryIdentifier</key>
|
|
11
|
-
<string>ios-
|
|
11
|
+
<string>ios-arm64</string>
|
|
12
12
|
<key>LibraryPath</key>
|
|
13
13
|
<string>libeditor_core.a</string>
|
|
14
14
|
<key>SupportedArchitectures</key>
|
|
15
15
|
<array>
|
|
16
16
|
<string>arm64</string>
|
|
17
|
-
<string>x86_64</string>
|
|
18
17
|
</array>
|
|
19
18
|
<key>SupportedPlatform</key>
|
|
20
19
|
<string>ios</string>
|
|
21
|
-
<key>SupportedPlatformVariant</key>
|
|
22
|
-
<string>simulator</string>
|
|
23
20
|
</dict>
|
|
24
21
|
<dict>
|
|
25
22
|
<key>BinaryPath</key>
|
|
26
23
|
<string>libeditor_core.a</string>
|
|
27
24
|
<key>LibraryIdentifier</key>
|
|
28
|
-
<string>ios-
|
|
25
|
+
<string>ios-arm64_x86_64-simulator</string>
|
|
29
26
|
<key>LibraryPath</key>
|
|
30
27
|
<string>libeditor_core.a</string>
|
|
31
28
|
<key>SupportedArchitectures</key>
|
|
32
29
|
<array>
|
|
33
30
|
<string>arm64</string>
|
|
31
|
+
<string>x86_64</string>
|
|
34
32
|
</array>
|
|
35
33
|
<key>SupportedPlatform</key>
|
|
36
34
|
<string>ios</string>
|
|
35
|
+
<key>SupportedPlatformVariant</key>
|
|
36
|
+
<string>simulator</string>
|
|
37
37
|
</dict>
|
|
38
38
|
</array>
|
|
39
39
|
<key>CFBundlePackageType</key>
|
|
Binary file
|
|
Binary file
|
|
@@ -1626,7 +1626,8 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
|
|
|
1626
1626
|
inputViewStyle: .keyboard
|
|
1627
1627
|
)
|
|
1628
1628
|
private let accessoryPlaceholder = EditorAccessoryPlaceholderView(frame: .zero)
|
|
1629
|
-
private var
|
|
1629
|
+
private var toolbarFramesInWindow: [CGRect] = []
|
|
1630
|
+
private var lastToolbarTouchUptime: TimeInterval = -Double.infinity
|
|
1630
1631
|
private var didApplyAutoFocus = false
|
|
1631
1632
|
private var toolbarState = NativeToolbarState.empty
|
|
1632
1633
|
private var toolbarItems: [NativeToolbarItem] = NativeToolbarItem.defaults
|
|
@@ -1855,17 +1856,32 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
|
|
|
1855
1856
|
lastToolbarFrameJSON = toolbarFrameJson
|
|
1856
1857
|
guard let toolbarFrameJson,
|
|
1857
1858
|
let data = toolbarFrameJson.data(using: .utf8),
|
|
1858
|
-
let raw = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
|
|
1859
|
-
|
|
1859
|
+
let raw = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
|
|
1860
|
+
else {
|
|
1861
|
+
toolbarFramesInWindow = []
|
|
1862
|
+
return
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
if let frameDictionaries = raw["frames"] as? [[String: Any]] {
|
|
1866
|
+
toolbarFramesInWindow = frameDictionaries.compactMap(Self.toolbarFrame(from:))
|
|
1867
|
+
return
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
toolbarFramesInWindow = Self.toolbarFrame(from: raw).map { [$0] } ?? []
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
private static func toolbarFrame(from raw: [String: Any]) -> CGRect? {
|
|
1874
|
+
guard let x = (raw["x"] as? NSNumber)?.doubleValue,
|
|
1860
1875
|
let y = (raw["y"] as? NSNumber)?.doubleValue,
|
|
1861
1876
|
let width = (raw["width"] as? NSNumber)?.doubleValue,
|
|
1862
|
-
let height = (raw["height"] as? NSNumber)?.doubleValue
|
|
1877
|
+
let height = (raw["height"] as? NSNumber)?.doubleValue,
|
|
1878
|
+
width > 0,
|
|
1879
|
+
height > 0
|
|
1863
1880
|
else {
|
|
1864
|
-
|
|
1865
|
-
return
|
|
1881
|
+
return nil
|
|
1866
1882
|
}
|
|
1867
1883
|
|
|
1868
|
-
|
|
1884
|
+
return CGRect(x: x, y: y, width: width, height: height)
|
|
1869
1885
|
}
|
|
1870
1886
|
|
|
1871
1887
|
func setPendingEditorUpdateJson(_ editorUpdateJson: String?) {
|
|
@@ -1904,6 +1920,30 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
|
|
|
1904
1920
|
richTextView.textView.resignFirstResponder()
|
|
1905
1921
|
}
|
|
1906
1922
|
|
|
1923
|
+
func getCaretRectJson() -> String? {
|
|
1924
|
+
layoutIfNeeded()
|
|
1925
|
+
richTextView.layoutIfNeeded()
|
|
1926
|
+
|
|
1927
|
+
guard let caretRect = richTextView.currentCaretRect() else {
|
|
1928
|
+
return nil
|
|
1929
|
+
}
|
|
1930
|
+
let editorRect = richTextView.convert(caretRect, to: self)
|
|
1931
|
+
let payload: [String: Any] = [
|
|
1932
|
+
"x": editorRect.minX,
|
|
1933
|
+
"y": editorRect.minY,
|
|
1934
|
+
"width": editorRect.width,
|
|
1935
|
+
"height": editorRect.height,
|
|
1936
|
+
"editorWidth": bounds.width,
|
|
1937
|
+
"editorHeight": bounds.height,
|
|
1938
|
+
]
|
|
1939
|
+
guard let data = try? JSONSerialization.data(withJSONObject: payload),
|
|
1940
|
+
let json = String(data: data, encoding: .utf8)
|
|
1941
|
+
else {
|
|
1942
|
+
return nil
|
|
1943
|
+
}
|
|
1944
|
+
return json
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1907
1947
|
// MARK: - Focus Notifications
|
|
1908
1948
|
|
|
1909
1949
|
@objc private func textViewDidBeginEditing(_ notification: Notification) {
|
|
@@ -1914,6 +1954,13 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
|
|
|
1914
1954
|
}
|
|
1915
1955
|
|
|
1916
1956
|
@objc private func textViewDidEndEditing(_ notification: Notification) {
|
|
1957
|
+
if shouldPreserveFocusAfterToolbarTouch() {
|
|
1958
|
+
DispatchQueue.main.async { [weak self] in
|
|
1959
|
+
self?.richTextView.textView.becomeFirstResponder()
|
|
1960
|
+
}
|
|
1961
|
+
return
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1917
1964
|
uninstallOutsideTapRecognizer()
|
|
1918
1965
|
richTextView.textView.refreshSelectionVisualState()
|
|
1919
1966
|
clearMentionQueryStateAndHidePopover()
|
|
@@ -1952,6 +1999,9 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
|
|
|
1952
1999
|
guard gestureRecognizer === outsideTapGestureRecognizer else { return true }
|
|
1953
2000
|
guard let tapWindow = gestureWindow ?? window else { return true }
|
|
1954
2001
|
let locationInWindow = touch.location(in: tapWindow)
|
|
2002
|
+
if isLocationInStandaloneToolbarFrame(locationInWindow) {
|
|
2003
|
+
markRecentToolbarTouch()
|
|
2004
|
+
}
|
|
1955
2005
|
let result = shouldHandleOutsideTap(
|
|
1956
2006
|
locationInWindow: locationInWindow,
|
|
1957
2007
|
touchedView: touch.view
|
|
@@ -1959,6 +2009,18 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
|
|
|
1959
2009
|
return result
|
|
1960
2010
|
}
|
|
1961
2011
|
|
|
2012
|
+
private func markRecentToolbarTouch() {
|
|
2013
|
+
lastToolbarTouchUptime = ProcessInfo.processInfo.systemUptime
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
private func shouldPreserveFocusAfterToolbarTouch() -> Bool {
|
|
2017
|
+
ProcessInfo.processInfo.systemUptime - lastToolbarTouchUptime <= 0.75
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
private func isLocationInStandaloneToolbarFrame(_ locationInWindow: CGPoint) -> Bool {
|
|
2021
|
+
toolbarFramesInWindow.contains(where: { $0.contains(locationInWindow) })
|
|
2022
|
+
}
|
|
2023
|
+
|
|
1962
2024
|
private func shouldHandleOutsideTap(
|
|
1963
2025
|
locationInWindow: CGPoint,
|
|
1964
2026
|
touchedView: UIView?
|
|
@@ -1975,7 +2037,7 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
|
|
|
1975
2037
|
if let touchedView, touchedView.isDescendant(of: accessoryToolbar) {
|
|
1976
2038
|
return false
|
|
1977
2039
|
}
|
|
1978
|
-
if
|
|
2040
|
+
if isLocationInStandaloneToolbarFrame(locationInWindow) {
|
|
1979
2041
|
return false
|
|
1980
2042
|
}
|
|
1981
2043
|
return true
|
|
@@ -386,6 +386,9 @@ public class NativeEditorModule: Module {
|
|
|
386
386
|
AsyncFunction("blur") { (view: NativeEditorExpoView) in
|
|
387
387
|
view.blur()
|
|
388
388
|
}
|
|
389
|
+
AsyncFunction("getCaretRect") { (view: NativeEditorExpoView) -> String? in
|
|
390
|
+
view.getCaretRectJson()
|
|
391
|
+
}
|
|
389
392
|
}
|
|
390
393
|
|
|
391
394
|
View(NativeProseViewerExpoView.self) {
|