rn-rich-text-editor 1.3.2 → 1.3.4

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/README.md CHANGED
@@ -1,39 +1,38 @@
1
1
  # React Native Rich Text Editor
2
2
 
3
- A powerful, feature-rich text editor component for React Native built with WebView. Perfect for creating rich text editing experiences in your React Native applications.
3
+ [![npm version](https://img.shields.io/npm/v/rn-rich-text-editor.svg)](https://www.npmjs.com/package/rn-rich-text-editor)
4
+ [![npm downloads](https://img.shields.io/npm/dm/rn-rich-text-editor.svg)](https://www.npmjs.com/package/rn-rich-text-editor)
5
+ [![license](https://img.shields.io/npm/l/rn-rich-text-editor.svg)](https://www.npmjs.com/package/rn-rich-text-editor)
4
6
 
5
- ## Features
7
+ **A powerful and customizable rich text editor built natively for React Native. Everything included, nothing missing.**
6
8
 
7
- -**Rich Text Formatting**: Bold, italic, underline, strikethrough, and more
8
- - 📝 **Text Styling**: Font size, font family, text color, highlight color, line height
9
- - 📋 **Lists**: Bullet lists, ordered lists, and checkbox lists
10
- - 🔗 **Media Support**: Insert images and videos
11
- - 📐 **Alignment**: Left, center, right, and justify alignment
12
- - 📑 **Headings**: Support for H1-H6 headings
13
- - 💻 **Code Blocks**: Insert code blocks and inline code
14
- - 🔄 **Undo/Redo**: Full undo and redo support
15
- - ⌨️ **Keyboard Management**: Built-in keyboard handling for iOS and Android
16
- - 🎨 **Customizable**: Fully customizable toolbar and editor styles
17
- - 📱 **Cross-Platform**: Works on both iOS and Android
18
- - ♿ **Accessible**: Built with accessibility in mind
9
+ ##Features
19
10
 
20
- ## Installation
11
+ - **Rich Formatting**: Bold, italic, underline, strikethrough, subscript, superscript
12
+ - **Text Styling**: Font size, family, colors, highlights, line height
13
+ - **Lists & Structure**: Bullet lists, numbered lists, checkbox lists, headings (H1-H6)
14
+ - **Media**: Insert images and videos with custom styling
15
+ - **Alignment**: Left, center, right, justify with cycle button
16
+ - **Code & Blocks**: Code blocks, blockquotes, horizontal rules
17
+ - **Smart Paste**: Automatically preserves HTML formatting from web pages, Word docs, and more
18
+ - **Read-Only Mode**: Display formatted content without editing
19
+ - **Keyboard Management**: Built-in iOS/Android keyboard handling
20
+ - **Fully Customizable**: Toolbar, icons, styles, and behavior
21
+
22
+ ## 📦 Installation
21
23
 
22
24
  ```bash
23
25
  npm install rn-rich-text-editor
24
26
  ```
25
27
 
26
- ### Peer Dependencies
27
-
28
- Make sure you have these installed:
29
-
28
+ **Peer dependencies:**
30
29
  ```bash
31
30
  npm install react react-native react-native-webview
32
31
  ```
33
32
 
34
- If your app already has `react-native-svg-transformer` (e.g. in devDependencies), toolbar SVG icons (e.g. italic) work with no extra setup. Install `react-native-svg` for runtime.
33
+ > **Note**: If you use `react-native-svg-transformer`, SVG icons work automatically. Install `react-native-svg` for runtime.
35
34
 
36
- ## Quick Start
35
+ ## 🚀 Quick Start
37
36
 
38
37
  ```tsx
39
38
  import React, { useRef } from 'react';
@@ -48,7 +47,7 @@ function App() {
48
47
  <Editor
49
48
  ref={editorRef}
50
49
  placeholder="Start typing..."
51
- onChange={(html) => console.log('Content changed:', html)}
50
+ onChange={(html) => console.log('Content:', html)}
52
51
  />
53
52
  <Toolbar editor={editorRef} />
54
53
  </View>
@@ -56,602 +55,282 @@ function App() {
56
55
  }
57
56
  ```
58
57
 
59
- ## Components
60
-
61
- ### Editor
62
-
63
- The main editor component that provides the rich text editing interface.
58
+ ## 📚 API Reference
64
59
 
65
- #### Props
60
+ ### Editor Props
66
61
 
67
62
  | Prop | Type | Default | Description |
68
63
  |------|------|---------|-------------|
69
- | `ref` | `RefObject<EditorRef>` | - | Reference to access editor methods |
70
- | `contentInset` | `object` | `{}` | Content inset for the editor |
71
- | `style` | `object` | `{}` | Custom styles for the editor container |
72
- | `placeholder` | `string` | `''` | Placeholder text shown when editor is empty |
73
- | `initialContentHTML` | `string` | `''` | Initial HTML content to load |
74
- | `initialFocus` | `boolean` | `false` | Whether to focus the editor on mount |
75
- | `disabled` | `boolean` | `false` | Disable the editor |
76
- | `readOnly` | `boolean` | `false` | Make the editor read-only |
77
- | `useContainer` | `boolean` | `true` | Use a container wrapper with height |
78
- | `pasteAsPlainText` | `boolean` | `false` | Paste content as plain text |
79
- | `autoCapitalize` | `string` | `'off'` | Auto-capitalization setting |
80
- | `defaultParagraphSeparator` | `string` | `'div'` | Default paragraph separator (`'div'` or `'p'`) |
81
- | `editorInitializedCallback` | `() => void` | `() => {}` | Callback when editor is initialized |
82
- | `initialHeight` | `number` | `0` | Initial height of the editor (0 = auto) |
83
- | `dataDetectorTypes` | `string[]` | `['none']` | Data detector types for links, phone numbers, etc. |
84
- | `editorStyle` | `object` | - | Custom editor styles (see EditorStyle below) |
85
- | `html` | `string \| { html: string }` | - | HTML content (alternative to `initialContentHTML`) |
86
- | `onFocus` | `() => void` | - | Callback when editor gains focus |
87
- | `onBlur` | `() => void` | - | Callback when editor loses focus |
88
- | `onChange` | `(html: string) => void` | - | Callback when content changes |
89
- | `onPaste` | `(data: unknown) => void` | - | Callback when content is pasted |
90
- | `onKeyUp` | `(data: unknown) => void` | - | Callback on key up event |
91
- | `onKeyDown` | `(data: unknown) => void` | - | Callback on key down event |
92
- | `onInput` | `(data: unknown) => void` | - | Callback on input event |
93
- | `onMessage` | `(message: unknown) => void` | - | Callback for editor messages |
94
- | `onCursorPosition` | `(offsetY: number) => void` | - | Callback when cursor position changes |
95
- | `onLink` | `(data: unknown) => void` | - | Callback when a link is clicked |
96
- | `onHeightChange` | `(height: number) => void` | - | Callback when editor height changes |
97
- | `errorMessage` | `string` | - | Error message to display |
98
-
99
- #### EditorStyle Object
100
-
64
+ | `ref` | `RefObject<EditorRef>` | - | Editor reference for methods |
65
+ | `placeholder` | `string` | `''` | Placeholder text |
66
+ | `initialContentHTML` | `string` | `''` | Initial HTML content |
67
+ | `readOnly` | `boolean` | `false` | Display-only mode |
68
+ | `disabled` | `boolean` | `false` | Disable editing |
69
+ | `pasteAsPlainText` | `boolean` | `false` | Disable HTML paste |
70
+ | `initialFocus` | `boolean` | `false` | Auto-focus on mount |
71
+ | `editorStyle` | `object` | - | Style configuration (see below) |
72
+ | `onChange` | `(html: string) => void` | - | Content change callback |
73
+ | `onFocus` / `onBlur` | `() => void` | - | Focus callbacks |
74
+ | `onPaste` | `(data: unknown) => void` | - | Paste event callback |
75
+ | `onLink` | `(data: unknown) => void` | - | Link click callback |
76
+ | `onHeightChange` | `(height: number) => void` | - | Height change callback |
77
+ | `errorMessage` | `string` | - | Error message (shows red border) |
78
+
79
+ **EditorStyle:**
101
80
  ```typescript
102
81
  {
103
- backgroundColor?: string; // Editor background color
82
+ backgroundColor?: string; // Editor background
104
83
  color?: string; // Text color
105
- placeholderColor?: string; // Placeholder text color
106
- caretColor?: string; // Cursor/caret color
107
- initialCSSText?: string; // Initial CSS styles
108
- cssText?: string; // Additional CSS styles
109
- contentCSSText?: string; // Content area CSS styles
84
+ placeholderColor?: string; // Placeholder color
85
+ caretColor?: string; // Cursor color
86
+ contentCSSText?: string; // Content CSS
87
+ cssText?: string; // Additional CSS
88
+ initialCSSText?: string; // Initial CSS
110
89
  }
111
90
  ```
112
91
 
113
- #### EditorRef Methods
114
-
115
- Access these methods through the editor ref:
116
-
117
- | Method | Parameters | Description |
118
- |--------|------------|-------------|
119
- | `setContentHTML` | `(html: string)` | Set the editor content HTML |
120
- | `getContentHtml` | `()` | Get the current content HTML (returns Promise) |
121
- | `setPlaceholder` | `(placeholder: string)` | Set the placeholder text |
122
- | `setContentStyle` | `(styles: object)` | Update editor styles dynamically |
123
- | `setDisable` | `(disabled: boolean)` | Enable/disable the editor |
124
- | `focusContentEditor` | `()` | Focus the editor |
125
- | `blurContentEditor` | `()` | Blur the editor |
126
- | `showAndroidKeyboard` | `()` | Show keyboard on Android |
127
- | `dismissKeyboard` | `()` | Dismiss the keyboard |
128
- | `insertText` | `(text: string)` | Insert plain text at cursor |
129
- | `insertHTML` | `(html: string)` | Insert HTML at cursor |
130
- | `insertImage` | `(attributes: object, style?: object)` | Insert an image |
131
- | `insertVideo` | `(attributes: object, style?: object)` | Insert a video |
132
- | `insertLink` | `(title: string, url: string)` | Insert a link |
133
- | `setFontSize` | `(size: unknown)` | Set font size |
134
- | `setFontName` | `(name: string)` | Set font family |
135
- | `setForeColor` | `(color: string)` | Set text color |
136
- | `setHiliteColor` | `(color: string)` | Set highlight/background color |
137
- | `setLineHeight` | `(value: number \| string)` | Set line height |
138
- | `command` | `(command: unknown)` | Execute a command |
139
- | `commandDOM` | `(command: unknown)` | Execute a DOM command |
140
- | `injectJavascript` | `(script: string)` | Inject custom JavaScript |
141
- | `registerToolbar` | `(listener: (items: string[]) => void)` | Register toolbar selection listener |
142
-
143
- #### Properties
144
-
145
- | Property | Type | Description |
146
- |----------|------|-------------|
147
- | `isKeyboardOpen` | `boolean` | Whether the keyboard is currently open |
148
-
149
- ### Toolbar
150
-
151
- A customizable toolbar component that provides formatting buttons for the editor.
152
-
153
- #### Props
92
+ ### Editor Methods
93
+
94
+ Access via `editorRef.current`:
95
+
96
+ | Method | Description |
97
+ |--------|-------------|
98
+ | `setContentHTML(html)` | Set editor content |
99
+ | `getContentHtml()` | Get content (Promise, deprecated - use `onChange`) |
100
+ | `insertText(text)` | Insert plain text |
101
+ | `insertHTML(html)` | Insert HTML |
102
+ | `insertImage(attrs, style?)` | Insert image |
103
+ | `insertVideo(attrs, style?)` | Insert video |
104
+ | `insertLink(title, url)` | Insert link |
105
+ | `setFontSize(size)` | Set font size |
106
+ | `setFontName(name)` | Set font family |
107
+ | `setForeColor(color)` | Set text color |
108
+ | `setHiliteColor(color)` | Set highlight color |
109
+ | `setLineHeight(value)` | Set line height |
110
+ | `focusContentEditor()` | Focus editor |
111
+ | `blurContentEditor()` | Blur editor |
112
+ | `dismissKeyboard()` | Dismiss keyboard |
113
+ | `injectJavascript(script)` | Inject custom JS |
114
+
115
+ ### Toolbar Props
154
116
 
155
117
  | Prop | Type | Default | Description |
156
118
  |------|------|---------|-------------|
157
- | `editor` | `{ current: EditorRef \| null }` | - | Editor ref object (use `editor={editorRef}`) |
158
- | `getEditor` | `() => EditorRef \| null` | - | Function that returns editor ref (alternative to `editor`) |
159
- | `actions` | `string[]` | `defaultActions` | Array of action keys to display (see Actions below) |
160
- | `disabled` | `boolean` | `false` | Disable all toolbar buttons |
161
- | `readOnly` | `boolean` | `false` | Hide toolbar when read-only |
162
- | `iconTint` | `string` | `'#71787F'` | Default icon color |
119
+ | `editor` | `{ current: EditorRef \| null }` | - | Editor ref |
120
+ | `actions` | `string[]` | `defaultActions` | Action array (see Actions) |
121
+ | `disabled` | `boolean` | `false` | Disable toolbar |
122
+ | `readOnly` | `boolean` | `false` | Hide when read-only |
123
+ | `iconTint` | `string` | `'#71787F'` | Icon color |
163
124
  | `selectedIconTint` | `string` | - | Selected icon color |
164
- | `disabledIconTint` | `string` | - | Disabled icon color |
165
- | `iconSize` | `number` | `20` | Size of toolbar icons |
166
- | `iconGap` | `number` | `16` | Gap between toolbar icons |
167
- | `style` | `object` | - | Custom styles for toolbar container |
168
- | `itemStyle` | `object` | - | Custom styles for toolbar items |
169
- | `selectedButtonStyle` | `object` | - | Styles for selected buttons |
170
- | `unselectedButtonStyle` | `object` | - | Styles for unselected buttons |
171
- | `disabledButtonStyle` | `object` | - | Styles for disabled buttons |
172
- | `iconMap` | `Record<string, unknown>` | - | Custom icon map (override default icons) |
173
- | `renderAction` | `(action: string, selected: boolean) => React.ReactElement` | - | Custom render function for actions |
174
- | `onPressAddImage` | `() => void` | - | Callback when image button is pressed |
175
- | `onInsertLink` | `() => void` | - | Callback when link button is pressed |
176
- | `insertVideo` | `() => void` | - | Callback when video button is pressed |
177
- | `flatContainerStyle` | `object` | - | Styles for the FlatList container |
178
- | `horizontal` | `boolean` | `true` | Whether to display toolbar horizontally |
179
- | `children` | `React.ReactNode` | - | Additional content to render in toolbar |
180
-
181
- #### Custom Action Handlers
182
-
183
- You can provide custom handlers for specific actions by passing them as props:
125
+ | `iconSize` | `number` | `20` | Icon size |
126
+ | `iconGap` | `number` | `12` | Icon spacing |
127
+ | `iconMap` | `Record<string, unknown>` | - | Custom icons |
128
+ | `renderAction` | `(action, selected) => ReactNode` | - | Custom renderer |
129
+ | `onPressAddImage` | `() => void` | - | Image button handler |
130
+ | `onInsertLink` | `() => void` | - | Link button handler |
131
+ | `insertVideo` | `() => void` | - | Video button handler |
184
132
 
185
- ```tsx
186
- <Toolbar
187
- editor={editorRef}
188
- fontSize={() => {
189
- // Custom font size handler
190
- editorRef.current?.setFontSize(18);
191
- }}
192
- foreColor={() => {
193
- // Custom color picker handler
194
- editorRef.current?.setForeColor('#FF0000');
195
- }}
196
- lineHeight={() => {
197
- // Custom line height handler
198
- editorRef.current?.setLineHeight(1.5);
199
- }}
200
- />
201
- ```
133
+ ### Actions
202
134
 
203
- ## Actions
135
+ Import actions: `import { actions } from 'rn-rich-text-editor'`
204
136
 
205
- The toolbar uses action constants to identify different formatting options. Import them from the package:
137
+ **Text Formatting:** `setBold`, `setItalic`, `setUnderline`, `setStrikethrough`, `setSubscript`, `setSuperscript`, `removeFormat`
206
138
 
207
- ```tsx
208
- import { actions } from 'rn-rich-text-editor';
209
- ```
139
+ **Headings:** `heading1` through `heading6`, `setParagraph`
210
140
 
211
- ### Available Actions
212
-
213
- | Action | Constant | Description |
214
- |--------|----------|-------------|
215
- | **Text Formatting** |
216
- | Bold | `actions.setBold` | Make text bold |
217
- | Italic | `actions.setItalic` | Make text italic |
218
- | Underline | `actions.setUnderline` | Underline text |
219
- | Strikethrough | `actions.setStrikethrough` | Strikethrough text |
220
- | Subscript | `actions.setSubscript` | Subscript text |
221
- | Superscript | `actions.setSuperscript` | Superscript text |
222
- | Remove Format | `actions.removeFormat` | Remove all formatting |
223
- | **Headings** |
224
- | Heading 1 | `actions.heading1` | Apply H1 heading |
225
- | Heading 2 | `actions.heading2` | Apply H2 heading |
226
- | Heading 3 | `actions.heading3` | Apply H3 heading |
227
- | Heading 4 | `actions.heading4` | Apply H4 heading |
228
- | Heading 5 | `actions.heading5` | Apply H5 heading |
229
- | Heading 6 | `actions.heading6` | Apply H6 heading |
230
- | Paragraph | `actions.setParagraph` | Apply paragraph style |
231
- | **Alignment** |
232
- | Align Left | `actions.alignLeft` | Align text left |
233
- | Align Center | `actions.alignCenter` | Align text center |
234
- | Align Right | `actions.alignRight` | Align text right |
235
- | Align Full | `actions.alignFull` | Justify text |
236
- | Align (Cycle) | `actions.align` | Cycle through alignments |
237
- | **Lists** |
238
- | Bullet List | `actions.insertBulletsList` | Insert unordered list |
239
- | Ordered List | `actions.insertOrderedList` | Insert ordered list |
240
- | Checkbox List | `actions.checkboxList` | Insert checkbox list |
241
- | Indent | `actions.indent` | Increase indent |
242
- | Outdent | `actions.outdent` | Decrease indent |
243
- | **Media** |
244
- | Insert Image | `actions.insertImage` | Insert image |
245
- | Insert Video | `actions.insertVideo` | Insert video |
246
- | Insert Link | `actions.insertLink` | Insert hyperlink |
247
- | **Text Styling** |
248
- | Font Size | `actions.fontSize` | Change font size |
249
- | Font Name | `actions.fontName` | Change font family |
250
- | Text Color | `actions.foreColor` | Change text color |
251
- | Highlight Color | `actions.hiliteColor` | Change highlight color |
252
- | Line Height | `actions.lineHeight` | Change line height |
253
- | **Other** |
254
- | Code Block | `actions.code` | Insert code block |
255
- | Blockquote | `actions.blockquote` | Insert blockquote |
256
- | Horizontal Rule | `actions.setHR` | Insert horizontal line |
257
- | Line | `actions.line` | Insert line break |
258
- | Undo | `actions.undo` | Undo last action |
259
- | Redo | `actions.redo` | Redo last action |
260
- | Table | `actions.table` | Insert table |
261
- | Keyboard | `actions.keyboard` | Toggle keyboard |
262
-
263
- ### Default Actions
264
-
265
- The toolbar comes with a default set of actions:
141
+ **Alignment:** `alignLeft`, `alignCenter`, `alignRight`, `alignFull`, `align` (cycle)
266
142
 
267
- ```tsx
268
- import { defaultActions } from 'rn-rich-text-editor';
269
-
270
- // Default actions include:
271
- // - keyboard
272
- // - setBold
273
- // - setItalic
274
- // - setUnderline
275
- // - removeFormat
276
- // - insertBulletsList
277
- // - indent
278
- // - outdent
279
- // - insertLink
280
- // - lineHeight
281
- ```
282
-
283
- ## Usage Examples
143
+ **Lists:** `insertBulletsList`, `insertOrderedList`, `checkboxList`, `indent`, `outdent`
284
144
 
285
- ### Basic Editor with Toolbar
145
+ **Media:** `insertImage`, `insertVideo`, `insertLink`
286
146
 
287
- ```tsx
288
- import React, { useRef } from 'react';
289
- import { View, StyleSheet } from 'react-native';
290
- import { Editor, Toolbar } from 'rn-rich-text-editor';
147
+ **Styling:** `fontSize`, `fontName`, `foreColor`, `hiliteColor`, `lineHeight`
291
148
 
292
- function RichTextEditor() {
293
- const editorRef = useRef(null);
149
+ **Other:** `code`, `blockquote`, `setHR`, `line`, `undo`, `redo`, `table`, `keyboard`, `separator`
294
150
 
295
- return (
296
- <View style={styles.container}>
297
- <Editor
298
- ref={editorRef}
299
- placeholder="Start typing..."
300
- onChange={(html) => {
301
- console.log('Content:', html);
302
- }}
303
- style={styles.editor}
304
- />
305
- <Toolbar editor={editorRef} />
306
- </View>
307
- );
308
- }
151
+ **Default actions:** `keyboard`, `setBold`, `setItalic`, `setUnderline`, `removeFormat`, `insertBulletsList`, `indent`, `outdent`, `insertLink`
309
152
 
310
- const styles = StyleSheet.create({
311
- container: {
312
- flex: 1,
313
- backgroundColor: '#fff',
314
- },
315
- editor: {
316
- backgroundColor: '#fff',
317
- borderColor: '#ddd',
318
- borderWidth: 1,
319
- minHeight: 200,
320
- },
321
- });
322
- ```
153
+ ## 💡 Examples
323
154
 
324
- ### Custom Toolbar Actions
155
+ ### Custom Toolbar
325
156
 
326
157
  ```tsx
327
- import React, { useRef } from 'react';
328
158
  import { Editor, Toolbar, actions } from 'rn-rich-text-editor';
329
159
 
330
- function CustomToolbar() {
331
- const editorRef = useRef(null);
332
-
333
- const customActions = [
334
- actions.keyboard,
335
- actions.setBold,
336
- actions.setItalic,
337
- actions.setUnderline,
338
- actions.heading1,
339
- actions.insertBulletsList,
340
- actions.insertOrderedList,
341
- actions.insertLink,
342
- actions.insertImage,
343
- ];
160
+ const customActions = [
161
+ actions.keyboard,
162
+ actions.setBold,
163
+ actions.setItalic,
164
+ actions.heading1,
165
+ actions.insertBulletsList,
166
+ actions.insertLink,
167
+ ];
344
168
 
345
- return (
346
- <>
347
- <Editor ref={editorRef} />
348
- <Toolbar editor={editorRef} actions={customActions} />
349
- </>
350
- );
351
- }
169
+ <Toolbar editor={editorRef} actions={customActions} />
352
170
  ```
353
171
 
354
- ### Inserting Content Programmatically
172
+ ### Programmatic Content
355
173
 
356
174
  ```tsx
357
- import React, { useRef } from 'react';
358
- import { View, Button } from 'react-native';
359
- import { Editor } from 'rn-rich-text-editor';
360
-
361
- function ProgrammaticEditor() {
362
- const editorRef = useRef(null);
363
-
364
- const insertImage = () => {
365
- editorRef.current?.insertImage(
366
- { src: 'https://example.com/image.jpg', alt: 'Image' },
367
- { width: '100%', height: 'auto' }
368
- );
369
- };
370
-
371
- const insertLink = () => {
372
- editorRef.current?.insertLink('Click here', 'https://example.com');
373
- };
374
-
375
- const insertText = () => {
376
- editorRef.current?.insertText('Hello World!');
377
- };
378
-
379
- return (
380
- <View>
381
- <Editor ref={editorRef} />
382
- <Button title="Insert Image" onPress={insertImage} />
383
- <Button title="Insert Link" onPress={insertLink} />
384
- <Button title="Insert Text" onPress={insertText} />
385
- </View>
386
- );
387
- }
175
+ // Insert content
176
+ editorRef.current?.insertText('Hello');
177
+ editorRef.current?.insertHTML('<strong>Bold</strong>');
178
+ editorRef.current?.insertLink('Click', 'https://example.com');
179
+ editorRef.current?.insertImage({ src: 'image.jpg' }, { width: '100%' });
180
+
181
+ // Style text
182
+ editorRef.current?.setFontSize(18);
183
+ editorRef.current?.setForeColor('#FF0000');
184
+ editorRef.current?.setLineHeight(1.5);
388
185
  ```
389
186
 
390
- ### Styling the Editor
187
+ ### Styling
391
188
 
392
189
  ```tsx
393
- import React, { useRef } from 'react';
394
- import { Editor } from 'rn-rich-text-editor';
395
-
396
- function StyledEditor() {
397
- const editorRef = useRef(null);
190
+ <Editor
191
+ ref={editorRef}
192
+ editorStyle={{
193
+ backgroundColor: '#f5f5f5',
194
+ color: '#333',
195
+ placeholderColor: '#999',
196
+ caretColor: '#007AFF',
197
+ contentCSSText: `
198
+ font-family: -apple-system, sans-serif;
199
+ font-size: 16px;
200
+ line-height: 1.6;
201
+ `,
202
+ }}
203
+ />
398
204
 
399
- return (
400
- <Editor
401
- ref={editorRef}
402
- editorStyle={{
403
- backgroundColor: '#f5f5f5',
404
- color: '#333',
405
- placeholderColor: '#999',
406
- caretColor: '#007AFF',
407
- contentCSSText: `
408
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
409
- font-size: 16px;
410
- line-height: 1.6;
411
- `,
412
- }}
413
- />
414
- );
415
- }
205
+ <Toolbar
206
+ editor={editorRef}
207
+ iconTint="#333"
208
+ selectedIconTint="#007AFF"
209
+ iconSize={24}
210
+ style={{ backgroundColor: '#f0f0f0', paddingVertical: 8 }}
211
+ />
416
212
  ```
417
213
 
418
- ### Custom Toolbar Styling
214
+ ### Image Handling
419
215
 
420
216
  ```tsx
421
- import React, { useRef } from 'react';
422
- import { Editor, Toolbar } from 'rn-rich-text-editor';
217
+ import { ImagePicker } from 'react-native-image-picker';
423
218
 
424
- function CustomStyledToolbar() {
425
- const editorRef = useRef(null);
219
+ const handleAddImage = () => {
220
+ ImagePicker.launchImageLibrary({}, (response) => {
221
+ if (response.assets?.[0]) {
222
+ editorRef.current?.insertImage(
223
+ { src: response.assets[0].uri },
224
+ { width: '100%', height: 'auto' }
225
+ );
226
+ }
227
+ });
228
+ };
426
229
 
427
- return (
428
- <>
429
- <Editor ref={editorRef} />
430
- <Toolbar
431
- editor={editorRef}
432
- iconTint="#333"
433
- selectedIconTint="#007AFF"
434
- iconSize={24}
435
- iconGap={20}
436
- style={{
437
- backgroundColor: '#f0f0f0',
438
- paddingVertical: 8,
439
- }}
440
- selectedButtonStyle={{
441
- backgroundColor: '#e0e0e0',
442
- borderRadius: 4,
443
- }}
444
- />
445
- </>
446
- );
447
- }
230
+ <Toolbar editor={editorRef} onPressAddImage={handleAddImage} />
448
231
  ```
449
232
 
450
- ### Handling Image Insertion
233
+ ### Read-Only Display
451
234
 
452
235
  ```tsx
453
- import React, { useRef, useState } from 'react';
454
- import { Editor, Toolbar, actions } from 'rn-rich-text-editor';
455
- import { ImagePicker } from 'react-native-image-picker';
456
-
457
- function ImageEditor() {
458
- const editorRef = useRef(null);
459
- const [html, setHtml] = useState('');
460
-
461
- const handleAddImage = () => {
462
- ImagePicker.launchImageLibrary({}, (response) => {
463
- if (response.assets && response.assets[0]) {
464
- const imageUri = response.assets[0].uri;
465
- editorRef.current?.insertImage(
466
- { src: imageUri, alt: 'Image' },
467
- { width: '100%', height: 'auto' }
468
- );
469
- }
470
- });
471
- };
472
-
473
- return (
474
- <>
475
- <Editor
476
- ref={editorRef}
477
- onChange={setHtml}
478
- initialContentHTML={html}
479
- />
480
- <Toolbar
481
- editor={editorRef}
482
- onPressAddImage={handleAddImage}
483
- />
484
- </>
485
- );
486
- }
236
+ <Editor
237
+ readOnly={true}
238
+ initialContentHTML="<p>Formatted content</p>"
239
+ editorStyle={{ backgroundColor: '#fff', color: '#333' }}
240
+ />
487
241
  ```
488
242
 
489
- ### Read-Only Mode
243
+ ### Custom Action Handlers
490
244
 
491
245
  ```tsx
492
- import React from 'react';
493
- import { Editor } from 'rn-rich-text-editor';
494
-
495
- function ReadOnlyViewer() {
496
- const htmlContent = '<p>This is read-only content</p>';
497
-
498
- return (
499
- <Editor
500
- readOnly={true}
501
- initialContentHTML={htmlContent}
502
- editorStyle={{
503
- backgroundColor: '#fff',
504
- color: '#333',
505
- }}
506
- />
507
- );
508
- }
246
+ <Toolbar
247
+ editor={editorRef}
248
+ fontSize={() => editorRef.current?.setFontSize(18)}
249
+ foreColor={() => editorRef.current?.setForeColor('#FF0000')}
250
+ lineHeight={() => editorRef.current?.setLineHeight(1.5)}
251
+ />
509
252
  ```
510
253
 
511
- ### Getting Content
254
+ ### Custom Icons & Renderers
512
255
 
513
256
  ```tsx
514
- import React, { useRef } from 'react';
515
- import { Button } from 'react-native';
516
- import { Editor } from 'rn-rich-text-editor';
257
+ // Custom icons
258
+ const iconMap = {
259
+ [actions.setBold]: require('./assets/bold.png'),
260
+ [actions.setItalic]: require('./assets/italic.png'),
261
+ };
517
262
 
518
- function ContentGetter() {
519
- const editorRef = useRef(null);
263
+ <Toolbar editor={editorRef} iconMap={iconMap} />
520
264
 
521
- const getContent = async () => {
522
- try {
523
- const html = await editorRef.current?.getContentHtml();
524
- console.log('Editor content:', html);
525
- // Use html for saving, sending, etc.
526
- } catch (error) {
527
- console.error('Error getting content:', error);
528
- }
529
- };
265
+ // Custom renderer
266
+ const renderAction = (action, selected) => {
267
+ if (action === actions.setBold) {
268
+ return <CustomBoldButton selected={selected} />;
269
+ }
270
+ return null; // Use default for others
271
+ };
530
272
 
531
- return (
532
- <>
533
- <Editor ref={editorRef} />
534
- <Button title="Get Content" onPress={getContent} />
535
- </>
536
- );
537
- }
273
+ <Toolbar editor={editorRef} renderAction={renderAction} />
538
274
  ```
539
275
 
540
- ## Advanced Usage
276
+ ## 🎯 Smart Paste
541
277
 
542
- ### Custom Icon Map
278
+ The editor automatically preserves HTML formatting when pasting. It detects HTML in the clipboard and maintains:
279
+ - Text formatting (bold, italic, colors, etc.)
280
+ - Inline styles
281
+ - Links and HTML elements
543
282
 
283
+ To paste as plain text only:
544
284
  ```tsx
545
- import React, { useRef } from 'react';
546
- import { Editor, Toolbar, actions } from 'rn-rich-text-editor';
547
- import { Image } from 'react-native';
548
-
549
- function CustomIcons() {
550
- const editorRef = useRef(null);
551
-
552
- const iconMap = {
553
- [actions.setBold]: require('./assets/custom-bold.png'),
554
- [actions.setItalic]: require('./assets/custom-italic.png'),
555
- // ... more custom icons
556
- };
557
-
558
- return (
559
- <>
560
- <Editor ref={editorRef} />
561
- <Toolbar editor={editorRef} iconMap={iconMap} />
562
- </>
563
- );
564
- }
285
+ <Editor pasteAsPlainText={true} />
565
286
  ```
566
287
 
567
- ### Custom Action Renderer
568
-
569
- ```tsx
570
- import React, { useRef } from 'react';
571
- import { Text, TouchableOpacity } from 'react-native';
572
- import { Editor, Toolbar, actions } from 'rn-rich-text-editor';
573
-
574
- function CustomRenderer() {
575
- const editorRef = useRef(null);
288
+ ## 🔧 Advanced
576
289
 
577
- const renderAction = (action, selected) => {
578
- if (action === actions.setBold) {
579
- return (
580
- <TouchableOpacity
581
- style={{
582
- padding: 8,
583
- backgroundColor: selected ? '#007AFF' : 'transparent',
584
- }}
585
- onPress={() => editorRef.current?.sendAction(action, 'result')}
586
- >
587
- <Text style={{ fontWeight: 'bold' }}>B</Text>
588
- </TouchableOpacity>
589
- );
590
- }
591
- // Return null to use default rendering
592
- return null;
593
- };
290
+ ### Custom JavaScript
594
291
 
595
- return (
596
- <>
597
- <Editor ref={editorRef} />
598
- <Toolbar editor={editorRef} renderAction={renderAction} />
599
- </>
600
- );
601
- }
292
+ ```tsx
293
+ editorRef.current?.injectJavascript(`
294
+ document.querySelector('#content').style.border = '2px solid red';
295
+ `);
602
296
  ```
603
297
 
604
- ## TypeScript Support
605
-
606
- This package includes TypeScript definitions. Import types as needed:
298
+ ### TypeScript
607
299
 
608
300
  ```typescript
609
- import { Editor, Toolbar, EditorRef, EditorProps, ToolbarProps, actions } from 'rn-rich-text-editor';
301
+ import { Editor, Toolbar, EditorRef, EditorProps, actions } from 'rn-rich-text-editor';
610
302
 
611
303
  const editorRef = useRef<EditorRef>(null);
612
-
613
- const editorProps: EditorProps = {
614
- placeholder: 'Type here...',
615
- onChange: (html) => console.log(html),
616
- };
304
+ const props: EditorProps = { placeholder: 'Type...', onChange: (html) => {} };
617
305
  ```
618
306
 
619
- ## Platform-Specific Notes
307
+ ## 🐛 Troubleshooting
620
308
 
621
- ### iOS
622
- - Uses WebKit WebView
623
- - Keyboard handling is automatic
624
- - Supports all features
309
+ **Editor not focusing?**
310
+ - Set `initialFocus={true}` or call `focusContentEditor()`
311
+ - On Android, use `showAndroidKeyboard()` first
625
312
 
626
- ### Android
627
- - Uses Android WebView
628
- - May require additional keyboard handling
629
- - All features supported
630
-
631
- ## Troubleshooting
632
-
633
- ### Editor not focusing
634
- - Ensure `initialFocus` is set to `true` if needed
635
- - Call `editorRef.current?.focusContentEditor()` programmatically
636
- - On Android, use `showAndroidKeyboard()` before focusing
637
-
638
- ### Content not updating
313
+ **Content not updating?**
639
314
  - Use `onChange` callback instead of polling `getContentHtml()`
640
- - Ensure `initialContentHTML` is set correctly on mount
315
+ - Set `initialContentHTML` correctly on mount
316
+
317
+ **Paste not preserving format?**
318
+ - Ensure `pasteAsPlainText` is `false`
319
+ - HTML paste works when clipboard contains HTML data
641
320
 
642
- ### Toolbar not responding
643
- - Verify `editor` prop is correctly passed with ref object
644
- - Check that editor is initialized before toolbar interactions
645
- - Ensure `disabled` prop is not set to `true`
321
+ **Toolbar not working?**
322
+ - Verify `editor` prop uses ref object: `editor={editorRef}`
323
+ - Ensure editor initializes before toolbar interactions
646
324
 
647
- ## Contributing
325
+ ## 📱 Platform Notes
648
326
 
649
- Contributions are welcome! Please feel free to submit a Pull Request.
327
+ - **iOS**: WebKit WebView, automatic keyboard handling
328
+ - **Android**: Android WebView, may need `showAndroidKeyboard()` for focus
650
329
 
651
- ## License
330
+ ## 📄 License
652
331
 
653
332
  MIT
654
333
 
655
- ## Support
334
+ ## 🤝 Contributing
656
335
 
657
- For issues and feature requests, please visit the [GitHub repository](https://github.com/vishaal2002/rn-rich-text-editor).
336
+ Contributions welcome! See the [GitHub repository](https://github.com/vishaal2002/rn-rich-editor) for issues and PRs.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rn-rich-text-editor",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "A rich text editor component for React Native",
5
5
  "keywords": [
6
6
  "react-native",
package/src/Editor.js CHANGED
@@ -295,12 +295,13 @@ export default class Editor extends Component {
295
295
 
296
296
  renderWebView() {
297
297
  let that = this;
298
- const { html, editorStyle, useContainer, style, onLink, dataDetectorTypes, readOnly, ...rest } = that.props;
298
+ const { html, editorStyle, useContainer, style, onLink, dataDetectorTypes, readOnly, disabled, ...rest } = that.props;
299
299
  const { html: viewHTML, height: stateHeight } = that.state;
300
+ // Don't apply style prop to WebView to avoid double borders - style is only for container
300
301
  const webViewStyle = [
301
302
  styles.webview,
302
- style,
303
303
  readOnly && stateHeight > 0 ? { height: stateHeight, flex: 0 } : undefined,
304
+ disabled ? { pointerEvents: 'none' } : undefined,
304
305
  ].filter(Boolean);
305
306
  return (
306
307
  <>
@@ -315,7 +316,7 @@ export default class Editor extends Component {
315
316
  ref={that.setRef}
316
317
  onMessage={that.onMessage}
317
318
  originWhitelist={['*']}
318
- dataDetectorTypes={dataDetectorTypes}
319
+ dataDetectorTypes={disabled ? 'none' : dataDetectorTypes}
319
320
  domStorageEnabled={false}
320
321
  bounces={false}
321
322
  javaScriptEnabled={true}
@@ -348,11 +349,30 @@ export default class Editor extends Component {
348
349
  // If set to false, it will not use a View wrapper
349
350
  const { useContainer, style, errorMessage, readOnly, disabled } = this.props;
350
351
  const errorStyle = !readOnly && errorMessage ? { borderWidth: 1, borderColor: '#d92d20' } : {};
351
- const disabledStyle = disabled ? { backgroundColor: '#C9CED7' } : {};
352
- const readOnlyStyle = readOnly ? { borderWidth: 0 } : {};
352
+ const disabledStyle = disabled ? { backgroundColor: '#C9CED7', pointerEvents: 'none' } : {};
353
+
354
+ // For readonly, remove border from style prop to ensure no border appears
355
+ let containerStyleProp = style;
356
+ if (readOnly && style) {
357
+ if (Array.isArray(style)) {
358
+ containerStyleProp = style.map(styleItem => {
359
+ if (styleItem && typeof styleItem === 'object') {
360
+ const { borderWidth, borderColor, borderTopWidth, borderBottomWidth, borderLeftWidth, borderRightWidth, ...rest } = styleItem;
361
+ return rest;
362
+ }
363
+ return styleItem;
364
+ });
365
+ } else if (typeof style === 'object') {
366
+ const { borderWidth, borderColor, borderTopWidth, borderBottomWidth, borderLeftWidth, borderRightWidth, ...rest } = style;
367
+ containerStyleProp = rest;
368
+ }
369
+ }
370
+
371
+ const readOnlyStyle = readOnly ? { borderWidth: 0, borderColor: 'transparent' } : {};
353
372
  // overflow: 'hidden' prevents the WebView from visually cutting off the container border at corners
354
373
  // disabledStyle last so it overrides user's backgroundColor when disabled
355
- const containerStyle = [style, errorStyle, readOnlyStyle, { overflow: 'hidden' }, disabledStyle];
374
+ // readOnlyStyle after containerStyleProp to ensure border is removed
375
+ const containerStyle = [containerStyleProp, errorStyle, { overflow: 'hidden' }, readOnlyStyle, disabledStyle];
356
376
  if (useContainer) {
357
377
  containerStyle.push({ height });
358
378
  // For readonly, ensure container expands fully to show all content without scrolling
@@ -675,15 +675,25 @@ function createHTML(options = {}) {
675
675
  addEventListener(content, 'blur', handleBlur);
676
676
  addEventListener(content, 'focus', handleFocus);
677
677
  addEventListener(content, 'paste', function (e) {
678
- // get text representation of clipboard
679
- var text = (e.originalEvent || e).clipboardData.getData('text/plain');
678
+ var clipboardData = (e.originalEvent || e).clipboardData;
679
+ var text = clipboardData.getData('text/plain');
680
+ var html = clipboardData.getData('text/html');
680
681
 
681
- ${pasteListener} && postAction({type: 'CONTENT_PASTED', data: text});
682
+ ${pasteListener} && postAction({type: 'CONTENT_PASTED', data: text || html});
682
683
  if (${pasteAsPlainText}) {
683
- // cancel paste
684
684
  e.preventDefault();
685
- // insert text manually
686
685
  exec("insertText", text);
686
+ } else if (html && html.trim() !== '') {
687
+ e.preventDefault();
688
+ exec("insertHTML", html);
689
+ } else if (text && (text.indexOf('&lt;') !== -1 || text.indexOf('<') !== -1)) {
690
+ var htmlFromText = text.indexOf('&lt;') !== -1
691
+ ? text.replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"')
692
+ : text;
693
+ if (htmlFromText.indexOf('<') !== -1) {
694
+ e.preventDefault();
695
+ exec("insertHTML", htmlFromText);
696
+ }
687
697
  }
688
698
  });
689
699
  addEventListener(content, 'compositionstart', function(event){