@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.
@@ -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
- implementation "net.java.dev.jna:jna:5.18.1@aar"
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 = EditorInfo.TYPE_CLASS_TEXT or
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
  }
@@ -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 {};
@@ -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, contentContainerStyle: styles.scrollContent, keyboardShouldPersistTaps: 'always', onScrollBeginDrag: () => setMenuState(null), children: renderedItems.map((item) => {
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
- ? (inlineToolbarMentionTheme?.optionHighlightedBackgroundColor ??
1413
+ ? (suggestionTheme?.optionHighlightedBackgroundColor ??
1323
1414
  'rgba(0, 122, 255, 0.12)')
1324
- : (inlineToolbarMentionTheme?.backgroundColor ??
1415
+ : (suggestionTheme?.backgroundColor ??
1325
1416
  '#F2F2F7'),
1326
- borderColor: inlineToolbarMentionTheme?.borderColor ??
1417
+ borderColor: suggestionTheme?.borderColor ??
1327
1418
  'transparent',
1328
- borderWidth: inlineToolbarMentionTheme?.borderWidth ?? 0,
1329
- borderRadius: inlineToolbarMentionTheme?.borderRadius ?? 12,
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
- ? (inlineToolbarMentionTheme?.optionHighlightedTextColor ??
1336
- inlineToolbarMentionTheme?.optionTextColor ??
1428
+ ? (suggestionTheme?.optionHighlightedTextColor ??
1429
+ suggestionTheme?.optionTextColor ??
1337
1430
  '#000000')
1338
- : (inlineToolbarMentionTheme?.optionTextColor ??
1339
- inlineToolbarMentionTheme?.textColor ??
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: inlineToolbarMentionTheme?.optionSecondaryTextColor ??
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-arm64</string>
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-arm64_x86_64-simulator</string>
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>
@@ -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
- autocorrectionType = .no
1035
- autocapitalizationType = .sentences
1036
- spellCheckingType = .no
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.13",
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",