@chaitrabhairappa/react-native-rich-text-editor 2.1.2 → 3.1.0

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/src/index.tsx CHANGED
@@ -1,14 +1,21 @@
1
- import React, { forwardRef, useImperativeHandle, useRef, useCallback, useState } from 'react';
2
- import { StyleSheet } from 'react-native';
1
+ import React from "react";
2
+ import {
3
+ findNodeHandle,
4
+ NativeModules,
5
+ Platform,
6
+ StyleSheet,
7
+ UIManager,
8
+ } from "react-native";
3
9
  import type {
4
10
  Block,
11
+ MediaAttachment,
5
12
  TextAlignment,
6
13
  ContentChangeEvent,
7
14
  SelectionChangeEvent,
8
15
  RichTextEditorProps,
9
16
  RichTextEditorRef,
10
- } from './types';
11
- import RichTextEditorViewNative from './RichTextEditorViewNativeComponent';
17
+ } from "./types";
18
+ import RichTextEditorViewNative from "./RichTextEditorViewNativeComponent";
12
19
 
13
20
  interface SizeChangeEvent {
14
21
  nativeEvent: {
@@ -35,97 +42,168 @@ export interface RichTextEditorPropsExtended extends RichTextEditorProps {
35
42
  onActiveStylesChange?: (styles: ActiveStylesState) => void;
36
43
  }
37
44
 
38
- const RichTextEditor = forwardRef<RichTextEditorRef, RichTextEditorPropsExtended>((props, ref) => {
39
- const nativeRef = useRef<React.ElementRef<typeof RichTextEditorViewNative>>(null);
40
- const [height, setHeight] = useState<number>(44);
45
+ const RichTextEditor = React.forwardRef<
46
+ RichTextEditorRef,
47
+ RichTextEditorPropsExtended
48
+ >((props, ref) => {
49
+ const nativeRef =
50
+ React.useRef<React.ElementRef<typeof RichTextEditorViewNative>>(null);
51
+ const [height, setHeight] = React.useState<number>(44);
41
52
 
42
- const handleSizeChange = useCallback((event: SizeChangeEvent) => {
53
+ const handleSizeChange = React.useCallback((event: SizeChangeEvent) => {
43
54
  const newHeight = event.nativeEvent?.height;
44
55
  if (newHeight && newHeight > 0) {
45
56
  setHeight(newHeight);
46
57
  }
47
58
  }, []);
48
59
 
49
- // These are placeholder methods for potential future UIManager.dispatchViewManagerCommand usage
50
- useImperativeHandle(ref, () => ({
51
- setContent: (_blocks: Block[]) => {
52
- /* Native toolbar handles this */
53
- },
54
- getText: async (): Promise<string> => '',
55
- getBlocks: async (): Promise<Block[]> => [],
56
- clear: () => {
57
- /* Native toolbar handles this */
58
- },
59
- focus: () => {
60
- /* Native toolbar handles this */
61
- },
62
- blur: () => {
63
- /* Native toolbar handles this */
64
- },
65
- toggleBold: () => {
66
- /* Native toolbar handles this */
67
- },
68
- toggleItalic: () => {
69
- /* Native toolbar handles this */
70
- },
71
- toggleUnderline: () => {
72
- /* Native toolbar handles this */
73
- },
74
- toggleStrikethrough: () => {
75
- /* Native toolbar handles this */
76
- },
77
- toggleCode: () => {
78
- /* Native toolbar handles this */
79
- },
80
- toggleHighlight: (_color?: string) => {
81
- /* Native toolbar handles this */
82
- },
83
- setHeading: () => {
84
- /* Native toolbar handles this */
85
- },
86
- setBulletList: () => {
87
- /* Native toolbar handles this */
88
- },
89
- setNumberedList: () => {
90
- /* Native toolbar handles this */
91
- },
92
- setQuote: () => {
93
- /* Native toolbar handles this */
94
- },
95
- setChecklist: () => {
96
- /* Native toolbar handles this */
97
- },
98
- setParagraph: () => {
99
- /* Native toolbar handles this */
100
- },
101
- insertLink: (_url: string, _text: string) => {
102
- /* Native toolbar handles this */
103
- },
104
- undo: () => {
105
- /* Native toolbar handles this */
106
- },
107
- redo: () => {
108
- /* Native toolbar handles this */
109
- },
110
- clearFormatting: () => {
111
- /* Native toolbar handles this */
112
- },
113
- indent: () => {
114
- /* Native toolbar handles this */
115
- },
116
- outdent: () => {
117
- /* Native toolbar handles this */
118
- },
119
- setAlignment: (_alignment: TextAlignment) => {
120
- /* Native toolbar handles this */
60
+ const dispatchAndroidCommand = React.useCallback(
61
+ (commandName: string, args: (string | number | boolean)[] = []) => {
62
+ if (Platform.OS !== "android") return;
63
+
64
+ const nativeTag = findNodeHandle(nativeRef.current);
65
+ if (nativeTag == null) return;
66
+
67
+ const commandConfig =
68
+ UIManager.getViewManagerConfig("RichTextEditorView")?.Commands;
69
+ const commandId = commandConfig?.[commandName];
70
+
71
+ if (commandId == null) return;
72
+
73
+ UIManager.dispatchViewManagerCommand(nativeTag, commandId, args);
121
74
  },
122
- toggleChecklistItem: () => {
123
- /* Native toolbar handles this */
75
+ [],
76
+ );
77
+
78
+ const dispatchInsertMediaAttachment = React.useCallback(
79
+ (uri: string) => {
80
+ if (typeof uri !== "string" || uri.trim().length === 0) return;
81
+
82
+ if (Platform.OS === "android") {
83
+ dispatchAndroidCommand("insertMediaAttachment", [uri]);
84
+ return;
85
+ }
86
+
87
+ if (Platform.OS === "ios") {
88
+ const nativeTag = findNodeHandle(nativeRef.current);
89
+ if (nativeTag == null) return;
90
+
91
+ const manager = NativeModules
92
+ ? (NativeModules as Record<string, unknown>)[
93
+ "RichTextEditorViewManager"
94
+ ]
95
+ : null;
96
+
97
+ if (
98
+ manager &&
99
+ typeof manager === "object" &&
100
+ typeof (manager as { insertMediaAttachment?: unknown })
101
+ .insertMediaAttachment === "function"
102
+ ) {
103
+ (
104
+ manager as {
105
+ insertMediaAttachment: (tag: number, value: string) => void;
106
+ }
107
+ ).insertMediaAttachment(nativeTag, uri);
108
+ }
109
+ }
124
110
  },
125
- }));
111
+ [dispatchAndroidCommand],
112
+ );
113
+
114
+ // These are placeholder methods for potential future UIManager.dispatchViewManagerCommand usage
115
+ React.useImperativeHandle(
116
+ ref,
117
+ () => ({
118
+ setContent: (_blocks: Block[]) => {
119
+ /* Native toolbar handles this */
120
+ },
121
+ getText: async (): Promise<string> => "",
122
+ getBlocks: async (): Promise<Block[]> => [],
123
+ clear: () => {
124
+ /* Native toolbar handles this */
125
+ },
126
+ focus: () => {
127
+ /* Native toolbar handles this */
128
+ },
129
+ blur: () => {
130
+ /* Native toolbar handles this */
131
+ },
132
+ toggleBold: () => {
133
+ /* Native toolbar handles this */
134
+ },
135
+ toggleItalic: () => {
136
+ /* Native toolbar handles this */
137
+ },
138
+ toggleUnderline: () => {
139
+ /* Native toolbar handles this */
140
+ },
141
+ toggleStrikethrough: () => {
142
+ /* Native toolbar handles this */
143
+ },
144
+ toggleCode: () => {
145
+ /* Native toolbar handles this */
146
+ },
147
+ toggleHighlight: (_color?: string) => {
148
+ /* Native toolbar handles this */
149
+ },
150
+ setHeading: () => {
151
+ /* Native toolbar handles this */
152
+ },
153
+ setBulletList: () => {
154
+ /* Native toolbar handles this */
155
+ },
156
+ setNumberedList: () => {
157
+ /* Native toolbar handles this */
158
+ },
159
+ setQuote: () => {
160
+ /* Native toolbar handles this */
161
+ },
162
+ setChecklist: () => {
163
+ /* Native toolbar handles this */
164
+ },
165
+ setParagraph: () => {
166
+ /* Native toolbar handles this */
167
+ },
168
+ insertLink: (_url: string, _text: string) => {
169
+ /* Native toolbar handles this */
170
+ },
171
+ insertMediaAttachment: (mediaAttachment: MediaAttachment) => {
172
+ if (
173
+ mediaAttachment &&
174
+ typeof mediaAttachment === "object" &&
175
+ typeof mediaAttachment.uri === "string"
176
+ ) {
177
+ dispatchInsertMediaAttachment(mediaAttachment.uri);
178
+ }
179
+ },
180
+ undo: () => {
181
+ /* Native toolbar handles this */
182
+ },
183
+ redo: () => {
184
+ /* Native toolbar handles this */
185
+ },
186
+ clearFormatting: () => {
187
+ /* Native toolbar handles this */
188
+ },
189
+ indent: () => {
190
+ /* Native toolbar handles this */
191
+ },
192
+ outdent: () => {
193
+ /* Native toolbar handles this */
194
+ },
195
+ setAlignment: (_alignment: TextAlignment) => {
196
+ /* Native toolbar handles this */
197
+ },
198
+ toggleChecklistItem: () => {
199
+ /* Native toolbar handles this */
200
+ },
201
+ }),
202
+ [dispatchInsertMediaAttachment],
203
+ );
126
204
 
127
205
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
128
- const handleContentChange = useCallback(
206
+ const handleContentChange = React.useCallback(
129
207
  (event: any) => {
130
208
  // Parse blocksJson string (codegen doesn't support nested ReadonlyArray<Object>)
131
209
  let blocks: Block[] = [];
@@ -154,7 +232,7 @@ const RichTextEditor = forwardRef<RichTextEditorRef, RichTextEditorPropsExtended
154
232
  );
155
233
 
156
234
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
157
- const handleSelectionChange = useCallback(
235
+ const handleSelectionChange = React.useCallback(
158
236
  (event: any) => {
159
237
  const selectionEvent: SelectionChangeEvent = {
160
238
  nativeEvent: {
@@ -167,15 +245,15 @@ const RichTextEditor = forwardRef<RichTextEditorRef, RichTextEditorPropsExtended
167
245
  [props.onSelectionChange],
168
246
  );
169
247
 
170
- const handleFocus = useCallback(() => {
248
+ const handleFocus = React.useCallback(() => {
171
249
  props.onFocus?.();
172
250
  }, [props.onFocus]);
173
251
 
174
- const handleBlur = useCallback(() => {
252
+ const handleBlur = React.useCallback(() => {
175
253
  props.onBlur?.();
176
254
  }, [props.onBlur]);
177
255
 
178
- const handleActiveStylesChange = useCallback(
256
+ const handleActiveStylesChange = React.useCallback(
179
257
  (event: ActiveStylesChangeEvent) => {
180
258
  props.onActiveStylesChange?.(event.nativeEvent);
181
259
  },
@@ -189,13 +267,15 @@ const RichTextEditor = forwardRef<RichTextEditorRef, RichTextEditorPropsExtended
189
267
  ref={nativeRef}
190
268
  style={combinedStyle}
191
269
  placeholder={props.placeholder}
192
- initialContentJson={props.initialContent ? JSON.stringify(props.initialContent) : undefined}
270
+ initialContentJson={
271
+ props.initialContent ? JSON.stringify(props.initialContent) : undefined
272
+ }
193
273
  editable={props.readOnly !== undefined ? !props.readOnly : true}
194
274
  maxHeight={props.maxHeight}
195
275
  numberOfLines={props.numberOfLines}
196
- showToolbar={props.readOnly ? false : props.showToolbar ?? true}
276
+ showToolbar={props.readOnly ? false : (props.showToolbar ?? true)}
197
277
  toolbarOptions={props.toolbarOptions}
198
- variant={props.variant ?? 'outlined'}
278
+ variant={props.variant ?? "outlined"}
199
279
  onContentChange={handleContentChange}
200
280
  onSelectionChange={handleSelectionChange}
201
281
  onEditorFocus={handleFocus}
@@ -206,14 +286,15 @@ const RichTextEditor = forwardRef<RichTextEditorRef, RichTextEditorPropsExtended
206
286
  );
207
287
  });
208
288
 
209
- RichTextEditor.displayName = 'RichTextEditor';
289
+ RichTextEditor.displayName = "RichTextEditor";
210
290
 
211
291
  export default RichTextEditor;
212
- export { DEFAULT_TOOLBAR_OPTIONS } from './types';
292
+ export { DEFAULT_TOOLBAR_OPTIONS } from "./types";
213
293
  export type {
214
294
  Block,
215
295
  BlockType,
216
296
  StyleRange,
297
+ MediaAttachment,
217
298
  TextAlignment,
218
299
  EditorVariant,
219
300
  ContentChangeEvent,
@@ -223,4 +304,4 @@ export type {
223
304
  ToolbarOption,
224
305
  ContentDelta,
225
306
  DeltaType,
226
- } from './types';
307
+ } from "./types";
package/src/types.ts CHANGED
@@ -1,16 +1,38 @@
1
- import type { StyleProp, ViewStyle } from 'react-native';
1
+ import type { StyleProp, ViewStyle } from "react-native";
2
2
 
3
3
  export interface StyleRange {
4
- style: 'bold' | 'italic' | 'underline' | 'strikethrough' | 'link' | 'code' | 'highlight';
4
+ style:
5
+ | "bold"
6
+ | "italic"
7
+ | "underline"
8
+ | "strikethrough"
9
+ | "link"
10
+ | "code"
11
+ | "highlight";
5
12
  start: number;
6
13
  end: number;
7
14
  url?: string;
8
15
  highlightColor?: string;
9
16
  }
10
17
 
11
- export type BlockType = 'paragraph' | 'bullet' | 'numbered' | 'heading' | 'quote' | 'checklist';
12
- export type TextAlignment = 'left' | 'center' | 'right';
13
- export type EditorVariant = 'outlined' | 'flat' | 'plain';
18
+ export type BlockType =
19
+ | "paragraph"
20
+ | "bullet"
21
+ | "numbered"
22
+ | "heading"
23
+ | "quote"
24
+ | "checklist"
25
+ | "mediaAttachment";
26
+ export type TextAlignment = "left" | "center" | "right";
27
+ export type EditorVariant = "outlined" | "flat" | "plain";
28
+
29
+ export interface MediaAttachment {
30
+ kind: "image";
31
+ uri: string;
32
+ width?: number;
33
+ height?: number;
34
+ alt?: string;
35
+ }
14
36
 
15
37
  export interface Block {
16
38
  type: BlockType;
@@ -19,6 +41,7 @@ export interface Block {
19
41
  alignment?: TextAlignment;
20
42
  checked?: boolean;
21
43
  indentLevel?: number;
44
+ mediaAttachment?: MediaAttachment;
22
45
  }
23
46
 
24
47
  export interface ContentChangeEvent {
@@ -29,7 +52,7 @@ export interface ContentChangeEvent {
29
52
  };
30
53
  }
31
54
 
32
- export type DeltaType = 'insert' | 'delete' | 'format' | 'replace';
55
+ export type DeltaType = "insert" | "delete" | "format" | "replace";
33
56
 
34
57
  export interface ContentDelta {
35
58
  type: DeltaType;
@@ -48,48 +71,50 @@ export interface SelectionChangeEvent {
48
71
  }
49
72
 
50
73
  export type ToolbarOption =
51
- | 'bold'
52
- | 'italic'
53
- | 'strikethrough'
54
- | 'underline'
55
- | 'code'
56
- | 'highlight'
57
- | 'heading'
58
- | 'bullet'
59
- | 'numbered'
60
- | 'quote'
61
- | 'checklist'
62
- | 'link'
63
- | 'undo'
64
- | 'redo'
65
- | 'clearFormatting'
66
- | 'indent'
67
- | 'outdent'
68
- | 'alignLeft'
69
- | 'alignCenter'
70
- | 'alignRight';
74
+ | "bold"
75
+ | "italic"
76
+ | "strikethrough"
77
+ | "underline"
78
+ | "code"
79
+ | "highlight"
80
+ | "heading"
81
+ | "bullet"
82
+ | "numbered"
83
+ | "quote"
84
+ | "checklist"
85
+ | "mediaAttachment"
86
+ | "link"
87
+ | "undo"
88
+ | "redo"
89
+ | "clearFormatting"
90
+ | "indent"
91
+ | "outdent"
92
+ | "alignLeft"
93
+ | "alignCenter"
94
+ | "alignRight";
71
95
 
72
96
  export const DEFAULT_TOOLBAR_OPTIONS: ToolbarOption[] = [
73
- 'bold',
74
- 'italic',
75
- 'underline',
76
- 'strikethrough',
77
- 'code',
78
- 'highlight',
79
- 'heading',
80
- 'bullet',
81
- 'numbered',
82
- 'quote',
83
- 'checklist',
84
- 'link',
85
- 'undo',
86
- 'redo',
87
- 'clearFormatting',
88
- 'indent',
89
- 'outdent',
90
- 'alignLeft',
91
- 'alignCenter',
92
- 'alignRight',
97
+ "bold",
98
+ "italic",
99
+ "underline",
100
+ "strikethrough",
101
+ "code",
102
+ "highlight",
103
+ "heading",
104
+ "bullet",
105
+ "numbered",
106
+ "quote",
107
+ "checklist",
108
+ "mediaAttachment",
109
+ "link",
110
+ "undo",
111
+ "redo",
112
+ "clearFormatting",
113
+ "indent",
114
+ "outdent",
115
+ "alignLeft",
116
+ "alignCenter",
117
+ "alignRight",
93
118
  ];
94
119
 
95
120
  export interface RichTextEditorProps {
@@ -128,6 +153,7 @@ export interface RichTextEditorRef {
128
153
  setChecklist: () => void;
129
154
  setParagraph: () => void;
130
155
  insertLink: (url: string, text: string) => void;
156
+ insertMediaAttachment: (mediaAttachment: MediaAttachment) => void;
131
157
  undo: () => void;
132
158
  redo: () => void;
133
159
  clearFormatting: () => void;