@chaitrabhairappa/react-native-rich-text-editor 2.0.0 → 2.0.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chaitrabhairappa/react-native-rich-text-editor",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "A high-performance native rich text editor for React Native (New Architecture / Fabric only)",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -8,6 +8,7 @@
8
8
  "react-native": "lib/module/index.js",
9
9
  "source": "lib/module/index.js",
10
10
  "files": [
11
+ "src",
11
12
  "lib",
12
13
  "assets",
13
14
  "android",
@@ -0,0 +1,82 @@
1
+ import type { ViewProps, HostComponent } from 'react-native';
2
+ import { requireNativeComponent, UIManager } from 'react-native';
3
+
4
+ type Double = number;
5
+ type Int32 = number;
6
+ type DirectEventHandler<T> = (event: { nativeEvent: T }) => void;
7
+
8
+ export type StyleRange = Readonly<{
9
+ style: string;
10
+ start: Int32;
11
+ end: Int32;
12
+ url?: string;
13
+ highlightColor?: string;
14
+ }>;
15
+
16
+ export type Block = Readonly<{
17
+ type: string;
18
+ text: string;
19
+ styles: ReadonlyArray<StyleRange>;
20
+ alignment?: string;
21
+ checked?: boolean;
22
+ indentLevel?: Int32;
23
+ }>;
24
+
25
+ type ContentChangeEventData = Readonly<{
26
+ text: string;
27
+ blocksJson: string;
28
+ }>;
29
+
30
+ type SelectionChangeEventData = Readonly<{
31
+ start: Int32;
32
+ end: Int32;
33
+ }>;
34
+
35
+ type SizeChangeEventData = Readonly<{
36
+ height: Double;
37
+ }>;
38
+
39
+ type ActiveStylesEventData = Readonly<{
40
+ bold: boolean;
41
+ italic: boolean;
42
+ underline: boolean;
43
+ strikethrough: boolean;
44
+ code: boolean;
45
+ highlight: boolean;
46
+ blockType: string;
47
+ alignment: string;
48
+ }>;
49
+
50
+ export interface NativeProps extends ViewProps {
51
+ placeholder?: string;
52
+ editable?: boolean;
53
+ maxHeight?: Double;
54
+ showToolbar?: boolean;
55
+ toolbarOptions?: ReadonlyArray<string>;
56
+ variant?: string;
57
+ initialContentJson?: string;
58
+
59
+ onContentChange?: DirectEventHandler<ContentChangeEventData>;
60
+ onSelectionChange?: DirectEventHandler<SelectionChangeEventData>;
61
+ onEditorFocus?: DirectEventHandler<Readonly<{}>>;
62
+ onEditorBlur?: DirectEventHandler<Readonly<{}>>;
63
+ onSizeChange?: DirectEventHandler<SizeChangeEventData>;
64
+ onActiveStylesChange?: DirectEventHandler<ActiveStylesEventData>;
65
+ }
66
+
67
+ const COMPONENT_NAME = 'RichTextEditorView';
68
+
69
+ // Check if native component exists
70
+ const hasNativeComponent = UIManager.getViewManagerConfig(COMPONENT_NAME) != null;
71
+
72
+ let RichTextEditorViewNative: HostComponent<NativeProps>;
73
+
74
+ if (hasNativeComponent) {
75
+ RichTextEditorViewNative = requireNativeComponent<NativeProps>(
76
+ COMPONENT_NAME,
77
+ ) as HostComponent<NativeProps>;
78
+ } else {
79
+ RichTextEditorViewNative = (() => null) as any;
80
+ }
81
+
82
+ export default RichTextEditorViewNative;
package/src/index.tsx ADDED
@@ -0,0 +1,225 @@
1
+ import React, { forwardRef, useImperativeHandle, useRef, useCallback, useState } from 'react';
2
+ import { StyleSheet } from 'react-native';
3
+ import type {
4
+ Block,
5
+ TextAlignment,
6
+ ContentChangeEvent,
7
+ SelectionChangeEvent,
8
+ RichTextEditorProps,
9
+ RichTextEditorRef,
10
+ } from './types';
11
+ import RichTextEditorViewNative from './RichTextEditorViewNativeComponent';
12
+
13
+ interface SizeChangeEvent {
14
+ nativeEvent: {
15
+ height: number;
16
+ };
17
+ }
18
+
19
+ export interface ActiveStylesState {
20
+ bold: boolean;
21
+ italic: boolean;
22
+ underline: boolean;
23
+ strikethrough: boolean;
24
+ code: boolean;
25
+ highlight: boolean;
26
+ blockType: string;
27
+ alignment: string;
28
+ }
29
+
30
+ interface ActiveStylesChangeEvent {
31
+ nativeEvent: ActiveStylesState;
32
+ }
33
+
34
+ export interface RichTextEditorPropsExtended extends RichTextEditorProps {
35
+ onActiveStylesChange?: (styles: ActiveStylesState) => void;
36
+ }
37
+
38
+ const RichTextEditor = forwardRef<RichTextEditorRef, RichTextEditorPropsExtended>((props, ref) => {
39
+ const nativeRef = useRef<React.ElementRef<typeof RichTextEditorViewNative>>(null);
40
+ const [height, setHeight] = useState<number>(44);
41
+
42
+ const handleSizeChange = useCallback((event: SizeChangeEvent) => {
43
+ const newHeight = event.nativeEvent?.height;
44
+ if (newHeight && newHeight > 0) {
45
+ setHeight(newHeight);
46
+ }
47
+ }, []);
48
+
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 */
121
+ },
122
+ toggleChecklistItem: () => {
123
+ /* Native toolbar handles this */
124
+ },
125
+ }));
126
+
127
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
128
+ const handleContentChange = useCallback(
129
+ (event: any) => {
130
+ // Parse blocksJson string (codegen doesn't support nested ReadonlyArray<Object>)
131
+ let blocks: Block[] = [];
132
+ try {
133
+ if (event.nativeEvent.blocksJson) {
134
+ blocks = JSON.parse(event.nativeEvent.blocksJson);
135
+ } else if (event.nativeEvent.blocks) {
136
+ // Fallback for backward compatibility
137
+ blocks = [...event.nativeEvent.blocks];
138
+ }
139
+ } catch {
140
+ blocks = [];
141
+ }
142
+
143
+ // Convert native event to our API format
144
+ const contentEvent: ContentChangeEvent = {
145
+ nativeEvent: {
146
+ text: event.nativeEvent.text,
147
+ blocks,
148
+ delta: event.nativeEvent.delta,
149
+ },
150
+ };
151
+ props.onContentChange?.(contentEvent);
152
+ },
153
+ [props.onContentChange],
154
+ );
155
+
156
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
157
+ const handleSelectionChange = useCallback(
158
+ (event: any) => {
159
+ const selectionEvent: SelectionChangeEvent = {
160
+ nativeEvent: {
161
+ start: event.nativeEvent.start,
162
+ end: event.nativeEvent.end,
163
+ },
164
+ };
165
+ props.onSelectionChange?.(selectionEvent);
166
+ },
167
+ [props.onSelectionChange],
168
+ );
169
+
170
+ const handleFocus = useCallback(() => {
171
+ props.onFocus?.();
172
+ }, [props.onFocus]);
173
+
174
+ const handleBlur = useCallback(() => {
175
+ props.onBlur?.();
176
+ }, [props.onBlur]);
177
+
178
+ const handleActiveStylesChange = useCallback(
179
+ (event: ActiveStylesChangeEvent) => {
180
+ props.onActiveStylesChange?.(event.nativeEvent);
181
+ },
182
+ [props.onActiveStylesChange],
183
+ );
184
+
185
+ const combinedStyle = StyleSheet.flatten([props.style, { height }]);
186
+
187
+ return (
188
+ <RichTextEditorViewNative
189
+ ref={nativeRef}
190
+ style={combinedStyle}
191
+ placeholder={props.placeholder}
192
+ initialContentJson={props.initialContent ? JSON.stringify(props.initialContent) : undefined}
193
+ editable={props.readOnly !== undefined ? !props.readOnly : true}
194
+ maxHeight={props.maxHeight}
195
+ showToolbar={props.readOnly ? false : props.showToolbar ?? true}
196
+ toolbarOptions={props.toolbarOptions}
197
+ variant={props.variant ?? 'outlined'}
198
+ onContentChange={handleContentChange}
199
+ onSelectionChange={handleSelectionChange}
200
+ onEditorFocus={handleFocus}
201
+ onEditorBlur={handleBlur}
202
+ onSizeChange={handleSizeChange}
203
+ onActiveStylesChange={handleActiveStylesChange}
204
+ />
205
+ );
206
+ });
207
+
208
+ RichTextEditor.displayName = 'RichTextEditor';
209
+
210
+ export default RichTextEditor;
211
+ export { DEFAULT_TOOLBAR_OPTIONS } from './types';
212
+ export type {
213
+ Block,
214
+ BlockType,
215
+ StyleRange,
216
+ TextAlignment,
217
+ EditorVariant,
218
+ ContentChangeEvent,
219
+ SelectionChangeEvent,
220
+ RichTextEditorRef,
221
+ RichTextEditorProps,
222
+ ToolbarOption,
223
+ ContentDelta,
224
+ DeltaType,
225
+ } from './types';
package/src/types.ts ADDED
@@ -0,0 +1,137 @@
1
+ import type { StyleProp, ViewStyle } from 'react-native';
2
+
3
+ export interface StyleRange {
4
+ style: 'bold' | 'italic' | 'underline' | 'strikethrough' | 'link' | 'code' | 'highlight';
5
+ start: number;
6
+ end: number;
7
+ url?: string;
8
+ highlightColor?: string;
9
+ }
10
+
11
+ export type BlockType = 'paragraph' | 'bullet' | 'numbered' | 'heading' | 'quote' | 'checklist';
12
+ export type TextAlignment = 'left' | 'center' | 'right';
13
+ export type EditorVariant = 'outlined' | 'flat';
14
+
15
+ export interface Block {
16
+ type: BlockType;
17
+ text: string;
18
+ styles: StyleRange[];
19
+ alignment?: TextAlignment;
20
+ checked?: boolean;
21
+ indentLevel?: number;
22
+ }
23
+
24
+ export interface ContentChangeEvent {
25
+ nativeEvent: {
26
+ text: string;
27
+ blocks: Block[];
28
+ delta?: ContentDelta;
29
+ };
30
+ }
31
+
32
+ export type DeltaType = 'insert' | 'delete' | 'format' | 'replace';
33
+
34
+ export interface ContentDelta {
35
+ type: DeltaType;
36
+ position: number;
37
+ length?: number;
38
+ text?: string;
39
+ blockIndex?: number;
40
+ style?: string;
41
+ }
42
+
43
+ export interface SelectionChangeEvent {
44
+ nativeEvent: {
45
+ start: number;
46
+ end: number;
47
+ };
48
+ }
49
+
50
+ 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';
71
+
72
+ 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',
93
+ ];
94
+
95
+ export interface RichTextEditorProps {
96
+ style?: StyleProp<ViewStyle>;
97
+ placeholder?: string;
98
+ initialContent?: Block[];
99
+ readOnly?: boolean;
100
+ maxHeight?: number;
101
+ showToolbar?: boolean;
102
+ toolbarOptions?: ToolbarOption[];
103
+ variant?: EditorVariant;
104
+ onContentChange?: (event: ContentChangeEvent) => void;
105
+ onSelectionChange?: (event: SelectionChangeEvent) => void;
106
+ onFocus?: () => void;
107
+ onBlur?: () => void;
108
+ }
109
+
110
+ export interface RichTextEditorRef {
111
+ setContent: (blocks: Block[]) => void;
112
+ getText: () => Promise<string>;
113
+ getBlocks: () => Promise<Block[]>;
114
+ clear: () => void;
115
+ focus: () => void;
116
+ blur: () => void;
117
+ toggleBold: () => void;
118
+ toggleItalic: () => void;
119
+ toggleUnderline: () => void;
120
+ toggleStrikethrough: () => void;
121
+ toggleCode: () => void;
122
+ toggleHighlight: (color?: string) => void;
123
+ setHeading: () => void;
124
+ setBulletList: () => void;
125
+ setNumberedList: () => void;
126
+ setQuote: () => void;
127
+ setChecklist: () => void;
128
+ setParagraph: () => void;
129
+ insertLink: (url: string, text: string) => void;
130
+ undo: () => void;
131
+ redo: () => void;
132
+ clearFormatting: () => void;
133
+ indent: () => void;
134
+ outdent: () => void;
135
+ setAlignment: (alignment: TextAlignment) => void;
136
+ toggleChecklistItem: () => void;
137
+ }