@apollohg/react-native-prose-editor 0.5.13 → 0.5.15
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/build.gradle +3 -1
- package/android/consumer-rules.pro +6 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditText.kt +113 -4
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +12 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +9 -0
- package/dist/EditorToolbar.d.ts +12 -1
- package/dist/EditorToolbar.js +131 -2
- package/dist/NativeRichTextEditor.d.ts +8 -0
- package/dist/NativeRichTextEditor.js +112 -19
- 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 +14 -2
- package/ios/NativeEditorModule.swift +9 -0
- package/ios/RichTextEditorView.swift +60 -3
- 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/android/build.gradle
CHANGED
|
@@ -57,7 +57,9 @@ android {
|
|
|
57
57
|
dependencies {
|
|
58
58
|
implementation "androidx.appcompat:appcompat:1.7.0"
|
|
59
59
|
implementation "com.google.android.material:material:1.12.0"
|
|
60
|
-
|
|
60
|
+
// UniFFI loads the Rust bridge through JNA at runtime. Expose JNA to
|
|
61
|
+
// consuming apps so release shrinkers see the dependency on the app graph.
|
|
62
|
+
api "net.java.dev.jna:jna:5.18.1@aar"
|
|
61
63
|
|
|
62
64
|
testImplementation "junit:junit:4.13.2"
|
|
63
65
|
testImplementation "org.robolectric:robolectric:4.14.1"
|
|
@@ -6,3 +6,9 @@
|
|
|
6
6
|
# names to the Rust library. Keep these names stable in consuming release builds.
|
|
7
7
|
-keep class com.sun.jna.** { *; }
|
|
8
8
|
-keep class uniffi.editor_core.** { *; }
|
|
9
|
+
-keepattributes Signature,InnerClasses,EnclosingMethod,*Annotation*
|
|
10
|
+
|
|
11
|
+
# JNA inspects Library interfaces and Structure fields reflectively. These
|
|
12
|
+
# member rules make that contract explicit for ProGuard/R8 consumers.
|
|
13
|
+
-keepclassmembers class * implements com.sun.jna.Library { *; }
|
|
14
|
+
-keepclassmembers class * extends com.sun.jna.Structure { *; }
|
|
@@ -7,6 +7,7 @@ import android.graphics.Rect
|
|
|
7
7
|
import android.graphics.RectF
|
|
8
8
|
import android.text.Annotation
|
|
9
9
|
import android.text.Editable
|
|
10
|
+
import android.text.InputType
|
|
10
11
|
import android.text.Layout
|
|
11
12
|
import android.text.Spanned
|
|
12
13
|
import android.text.StaticLayout
|
|
@@ -19,6 +20,7 @@ import android.view.KeyEvent
|
|
|
19
20
|
import android.view.MotionEvent
|
|
20
21
|
import android.view.inputmethod.EditorInfo
|
|
21
22
|
import android.view.inputmethod.InputConnection
|
|
23
|
+
import android.view.inputmethod.InputMethodManager
|
|
22
24
|
import androidx.appcompat.widget.AppCompatEditText
|
|
23
25
|
import kotlin.math.roundToInt
|
|
24
26
|
import uniffi.editor_core.* // UniFFI-generated bindings
|
|
@@ -162,6 +164,9 @@ class EditorEditText @JvmOverloads constructor(
|
|
|
162
164
|
var heightBehavior: EditorHeightBehavior = EditorHeightBehavior.FIXED
|
|
163
165
|
private set
|
|
164
166
|
private var imageResizingEnabled = true
|
|
167
|
+
private var nativeAutoCapitalize = DEFAULT_AUTO_CAPITALIZE
|
|
168
|
+
private var nativeAutoCorrect = DEFAULT_AUTO_CORRECT
|
|
169
|
+
private var nativeKeyboardType = DEFAULT_KEYBOARD_TYPE
|
|
165
170
|
|
|
166
171
|
private var contentInsets: EditorContentInsets? = null
|
|
167
172
|
private var viewportBottomInsetPx: Int = 0
|
|
@@ -198,10 +203,7 @@ class EditorEditText @JvmOverloads constructor(
|
|
|
198
203
|
|
|
199
204
|
init {
|
|
200
205
|
// Configure for rich text editing.
|
|
201
|
-
inputType =
|
|
202
|
-
EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE or
|
|
203
|
-
EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT or
|
|
204
|
-
EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES
|
|
206
|
+
inputType = resolvedInputType()
|
|
205
207
|
|
|
206
208
|
// Disable built-in spell checking to avoid conflicts with Rust state.
|
|
207
209
|
// The Rust editor is the source of truth for text content.
|
|
@@ -224,6 +226,110 @@ class EditorEditText @JvmOverloads constructor(
|
|
|
224
226
|
updateEffectivePadding()
|
|
225
227
|
}
|
|
226
228
|
|
|
229
|
+
fun setAutoCapitalize(autoCapitalize: String?) {
|
|
230
|
+
val next = when (autoCapitalize) {
|
|
231
|
+
"none",
|
|
232
|
+
"sentences",
|
|
233
|
+
"words",
|
|
234
|
+
"characters" -> autoCapitalize
|
|
235
|
+
else -> DEFAULT_AUTO_CAPITALIZE
|
|
236
|
+
}
|
|
237
|
+
if (nativeAutoCapitalize == next) return
|
|
238
|
+
nativeAutoCapitalize = next
|
|
239
|
+
applyInputTraits()
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
fun setAutoCorrect(autoCorrect: Boolean?) {
|
|
243
|
+
val next = autoCorrect ?: DEFAULT_AUTO_CORRECT
|
|
244
|
+
if (nativeAutoCorrect == next) return
|
|
245
|
+
nativeAutoCorrect = next
|
|
246
|
+
applyInputTraits()
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
fun setKeyboardType(keyboardType: String?) {
|
|
250
|
+
val next = when (keyboardType) {
|
|
251
|
+
"default",
|
|
252
|
+
"email-address",
|
|
253
|
+
"numeric",
|
|
254
|
+
"phone-pad",
|
|
255
|
+
"ascii-capable",
|
|
256
|
+
"numbers-and-punctuation",
|
|
257
|
+
"url",
|
|
258
|
+
"number-pad",
|
|
259
|
+
"name-phone-pad",
|
|
260
|
+
"decimal-pad",
|
|
261
|
+
"twitter",
|
|
262
|
+
"web-search",
|
|
263
|
+
"visible-password",
|
|
264
|
+
"ascii-capable-number-pad" -> keyboardType
|
|
265
|
+
else -> DEFAULT_KEYBOARD_TYPE
|
|
266
|
+
}
|
|
267
|
+
if (nativeKeyboardType == next) return
|
|
268
|
+
nativeKeyboardType = next
|
|
269
|
+
applyInputTraits()
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private fun applyInputTraits() {
|
|
273
|
+
val nextInputType = resolvedInputType()
|
|
274
|
+
if (inputType == nextInputType) return
|
|
275
|
+
|
|
276
|
+
val currentStart = selectionStart
|
|
277
|
+
val currentEnd = selectionEnd
|
|
278
|
+
setRawInputType(nextInputType)
|
|
279
|
+
|
|
280
|
+
val editable = text
|
|
281
|
+
if (
|
|
282
|
+
editable != null &&
|
|
283
|
+
currentStart >= 0 &&
|
|
284
|
+
currentEnd >= 0 &&
|
|
285
|
+
currentStart <= editable.length &&
|
|
286
|
+
currentEnd <= editable.length
|
|
287
|
+
) {
|
|
288
|
+
setSelection(currentStart, currentEnd)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (hasFocus()) {
|
|
292
|
+
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
|
293
|
+
imm?.restartInput(this)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private fun resolvedInputType(): Int {
|
|
298
|
+
var nextInputType = when (nativeKeyboardType) {
|
|
299
|
+
"email-address" -> InputType.TYPE_CLASS_TEXT or
|
|
300
|
+
InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
|
|
301
|
+
"url" -> InputType.TYPE_CLASS_TEXT or
|
|
302
|
+
InputType.TYPE_TEXT_VARIATION_URI
|
|
303
|
+
"phone-pad" -> InputType.TYPE_CLASS_PHONE
|
|
304
|
+
"number-pad" -> InputType.TYPE_CLASS_NUMBER
|
|
305
|
+
"decimal-pad" -> InputType.TYPE_CLASS_NUMBER or
|
|
306
|
+
InputType.TYPE_NUMBER_FLAG_DECIMAL
|
|
307
|
+
"numeric" -> InputType.TYPE_CLASS_NUMBER or
|
|
308
|
+
InputType.TYPE_NUMBER_FLAG_DECIMAL or
|
|
309
|
+
InputType.TYPE_NUMBER_FLAG_SIGNED
|
|
310
|
+
"visible-password" -> InputType.TYPE_CLASS_TEXT or
|
|
311
|
+
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
|
312
|
+
else -> InputType.TYPE_CLASS_TEXT
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if ((nextInputType and InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT) {
|
|
316
|
+
nextInputType = nextInputType or InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
|
317
|
+
nextInputType = nextInputType or when (nativeAutoCapitalize) {
|
|
318
|
+
"none" -> 0
|
|
319
|
+
"words" -> InputType.TYPE_TEXT_FLAG_CAP_WORDS
|
|
320
|
+
"characters" -> InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS
|
|
321
|
+
else -> InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
|
|
322
|
+
}
|
|
323
|
+
nextInputType = nextInputType or if (nativeAutoCorrect) {
|
|
324
|
+
InputType.TYPE_TEXT_FLAG_AUTO_CORRECT
|
|
325
|
+
} else {
|
|
326
|
+
InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return nextInputType
|
|
331
|
+
}
|
|
332
|
+
|
|
227
333
|
// ── InputConnection Override ────────────────────────────────────────
|
|
228
334
|
|
|
229
335
|
/**
|
|
@@ -1929,6 +2035,9 @@ class EditorEditText @JvmOverloads constructor(
|
|
|
1929
2035
|
}
|
|
1930
2036
|
|
|
1931
2037
|
companion object {
|
|
2038
|
+
private const val DEFAULT_AUTO_CAPITALIZE = "sentences"
|
|
2039
|
+
private const val DEFAULT_AUTO_CORRECT = true
|
|
2040
|
+
private const val DEFAULT_KEYBOARD_TYPE = "default"
|
|
1932
2041
|
private const val EMPTY_BLOCK_PLACEHOLDER = '\u200B'
|
|
1933
2042
|
private const val LOG_TAG = "NativeEditor"
|
|
1934
2043
|
}
|
|
@@ -181,6 +181,18 @@ class NativeEditorExpoView(
|
|
|
181
181
|
focus()
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
fun setAutoCapitalize(autoCapitalize: String?) {
|
|
185
|
+
richTextView.editorEditText.setAutoCapitalize(autoCapitalize)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
fun setAutoCorrect(autoCorrect: Boolean?) {
|
|
189
|
+
richTextView.editorEditText.setAutoCorrect(autoCorrect)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
fun setKeyboardType(keyboardType: String?) {
|
|
193
|
+
richTextView.editorEditText.setKeyboardType(keyboardType)
|
|
194
|
+
}
|
|
195
|
+
|
|
184
196
|
fun setShowToolbar(showToolbar: Boolean) {
|
|
185
197
|
showsToolbar = showToolbar
|
|
186
198
|
updateKeyboardToolbarVisibility()
|
|
@@ -336,6 +336,15 @@ class NativeEditorModule : Module() {
|
|
|
336
336
|
Prop("autoFocus") { view: NativeEditorExpoView, autoFocus: Boolean ->
|
|
337
337
|
view.setAutoFocus(autoFocus)
|
|
338
338
|
}
|
|
339
|
+
Prop("autoCapitalize") { view: NativeEditorExpoView, autoCapitalize: String? ->
|
|
340
|
+
view.setAutoCapitalize(autoCapitalize)
|
|
341
|
+
}
|
|
342
|
+
Prop("autoCorrect") { view: NativeEditorExpoView, autoCorrect: Boolean? ->
|
|
343
|
+
view.setAutoCorrect(autoCorrect)
|
|
344
|
+
}
|
|
345
|
+
Prop("keyboardType") { view: NativeEditorExpoView, keyboardType: String? ->
|
|
346
|
+
view.setKeyboardType(keyboardType)
|
|
347
|
+
}
|
|
339
348
|
Prop("showToolbar") { view: NativeEditorExpoView, showToolbar: Boolean ->
|
|
340
349
|
view.setShowToolbar(showToolbar)
|
|
341
350
|
}
|
package/dist/EditorToolbar.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ActiveState, HistoryState } from './NativeEditorBridge';
|
|
2
|
-
import type { EditorToolbarTheme } from './EditorTheme';
|
|
2
|
+
import type { EditorMentionTheme, EditorToolbarTheme } from './EditorTheme';
|
|
3
|
+
import type { MentionSuggestion } from './addons';
|
|
3
4
|
export type EditorToolbarListType = 'bulletList' | 'orderedList';
|
|
4
5
|
export type EditorToolbarHeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
|
|
5
6
|
export type EditorToolbarCommand = 'indentList' | 'outdentList' | 'undo' | 'redo';
|
|
@@ -97,8 +98,17 @@ export interface EditorToolbarFrame {
|
|
|
97
98
|
width: number;
|
|
98
99
|
height: number;
|
|
99
100
|
}
|
|
101
|
+
interface EditorToolbarMentionState {
|
|
102
|
+
ownerId: number;
|
|
103
|
+
trigger: string;
|
|
104
|
+
suggestions: readonly MentionSuggestion[];
|
|
105
|
+
theme?: EditorMentionTheme;
|
|
106
|
+
suggestionThemes?: Readonly<Record<string, EditorMentionTheme | undefined>>;
|
|
107
|
+
onSelectSuggestion: (suggestion: MentionSuggestion) => void;
|
|
108
|
+
}
|
|
100
109
|
export declare function isEditorToolbarFocusPreservationActive(): boolean;
|
|
101
110
|
export declare function useEditorToolbarFrames(): readonly EditorToolbarFrame[];
|
|
111
|
+
export declare function setEditorToolbarMentionState(ownerId: number, state: Omit<EditorToolbarMentionState, 'ownerId'> | null): void;
|
|
102
112
|
export declare function _setEditorToolbarFrameForTests(id: number, frame: EditorToolbarFrame | null): void;
|
|
103
113
|
export declare function _resetEditorToolbarFrameRegistryForTests(): void;
|
|
104
114
|
export declare function _beginEditorToolbarInteractionForTests(): void;
|
|
@@ -164,3 +174,4 @@ export interface EditorToolbarProps {
|
|
|
164
174
|
preserveEditorFocus?: boolean;
|
|
165
175
|
}
|
|
166
176
|
export declare 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, theme, showTopBorder, preserveEditorFocus, }: EditorToolbarProps): import("react/jsx-runtime").JSX.Element;
|
|
177
|
+
export {};
|
package/dist/EditorToolbar.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.DEFAULT_EDITOR_TOOLBAR_ITEMS = void 0;
|
|
4
4
|
exports.isEditorToolbarFocusPreservationActive = isEditorToolbarFocusPreservationActive;
|
|
5
5
|
exports.useEditorToolbarFrames = useEditorToolbarFrames;
|
|
6
|
+
exports.setEditorToolbarMentionState = setEditorToolbarMentionState;
|
|
6
7
|
exports._setEditorToolbarFrameForTests = _setEditorToolbarFrameForTests;
|
|
7
8
|
exports._resetEditorToolbarFrameRegistryForTests = _resetEditorToolbarFrameRegistryForTests;
|
|
8
9
|
exports._beginEditorToolbarInteractionForTests = _beginEditorToolbarInteractionForTests;
|
|
@@ -14,9 +15,11 @@ const react_1 = require("react");
|
|
|
14
15
|
const react_native_1 = require("react-native");
|
|
15
16
|
const editorToolbarFrames = new Map();
|
|
16
17
|
const editorToolbarFrameListeners = new Set();
|
|
18
|
+
const editorToolbarMentionStateListeners = new Set();
|
|
17
19
|
let nextEditorToolbarRegistrationId = 1;
|
|
18
20
|
let activeEditorToolbarInteractions = 0;
|
|
19
21
|
let editorToolbarFocusPreserveUntil = 0;
|
|
22
|
+
let editorToolbarMentionState = null;
|
|
20
23
|
const EDITOR_TOOLBAR_FOCUS_PRESERVE_MS = 750;
|
|
21
24
|
function areToolbarFramesEqual(left, right) {
|
|
22
25
|
return (left?.x === right?.x &&
|
|
@@ -27,9 +30,24 @@ function areToolbarFramesEqual(left, right) {
|
|
|
27
30
|
function notifyEditorToolbarFrameListeners() {
|
|
28
31
|
editorToolbarFrameListeners.forEach((listener) => listener());
|
|
29
32
|
}
|
|
33
|
+
function notifyEditorToolbarMentionStateListeners() {
|
|
34
|
+
editorToolbarMentionStateListeners.forEach((listener) => listener());
|
|
35
|
+
}
|
|
30
36
|
function getEditorToolbarFramesSnapshot() {
|
|
31
37
|
return Array.from(editorToolbarFrames.values());
|
|
32
38
|
}
|
|
39
|
+
function subscribeEditorToolbarMentionState(listener) {
|
|
40
|
+
editorToolbarMentionStateListeners.add(listener);
|
|
41
|
+
return () => {
|
|
42
|
+
editorToolbarMentionStateListeners.delete(listener);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function getEditorToolbarMentionStateSnapshot() {
|
|
46
|
+
return editorToolbarMentionState;
|
|
47
|
+
}
|
|
48
|
+
function useEditorToolbarMentionState() {
|
|
49
|
+
return (0, react_1.useSyncExternalStore)(subscribeEditorToolbarMentionState, getEditorToolbarMentionStateSnapshot, getEditorToolbarMentionStateSnapshot);
|
|
50
|
+
}
|
|
33
51
|
function registerEditorToolbarFrame(id, frame) {
|
|
34
52
|
if (frame == null || frame.width <= 0 || frame.height <= 0) {
|
|
35
53
|
if (editorToolbarFrames.delete(id)) {
|
|
@@ -75,14 +93,31 @@ function useEditorToolbarFrames() {
|
|
|
75
93
|
}, []);
|
|
76
94
|
return frames;
|
|
77
95
|
}
|
|
96
|
+
function setEditorToolbarMentionState(ownerId, state) {
|
|
97
|
+
if (state == null) {
|
|
98
|
+
if (editorToolbarMentionState?.ownerId !== ownerId) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
editorToolbarMentionState = null;
|
|
102
|
+
notifyEditorToolbarMentionStateListeners();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
editorToolbarMentionState = {
|
|
106
|
+
ownerId,
|
|
107
|
+
...state,
|
|
108
|
+
};
|
|
109
|
+
notifyEditorToolbarMentionStateListeners();
|
|
110
|
+
}
|
|
78
111
|
function _setEditorToolbarFrameForTests(id, frame) {
|
|
79
112
|
registerEditorToolbarFrame(id, frame);
|
|
80
113
|
}
|
|
81
114
|
function _resetEditorToolbarFrameRegistryForTests() {
|
|
82
115
|
editorToolbarFrames.clear();
|
|
116
|
+
editorToolbarMentionState = null;
|
|
83
117
|
activeEditorToolbarInteractions = 0;
|
|
84
118
|
editorToolbarFocusPreserveUntil = 0;
|
|
85
119
|
notifyEditorToolbarFrameListeners();
|
|
120
|
+
notifyEditorToolbarMentionStateListeners();
|
|
86
121
|
}
|
|
87
122
|
function _beginEditorToolbarInteractionForTests() {
|
|
88
123
|
beginEditorToolbarInteraction();
|
|
@@ -194,6 +229,9 @@ const DEFAULT_MATERIAL_ICONS = {
|
|
|
194
229
|
undo: 'undo',
|
|
195
230
|
redo: 'redo',
|
|
196
231
|
};
|
|
232
|
+
function resolveMentionSuggestionLabel(suggestion, trigger) {
|
|
233
|
+
return suggestion.label?.trim() || `${trigger}${suggestion.title}`;
|
|
234
|
+
}
|
|
197
235
|
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, }) {
|
|
198
236
|
const marks = activeState.marks ?? {};
|
|
199
237
|
const nodes = activeState.nodes ?? {};
|
|
@@ -205,6 +243,7 @@ function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic
|
|
|
205
243
|
const { width: windowWidth, height: windowHeight } = (0, react_native_1.useWindowDimensions)();
|
|
206
244
|
const [expandedGroupKey, setExpandedGroupKey] = (0, react_1.useState)(null);
|
|
207
245
|
const [menuState, setMenuState] = (0, react_1.useState)(null);
|
|
246
|
+
const mentionState = useEditorToolbarMentionState();
|
|
208
247
|
const toolbarInteractionActiveRef = (0, react_1.useRef)(false);
|
|
209
248
|
const framePublishAnimationFramesRef = (0, react_1.useRef)([]);
|
|
210
249
|
const framePublishTimeoutsRef = (0, react_1.useRef)([]);
|
|
@@ -216,6 +255,7 @@ function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic
|
|
|
216
255
|
const isInList = !!nodes['bulletList'] || !!nodes['orderedList'];
|
|
217
256
|
const canIndentList = isInList && !!commands['indentList'];
|
|
218
257
|
const canOutdentList = isInList && !!commands['outdentList'];
|
|
258
|
+
const shouldRenderMentionSuggestions = preserveEditorFocus && mentionState != null && mentionState.suggestions.length > 0;
|
|
219
259
|
const getActionForItem = (0, react_1.useCallback)((item) => {
|
|
220
260
|
switch (item.type) {
|
|
221
261
|
case 'mark':
|
|
@@ -558,6 +598,12 @@ function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic
|
|
|
558
598
|
setMenuState(null);
|
|
559
599
|
}
|
|
560
600
|
}, [groupsByKey, menuState]);
|
|
601
|
+
(0, react_1.useEffect)(() => {
|
|
602
|
+
if (shouldRenderMentionSuggestions) {
|
|
603
|
+
setExpandedGroupKey(null);
|
|
604
|
+
setMenuState(null);
|
|
605
|
+
}
|
|
606
|
+
}, [shouldRenderMentionSuggestions]);
|
|
561
607
|
const handleButtonPress = (0, react_1.useCallback)((button) => {
|
|
562
608
|
button.action();
|
|
563
609
|
if (button.groupKey) {
|
|
@@ -665,7 +711,65 @@ function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic
|
|
|
665
711
|
{
|
|
666
712
|
borderRadius: theme?.borderRadius ?? TOOLBAR_RADIUS,
|
|
667
713
|
},
|
|
668
|
-
], children: [(0, jsx_runtime_1.jsx)(react_native_1.ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false,
|
|
714
|
+
], children: [shouldRenderMentionSuggestions && mentionState != null ? ((0, jsx_runtime_1.jsx)(react_native_1.ScrollView, { testID: 'editor-toolbar-mention-suggestions', horizontal: true, showsHorizontalScrollIndicator: false, style: [
|
|
715
|
+
styles.mentionSuggestionsScroll,
|
|
716
|
+
{
|
|
717
|
+
backgroundColor: mentionState.theme?.popoverBackgroundColor ??
|
|
718
|
+
mentionState.theme?.backgroundColor ??
|
|
719
|
+
'transparent',
|
|
720
|
+
borderColor: mentionState.theme?.popoverBorderColor ??
|
|
721
|
+
mentionState.theme?.borderColor ??
|
|
722
|
+
'transparent',
|
|
723
|
+
borderWidth: mentionState.theme?.popoverBorderWidth ??
|
|
724
|
+
mentionState.theme?.borderWidth ??
|
|
725
|
+
0,
|
|
726
|
+
borderRadius: mentionState.theme?.popoverBorderRadius ??
|
|
727
|
+
mentionState.theme?.borderRadius ??
|
|
728
|
+
0,
|
|
729
|
+
},
|
|
730
|
+
mentionState.theme?.popoverShadowColor != null
|
|
731
|
+
? {
|
|
732
|
+
shadowColor: mentionState.theme.popoverShadowColor,
|
|
733
|
+
shadowOpacity: 0.14,
|
|
734
|
+
shadowRadius: 12,
|
|
735
|
+
shadowOffset: { width: 0, height: 4 },
|
|
736
|
+
elevation: 8,
|
|
737
|
+
}
|
|
738
|
+
: null,
|
|
739
|
+
], contentContainerStyle: styles.mentionSuggestionsContent, keyboardShouldPersistTaps: 'always', children: mentionState.suggestions.map((suggestion) => {
|
|
740
|
+
const label = resolveMentionSuggestionLabel(suggestion, mentionState.trigger);
|
|
741
|
+
const suggestionTheme = mentionState.suggestionThemes?.[suggestion.key] ?? mentionState.theme;
|
|
742
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { testID: `editor-toolbar-mention-suggestion-${suggestion.key}`, accessibilityRole: 'button', accessibilityLabel: label, onPressIn: handleToolbarPressIn, onPressOut: handleToolbarPressOut, onPress: () => mentionState.onSelectSuggestion(suggestion), style: ({ pressed }) => [
|
|
743
|
+
styles.mentionSuggestion,
|
|
744
|
+
{
|
|
745
|
+
backgroundColor: pressed
|
|
746
|
+
? (suggestionTheme?.optionHighlightedBackgroundColor ??
|
|
747
|
+
'rgba(0, 122, 255, 0.12)')
|
|
748
|
+
: (suggestionTheme?.backgroundColor ?? '#F2F2F7'),
|
|
749
|
+
borderColor: suggestionTheme?.borderColor ?? 'transparent',
|
|
750
|
+
borderWidth: suggestionTheme?.borderWidth ?? 0,
|
|
751
|
+
borderRadius: suggestionTheme?.borderRadius ?? 12,
|
|
752
|
+
},
|
|
753
|
+
], children: ({ pressed }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { numberOfLines: 1, style: [
|
|
754
|
+
styles.mentionSuggestionTitle,
|
|
755
|
+
{
|
|
756
|
+
fontWeight: suggestionTheme?.fontWeight ?? '600',
|
|
757
|
+
color: pressed
|
|
758
|
+
? (suggestionTheme?.optionHighlightedTextColor ??
|
|
759
|
+
suggestionTheme?.optionTextColor ??
|
|
760
|
+
'#000000')
|
|
761
|
+
: (suggestionTheme?.optionTextColor ??
|
|
762
|
+
suggestionTheme?.textColor ??
|
|
763
|
+
'#000000'),
|
|
764
|
+
},
|
|
765
|
+
], children: label }), suggestion.subtitle ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { numberOfLines: 1, style: [
|
|
766
|
+
styles.mentionSuggestionSubtitle,
|
|
767
|
+
{
|
|
768
|
+
color: suggestionTheme?.optionSecondaryTextColor ??
|
|
769
|
+
'#8E8E93',
|
|
770
|
+
},
|
|
771
|
+
], children: suggestion.subtitle })) : null] })) }, suggestion.key));
|
|
772
|
+
}) })) : ((0, jsx_runtime_1.jsx)(react_native_1.ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, contentContainerStyle: styles.scrollContent, keyboardShouldPersistTaps: 'always', onScrollBeginDrag: () => setMenuState(null), children: renderedItems.map((item) => {
|
|
669
773
|
if (item.type === 'separator') {
|
|
670
774
|
return renderSeparator(item.key);
|
|
671
775
|
}
|
|
@@ -683,7 +787,7 @@ function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic
|
|
|
683
787
|
});
|
|
684
788
|
}
|
|
685
789
|
return renderButton(item.button, () => handleButtonPress(item.button));
|
|
686
|
-
}) }), menuState != null && menuGroup != null ? ((0, jsx_runtime_1.jsx)(react_native_1.Modal, { transparent: true, visible: true, animationType: 'fade', onRequestClose: () => setMenuState(null), children: (0, jsx_runtime_1.jsx)(react_native_1.Pressable, { style: styles.menuBackdrop, onPress: () => setMenuState(null), children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
|
|
790
|
+
}) })), !shouldRenderMentionSuggestions && menuState != null && menuGroup != null ? ((0, jsx_runtime_1.jsx)(react_native_1.Modal, { transparent: true, visible: true, animationType: 'fade', onRequestClose: () => setMenuState(null), children: (0, jsx_runtime_1.jsx)(react_native_1.Pressable, { style: styles.menuBackdrop, onPress: () => setMenuState(null), children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
|
|
687
791
|
styles.menuCard,
|
|
688
792
|
{
|
|
689
793
|
top: menuTop,
|
|
@@ -760,6 +864,31 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
760
864
|
paddingHorizontal: TOOLBAR_PADDING_H,
|
|
761
865
|
minWidth: '100%',
|
|
762
866
|
},
|
|
867
|
+
mentionSuggestionsContent: {
|
|
868
|
+
paddingHorizontal: 12,
|
|
869
|
+
paddingVertical: 4,
|
|
870
|
+
alignItems: 'center',
|
|
871
|
+
minWidth: '100%',
|
|
872
|
+
},
|
|
873
|
+
mentionSuggestionsScroll: {
|
|
874
|
+
overflow: 'hidden',
|
|
875
|
+
},
|
|
876
|
+
mentionSuggestion: {
|
|
877
|
+
minWidth: 88,
|
|
878
|
+
minHeight: 40,
|
|
879
|
+
marginRight: 8,
|
|
880
|
+
paddingHorizontal: 12,
|
|
881
|
+
paddingVertical: 8,
|
|
882
|
+
justifyContent: 'center',
|
|
883
|
+
},
|
|
884
|
+
mentionSuggestionTitle: {
|
|
885
|
+
fontSize: 14,
|
|
886
|
+
fontWeight: '600',
|
|
887
|
+
},
|
|
888
|
+
mentionSuggestionSubtitle: {
|
|
889
|
+
marginTop: 1,
|
|
890
|
+
fontSize: 12,
|
|
891
|
+
},
|
|
763
892
|
buttonAnchor: {
|
|
764
893
|
position: 'relative',
|
|
765
894
|
},
|
|
@@ -7,6 +7,8 @@ import { type EditorAddons } from './addons';
|
|
|
7
7
|
import { type ImageNodeAttributes, type SchemaDefinition } from './schemas';
|
|
8
8
|
export type NativeRichTextEditorHeightBehavior = 'fixed' | 'autoGrow';
|
|
9
9
|
export type NativeRichTextEditorToolbarPlacement = 'keyboard' | 'inline';
|
|
10
|
+
export type NativeRichTextEditorAutoCapitalize = 'none' | 'sentences' | 'words' | 'characters';
|
|
11
|
+
export type NativeRichTextEditorKeyboardType = 'default' | 'email-address' | 'numeric' | 'phone-pad' | 'ascii-capable' | 'numbers-and-punctuation' | 'url' | 'number-pad' | 'name-phone-pad' | 'decimal-pad' | 'twitter' | 'web-search' | 'visible-password' | 'ascii-capable-number-pad';
|
|
10
12
|
export interface RemoteSelectionDecoration {
|
|
11
13
|
clientId: number;
|
|
12
14
|
anchor: number;
|
|
@@ -49,6 +51,12 @@ export interface NativeRichTextEditorProps {
|
|
|
49
51
|
maxLength?: number;
|
|
50
52
|
/** Whether to auto-focus on mount. */
|
|
51
53
|
autoFocus?: boolean;
|
|
54
|
+
/** Controls native keyboard auto-capitalization. Defaults to sentences. */
|
|
55
|
+
autoCapitalize?: NativeRichTextEditorAutoCapitalize;
|
|
56
|
+
/** Controls native keyboard autocorrection. Defaults to the platform-specific editor default. */
|
|
57
|
+
autoCorrect?: boolean;
|
|
58
|
+
/** Controls the native keyboard layout. Defaults to the platform default keyboard. */
|
|
59
|
+
keyboardType?: NativeRichTextEditorKeyboardType;
|
|
52
60
|
/** Controls whether the editor scrolls internally or grows with content. */
|
|
53
61
|
heightBehavior?: NativeRichTextEditorHeightBehavior;
|
|
54
62
|
/** Whether to show the formatting toolbar. Defaults to true. */
|
|
@@ -98,6 +98,19 @@ function resolveMentionSuggestionAttrs(suggestion, trigger) {
|
|
|
98
98
|
}
|
|
99
99
|
return attrs;
|
|
100
100
|
}
|
|
101
|
+
function mergeMentionSuggestionTheme(baseTheme, resolvedTheme) {
|
|
102
|
+
if (baseTheme == null && resolvedTheme == null) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
const merged = {
|
|
106
|
+
...(baseTheme ?? {}),
|
|
107
|
+
...(resolvedTheme ?? {}),
|
|
108
|
+
};
|
|
109
|
+
if (resolvedTheme?.textColor != null && resolvedTheme.optionTextColor == null) {
|
|
110
|
+
merged.optionTextColor = resolvedTheme.textColor;
|
|
111
|
+
}
|
|
112
|
+
return merged;
|
|
113
|
+
}
|
|
101
114
|
const AUTO_LINK_URL_REGEX = /(?:https?:\/\/|www\.)\S+/giu;
|
|
102
115
|
const AUTO_LINK_INLINE_PLACEHOLDER = '\uFFFC';
|
|
103
116
|
const AUTO_LINK_LEADING_BOUNDARY_CHARS = new Set(['(', '[', '{', '<', '"', "'"]);
|
|
@@ -495,7 +508,7 @@ function useSerializedValue(value, serialize, revision) {
|
|
|
495
508
|
};
|
|
496
509
|
return serialized;
|
|
497
510
|
}
|
|
498
|
-
exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEditor({ initialContent, initialJSON, value, valueJSON, valueJSONRevision, schema, placeholder, editable = true, maxLength, autoFocus = false, heightBehavior = 'autoGrow', showToolbar = true, toolbarPlacement = 'keyboard', toolbarItems = EditorToolbar_1.DEFAULT_EDITOR_TOOLBAR_ITEMS, onToolbarAction, onRequestLink, onRequestImage, autoDetectLinks = false, onContentChange, onContentChangeJSON, onSelectionChange, onActiveStateChange, onHistoryStateChange, onFocus, onBlur, style, containerStyle, theme, addons, remoteSelections, allowBase64Images = false, allowImageResizing = true, }, ref) {
|
|
511
|
+
exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEditor({ initialContent, initialJSON, value, valueJSON, valueJSONRevision, schema, placeholder, editable = true, maxLength, autoFocus = false, autoCapitalize, autoCorrect, keyboardType, heightBehavior = 'autoGrow', showToolbar = true, toolbarPlacement = 'keyboard', toolbarItems = EditorToolbar_1.DEFAULT_EDITOR_TOOLBAR_ITEMS, onToolbarAction, onRequestLink, onRequestImage, autoDetectLinks = false, onContentChange, onContentChangeJSON, onSelectionChange, onActiveStateChange, onHistoryStateChange, onFocus, onBlur, style, containerStyle, theme, addons, remoteSelections, allowBase64Images = false, allowImageResizing = true, }, ref) {
|
|
499
512
|
const bridgeRef = (0, react_1.useRef)(null);
|
|
500
513
|
const nativeViewRef = (0, react_1.useRef)(null);
|
|
501
514
|
const [isReady, setIsReady] = (0, react_1.useState)(false);
|
|
@@ -1215,6 +1228,88 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
1215
1228
|
return bridgeRef.current.canRedo();
|
|
1216
1229
|
},
|
|
1217
1230
|
}), [insertImage, runAndApply]);
|
|
1231
|
+
const activeMentionTrigger = mentionQueryEvent?.trigger || resolveMentionTrigger(addons);
|
|
1232
|
+
const activeMentionSuggestions = (0, react_1.useMemo)(() => isFocused && mentionQueryEvent != null && addons?.mentions != null
|
|
1233
|
+
? filterMentionSuggestions(addons.mentions.suggestions ?? [], mentionQueryEvent.query, activeMentionTrigger)
|
|
1234
|
+
: [], [activeMentionTrigger, addons?.mentions, isFocused, mentionQueryEvent]);
|
|
1235
|
+
const inlineToolbarMentionTheme = theme?.mentions ?? addons?.mentions?.theme;
|
|
1236
|
+
const activeMentionSuggestionThemes = (0, react_1.useMemo)(() => {
|
|
1237
|
+
if (mentionQueryEvent == null ||
|
|
1238
|
+
addons?.mentions == null ||
|
|
1239
|
+
typeof addons.mentions.resolveTheme !== 'function' ||
|
|
1240
|
+
activeMentionSuggestions.length === 0) {
|
|
1241
|
+
return undefined;
|
|
1242
|
+
}
|
|
1243
|
+
const suggestionThemes = {};
|
|
1244
|
+
for (const suggestion of activeMentionSuggestions) {
|
|
1245
|
+
const selectionEvent = {
|
|
1246
|
+
trigger: activeMentionTrigger,
|
|
1247
|
+
suggestion,
|
|
1248
|
+
attrs: resolveMentionSuggestionAttrs(suggestion, activeMentionTrigger),
|
|
1249
|
+
range: mentionQueryEvent.range,
|
|
1250
|
+
};
|
|
1251
|
+
const attrs = resolveMentionSelectionAttrs(selectionEvent);
|
|
1252
|
+
let resolvedTheme;
|
|
1253
|
+
try {
|
|
1254
|
+
const nextTheme = addons.mentions.resolveTheme({
|
|
1255
|
+
...selectionEvent,
|
|
1256
|
+
attrs,
|
|
1257
|
+
});
|
|
1258
|
+
resolvedTheme = isRecord(nextTheme)
|
|
1259
|
+
? nextTheme
|
|
1260
|
+
: undefined;
|
|
1261
|
+
}
|
|
1262
|
+
catch (error) {
|
|
1263
|
+
if (__DEV__) {
|
|
1264
|
+
console.error('NativeRichTextEditor: mentions.resolveTheme threw', error);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
const mergedTheme = mergeMentionSuggestionTheme(inlineToolbarMentionTheme, resolvedTheme);
|
|
1268
|
+
if (mergedTheme != null) {
|
|
1269
|
+
suggestionThemes[suggestion.key] = mergedTheme;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
return Object.keys(suggestionThemes).length > 0 ? suggestionThemes : undefined;
|
|
1273
|
+
}, [
|
|
1274
|
+
activeMentionSuggestions,
|
|
1275
|
+
activeMentionTrigger,
|
|
1276
|
+
addons?.mentions,
|
|
1277
|
+
inlineToolbarMentionTheme,
|
|
1278
|
+
mentionQueryEvent,
|
|
1279
|
+
resolveMentionSelectionAttrs,
|
|
1280
|
+
]);
|
|
1281
|
+
const shouldPublishStandaloneMentionSuggestions = editable &&
|
|
1282
|
+
!showToolbar &&
|
|
1283
|
+
registeredToolbarFrames.length > 0 &&
|
|
1284
|
+
mentionQueryEvent != null &&
|
|
1285
|
+
activeMentionSuggestions.length > 0 &&
|
|
1286
|
+
addons?.mentions != null;
|
|
1287
|
+
(0, react_1.useEffect)(() => {
|
|
1288
|
+
if (editorInstanceId === 0) {
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
if (!shouldPublishStandaloneMentionSuggestions || mentionQueryEvent == null) {
|
|
1292
|
+
(0, EditorToolbar_1.setEditorToolbarMentionState)(editorInstanceId, null);
|
|
1293
|
+
return () => (0, EditorToolbar_1.setEditorToolbarMentionState)(editorInstanceId, null);
|
|
1294
|
+
}
|
|
1295
|
+
(0, EditorToolbar_1.setEditorToolbarMentionState)(editorInstanceId, {
|
|
1296
|
+
trigger: activeMentionTrigger,
|
|
1297
|
+
suggestions: activeMentionSuggestions,
|
|
1298
|
+
theme: inlineToolbarMentionTheme,
|
|
1299
|
+
suggestionThemes: activeMentionSuggestionThemes,
|
|
1300
|
+
onSelectSuggestion: handleInlineMentionSuggestionPress,
|
|
1301
|
+
});
|
|
1302
|
+
return () => (0, EditorToolbar_1.setEditorToolbarMentionState)(editorInstanceId, null);
|
|
1303
|
+
}, [
|
|
1304
|
+
activeMentionSuggestions,
|
|
1305
|
+
activeMentionSuggestionThemes,
|
|
1306
|
+
activeMentionTrigger,
|
|
1307
|
+
editorInstanceId,
|
|
1308
|
+
handleInlineMentionSuggestionPress,
|
|
1309
|
+
inlineToolbarMentionTheme,
|
|
1310
|
+
mentionQueryEvent,
|
|
1311
|
+
shouldPublishStandaloneMentionSuggestions,
|
|
1312
|
+
]);
|
|
1218
1313
|
if (!isReady)
|
|
1219
1314
|
return null;
|
|
1220
1315
|
const isLinkActive = activeState.marks.link === true;
|
|
@@ -1258,19 +1353,13 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
1258
1353
|
};
|
|
1259
1354
|
const inlineToolbarMarginTop = theme?.toolbar?.marginTop ?? 8;
|
|
1260
1355
|
const inlineToolbarShowTopBorder = theme?.toolbar?.showTopBorder ?? false;
|
|
1261
|
-
const inlineToolbarMentionTheme = theme?.mentions ?? addons?.mentions?.theme;
|
|
1262
1356
|
const inlineToolbarContentTopBorderStyle = inlineToolbarShowTopBorder
|
|
1263
1357
|
? {
|
|
1264
1358
|
borderTopWidth: theme?.toolbar?.borderWidth ?? react_native_1.StyleSheet.hairlineWidth,
|
|
1265
1359
|
borderTopColor: theme?.toolbar?.borderColor ?? INLINE_TOOLBAR_BORDER_COLOR,
|
|
1266
1360
|
}
|
|
1267
1361
|
: null;
|
|
1268
|
-
const inlineMentionSuggestions = toolbarPlacement === 'inline'
|
|
1269
|
-
isFocused &&
|
|
1270
|
-
mentionQueryEvent != null &&
|
|
1271
|
-
addons?.mentions != null
|
|
1272
|
-
? filterMentionSuggestions(addons.mentions.suggestions ?? [], mentionQueryEvent.query, mentionQueryEvent.trigger || resolveMentionTrigger(addons))
|
|
1273
|
-
: [];
|
|
1362
|
+
const inlineMentionSuggestions = toolbarPlacement === 'inline' ? activeMentionSuggestions : [];
|
|
1274
1363
|
const shouldShowInlineMentionSuggestions = shouldRenderJsToolbar &&
|
|
1275
1364
|
toolbarPlacement === 'inline' &&
|
|
1276
1365
|
isFocused &&
|
|
@@ -1315,34 +1404,38 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
1315
1404
|
inlineToolbarContentTopBorderStyle,
|
|
1316
1405
|
], children: (0, jsx_runtime_1.jsx)(react_native_1.ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, contentContainerStyle: styles.inlineMentionSuggestionsContent, keyboardShouldPersistTaps: 'always', children: inlineMentionSuggestions.map((suggestion) => {
|
|
1317
1406
|
const label = resolveMentionSuggestionLabel(suggestion, mentionQueryEvent?.trigger ?? resolveMentionTrigger(addons));
|
|
1407
|
+
const suggestionTheme = activeMentionSuggestionThemes?.[suggestion.key] ??
|
|
1408
|
+
inlineToolbarMentionTheme;
|
|
1318
1409
|
return ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { testID: `native-editor-inline-mention-suggestion-${suggestion.key}`, onPress: () => handleInlineMentionSuggestionPress(suggestion), accessibilityRole: 'button', accessibilityLabel: label, style: ({ pressed }) => [
|
|
1319
1410
|
styles.inlineMentionSuggestion,
|
|
1320
1411
|
{
|
|
1321
1412
|
backgroundColor: pressed
|
|
1322
|
-
? (
|
|
1413
|
+
? (suggestionTheme?.optionHighlightedBackgroundColor ??
|
|
1323
1414
|
'rgba(0, 122, 255, 0.12)')
|
|
1324
|
-
: (
|
|
1415
|
+
: (suggestionTheme?.backgroundColor ??
|
|
1325
1416
|
'#F2F2F7'),
|
|
1326
|
-
borderColor:
|
|
1417
|
+
borderColor: suggestionTheme?.borderColor ??
|
|
1327
1418
|
'transparent',
|
|
1328
|
-
borderWidth:
|
|
1329
|
-
borderRadius:
|
|
1419
|
+
borderWidth: suggestionTheme?.borderWidth ?? 0,
|
|
1420
|
+
borderRadius: suggestionTheme?.borderRadius ?? 12,
|
|
1330
1421
|
},
|
|
1331
1422
|
], children: ({ pressed }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { numberOfLines: 1, style: [
|
|
1332
1423
|
styles.inlineMentionSuggestionTitle,
|
|
1333
1424
|
{
|
|
1425
|
+
fontWeight: suggestionTheme?.fontWeight ??
|
|
1426
|
+
'600',
|
|
1334
1427
|
color: pressed
|
|
1335
|
-
? (
|
|
1336
|
-
|
|
1428
|
+
? (suggestionTheme?.optionHighlightedTextColor ??
|
|
1429
|
+
suggestionTheme?.optionTextColor ??
|
|
1337
1430
|
'#000000')
|
|
1338
|
-
: (
|
|
1339
|
-
|
|
1431
|
+
: (suggestionTheme?.optionTextColor ??
|
|
1432
|
+
suggestionTheme?.textColor ??
|
|
1340
1433
|
'#000000'),
|
|
1341
1434
|
},
|
|
1342
1435
|
], children: label }), suggestion.subtitle ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { numberOfLines: 1, style: [
|
|
1343
1436
|
styles.inlineMentionSuggestionSubtitle,
|
|
1344
1437
|
{
|
|
1345
|
-
color:
|
|
1438
|
+
color: suggestionTheme?.optionSecondaryTextColor ??
|
|
1346
1439
|
'#8E8E93',
|
|
1347
1440
|
},
|
|
1348
1441
|
], children: suggestion.subtitle })) : null] })) }, suggestion.key));
|
|
@@ -1372,7 +1465,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
1372
1465
|
}), onToggleStrike: () => runAndApply(() => bridgeRef.current?.toggleMark('strike') ?? null, {
|
|
1373
1466
|
skipNativeApplyIfContentUnchanged: true,
|
|
1374
1467
|
}), 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) })) }));
|
|
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] }));
|
|
1468
|
+
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, autoCapitalize: autoCapitalize, autoCorrect: autoCorrect, keyboardType: keyboardType, 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] }));
|
|
1376
1469
|
});
|
|
1377
1470
|
const styles = react_native_1.StyleSheet.create({
|
|
1378
1471
|
container: {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { NativeRichTextEditor, type NativeRichTextEditorProps, type NativeRichTextEditorRef, type NativeRichTextEditorCaretRect, 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 NativeRichTextEditorAutoCapitalize, type NativeRichTextEditorKeyboardType, 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_x86_64-simulator</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>
|
|
17
18
|
</array>
|
|
18
19
|
<key>SupportedPlatform</key>
|
|
19
20
|
<string>ios</string>
|
|
21
|
+
<key>SupportedPlatformVariant</key>
|
|
22
|
+
<string>simulator</string>
|
|
20
23
|
</dict>
|
|
21
24
|
<dict>
|
|
22
25
|
<key>BinaryPath</key>
|
|
23
26
|
<string>libeditor_core.a</string>
|
|
24
27
|
<key>LibraryIdentifier</key>
|
|
25
|
-
<string>ios-
|
|
28
|
+
<string>ios-arm64</string>
|
|
26
29
|
<key>LibraryPath</key>
|
|
27
30
|
<string>libeditor_core.a</string>
|
|
28
31
|
<key>SupportedArchitectures</key>
|
|
29
32
|
<array>
|
|
30
33
|
<string>arm64</string>
|
|
31
|
-
<string>x86_64</string>
|
|
32
34
|
</array>
|
|
33
35
|
<key>SupportedPlatform</key>
|
|
34
36
|
<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
|
|
@@ -1802,6 +1802,18 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
|
|
|
1802
1802
|
focus()
|
|
1803
1803
|
}
|
|
1804
1804
|
|
|
1805
|
+
func setAutoCapitalize(_ autoCapitalize: String?) {
|
|
1806
|
+
richTextView.textView.setAutoCapitalize(autoCapitalize)
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
func setAutoCorrect(_ autoCorrect: Bool?) {
|
|
1810
|
+
richTextView.textView.setAutoCorrect(autoCorrect)
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
func setKeyboardType(_ keyboardType: String?) {
|
|
1814
|
+
richTextView.textView.setKeyboardType(keyboardType)
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1805
1817
|
func setShowToolbar(_ showToolbar: Bool) {
|
|
1806
1818
|
showsToolbar = showToolbar
|
|
1807
1819
|
updateAccessoryToolbarVisibility()
|
|
@@ -1913,7 +1925,7 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
|
|
|
1913
1925
|
// MARK: - Focus Commands
|
|
1914
1926
|
|
|
1915
1927
|
func focus() {
|
|
1916
|
-
richTextView.textView.becomeFirstResponder()
|
|
1928
|
+
_ = richTextView.textView.becomeFirstResponder()
|
|
1917
1929
|
}
|
|
1918
1930
|
|
|
1919
1931
|
func blur() {
|
|
@@ -1956,7 +1968,7 @@ class NativeEditorExpoView: ExpoView, EditorTextViewDelegate, UIGestureRecognize
|
|
|
1956
1968
|
@objc private func textViewDidEndEditing(_ notification: Notification) {
|
|
1957
1969
|
if shouldPreserveFocusAfterToolbarTouch() {
|
|
1958
1970
|
DispatchQueue.main.async { [weak self] in
|
|
1959
|
-
self?.richTextView.textView.becomeFirstResponder()
|
|
1971
|
+
_ = self?.richTextView.textView.becomeFirstResponder()
|
|
1960
1972
|
}
|
|
1961
1973
|
return
|
|
1962
1974
|
}
|
|
@@ -340,6 +340,15 @@ public class NativeEditorModule: Module {
|
|
|
340
340
|
Prop("autoFocus") { (view: NativeEditorExpoView, autoFocus: Bool) in
|
|
341
341
|
view.setAutoFocus(autoFocus)
|
|
342
342
|
}
|
|
343
|
+
Prop("autoCapitalize") { (view: NativeEditorExpoView, autoCapitalize: String?) in
|
|
344
|
+
view.setAutoCapitalize(autoCapitalize)
|
|
345
|
+
}
|
|
346
|
+
Prop("autoCorrect") { (view: NativeEditorExpoView, autoCorrect: Bool?) in
|
|
347
|
+
view.setAutoCorrect(autoCorrect)
|
|
348
|
+
}
|
|
349
|
+
Prop("keyboardType") { (view: NativeEditorExpoView, keyboardType: String?) in
|
|
350
|
+
view.setKeyboardType(keyboardType)
|
|
351
|
+
}
|
|
343
352
|
Prop("showToolbar") { (view: NativeEditorExpoView, showToolbar: Bool) in
|
|
344
353
|
view.setShowToolbar(showToolbar)
|
|
345
354
|
}
|
|
@@ -1031,9 +1031,9 @@ final class EditorTextView: UITextView, UITextViewDelegate, UIGestureRecognizerD
|
|
|
1031
1031
|
// Configure the text view as a Rust-controlled editor surface.
|
|
1032
1032
|
// UIKit smart-edit features mutate text storage outside our transaction
|
|
1033
1033
|
// pipeline and can race with stored-mark typing after toolbar actions.
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1034
|
+
setAutoCorrect(nil)
|
|
1035
|
+
setAutoCapitalize(nil)
|
|
1036
|
+
setKeyboardType(nil)
|
|
1037
1037
|
smartQuotesType = .no
|
|
1038
1038
|
smartDashesType = .no
|
|
1039
1039
|
smartInsertDeleteType = .no
|
|
@@ -1063,6 +1063,63 @@ final class EditorTextView: UITextView, UITextViewDelegate, UIGestureRecognizerD
|
|
|
1063
1063
|
refreshNativeSelectionChromeVisibility()
|
|
1064
1064
|
}
|
|
1065
1065
|
|
|
1066
|
+
func setAutoCapitalize(_ autoCapitalize: String?) {
|
|
1067
|
+
switch autoCapitalize {
|
|
1068
|
+
case "none":
|
|
1069
|
+
autocapitalizationType = .none
|
|
1070
|
+
case "words":
|
|
1071
|
+
autocapitalizationType = .words
|
|
1072
|
+
case "characters":
|
|
1073
|
+
autocapitalizationType = .allCharacters
|
|
1074
|
+
default:
|
|
1075
|
+
autocapitalizationType = .sentences
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
func setAutoCorrect(_ autoCorrect: Bool?) {
|
|
1080
|
+
let isEnabled = autoCorrect ?? false
|
|
1081
|
+
autocorrectionType = isEnabled ? .yes : .no
|
|
1082
|
+
spellCheckingType = isEnabled ? .default : .no
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
func setKeyboardType(_ keyboardType: String?) {
|
|
1086
|
+
self.keyboardType = Self.resolvedKeyboardType(from: keyboardType)
|
|
1087
|
+
if isFirstResponder {
|
|
1088
|
+
reloadInputViews()
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
private static func resolvedKeyboardType(from keyboardType: String?) -> UIKeyboardType {
|
|
1093
|
+
switch keyboardType {
|
|
1094
|
+
case "ascii-capable":
|
|
1095
|
+
return .asciiCapable
|
|
1096
|
+
case "numbers-and-punctuation":
|
|
1097
|
+
return .numbersAndPunctuation
|
|
1098
|
+
case "url":
|
|
1099
|
+
return .URL
|
|
1100
|
+
case "number-pad":
|
|
1101
|
+
return .numberPad
|
|
1102
|
+
case "phone-pad":
|
|
1103
|
+
return .phonePad
|
|
1104
|
+
case "name-phone-pad":
|
|
1105
|
+
return .namePhonePad
|
|
1106
|
+
case "email-address":
|
|
1107
|
+
return .emailAddress
|
|
1108
|
+
case "decimal-pad", "numeric":
|
|
1109
|
+
return .decimalPad
|
|
1110
|
+
case "twitter":
|
|
1111
|
+
return .twitter
|
|
1112
|
+
case "web-search":
|
|
1113
|
+
return .webSearch
|
|
1114
|
+
case "ascii-capable-number-pad":
|
|
1115
|
+
return .asciiCapableNumberPad
|
|
1116
|
+
case "visible-password":
|
|
1117
|
+
return .asciiCapable
|
|
1118
|
+
default:
|
|
1119
|
+
return .default
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1066
1123
|
override func didMoveToWindow() {
|
|
1067
1124
|
super.didMoveToWindow()
|
|
1068
1125
|
installImageSelectionTapDependencies()
|
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.15",
|
|
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
|