react-native-richify 1.0.5 → 1.0.6
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 +383 -120
- package/lib/commonjs/components/RichTextInput.js +0 -4
- package/lib/commonjs/components/RichTextInput.js.map +1 -1
- package/lib/commonjs/constants/defaultStyles.js +6 -10
- package/lib/commonjs/constants/defaultStyles.js.map +1 -1
- package/lib/commonjs/hooks/useFormatting.js +9 -5
- package/lib/commonjs/hooks/useFormatting.js.map +1 -1
- package/lib/commonjs/utils/formatter.js.map +1 -1
- package/lib/module/components/RichTextInput.js +0 -4
- package/lib/module/components/RichTextInput.js.map +1 -1
- package/lib/module/constants/defaultStyles.js +6 -10
- package/lib/module/constants/defaultStyles.js.map +1 -1
- package/lib/module/hooks/useFormatting.js +9 -5
- package/lib/module/hooks/useFormatting.js.map +1 -1
- package/lib/module/utils/formatter.js.map +1 -1
- package/lib/typescript/src/components/RichTextInput.d.ts.map +1 -1
- package/lib/typescript/src/constants/defaultStyles.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useFormatting.d.ts.map +1 -1
- package/lib/typescript/src/utils/formatter.d.ts +1 -1
- package/lib/typescript/src/utils/formatter.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/RichTextInput.tsx +0 -4
- package/src/constants/defaultStyles.ts +6 -7
- package/src/hooks/useFormatting.ts +30 -5
- package/src/utils/formatter.ts +1 -1
package/README.md
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
# react-native-richify
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A rich text input for React Native with a normal `TextInput` editing surface, a built-in formatting toolbar, and live Markdown or HTML output.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/react-native-richify)
|
|
6
6
|
[](https://github.com/soumya-99/react-native-richify/blob/main/LICENSE)
|
|
7
7
|
|
|
8
8
|
## Features
|
|
9
9
|
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
10
|
+
- Native editing with no WebView
|
|
11
|
+
- Rich inline formatting: bold, italic, underline, strikethrough, code
|
|
12
|
+
- Line-level formatting: H1, H2, H3, bullet list, ordered list, left/center/right alignment
|
|
13
|
+
- Link support on selected text
|
|
14
|
+
- Live output panel with Markdown or HTML serialization
|
|
15
|
+
- Raw output view or rendered preview view
|
|
16
|
+
- Auto-growing input with configurable input and preview heights
|
|
17
|
+
- Custom toolbar items, custom toolbar rendering, and theme overrides
|
|
18
|
+
- JSON import/export through typed segment data
|
|
19
|
+
- Headless `useRichText` hook for fully custom editors
|
|
20
20
|
|
|
21
21
|
## Installation
|
|
22
22
|
|
|
@@ -26,7 +26,7 @@ npm install react-native-richify
|
|
|
26
26
|
yarn add react-native-richify
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
No native
|
|
29
|
+
No native modules are required. The package works in Expo and bare React Native apps.
|
|
30
30
|
|
|
31
31
|
## Quick Start
|
|
32
32
|
|
|
@@ -39,165 +39,411 @@ export default function App() {
|
|
|
39
39
|
return (
|
|
40
40
|
<View style={{ flex: 1, padding: 16 }}>
|
|
41
41
|
<RichTextInput
|
|
42
|
-
placeholder="
|
|
42
|
+
placeholder="Write something..."
|
|
43
43
|
showToolbar
|
|
44
|
+
showOutputPreview
|
|
45
|
+
defaultOutputFormat="markdown"
|
|
44
46
|
onChangeText={(text) => console.log('Plain text:', text)}
|
|
45
|
-
|
|
47
|
+
onChangeOutput={(output, format) =>
|
|
48
|
+
console.log(`Serialized ${format}:`, output)
|
|
49
|
+
}
|
|
46
50
|
/>
|
|
47
51
|
</View>
|
|
48
52
|
);
|
|
49
53
|
}
|
|
50
54
|
```
|
|
51
55
|
|
|
52
|
-
|
|
56
|
+
This gives you:
|
|
53
57
|
|
|
54
|
-
|
|
58
|
+
- a visible auto-growing `TextInput`
|
|
59
|
+
- a horizontally scrollable toolbar
|
|
60
|
+
- a collapsible output panel that opens when content exists
|
|
61
|
+
- Markdown output by default, with HTML and rendered preview toggles in the toolbar
|
|
55
62
|
|
|
56
|
-
|
|
63
|
+
## How Editing Works
|
|
57
64
|
|
|
58
|
-
The
|
|
65
|
+
The editor stores content as `StyledSegment[]`, not HTML strings. The plain text you type is always the source of truth for editing, and formatting metadata is applied to matching text ranges or lines.
|
|
59
66
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
|
72
|
-
|
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
77
|
-
|
|
|
67
|
+
Formatting rules:
|
|
68
|
+
|
|
69
|
+
- If no text is selected, pressing a style button affects the next characters you type.
|
|
70
|
+
- If text is selected, pressing a style button formats the selected range immediately.
|
|
71
|
+
- Headings, lists, and alignment are line-level controls.
|
|
72
|
+
- Every formatting button is toggleable except the output controls: `MD`, `HTML`, `Raw`, and `View`.
|
|
73
|
+
|
|
74
|
+
## Built-in Toolbar
|
|
75
|
+
|
|
76
|
+
The default toolbar includes these controls:
|
|
77
|
+
|
|
78
|
+
| Button | Purpose |
|
|
79
|
+
| --- | --- |
|
|
80
|
+
| `B` | Toggle bold |
|
|
81
|
+
| `I` | Toggle italic |
|
|
82
|
+
| `U` | Toggle underline |
|
|
83
|
+
| `S` | Toggle strikethrough |
|
|
84
|
+
| `<>` | Toggle inline code |
|
|
85
|
+
| `H1`, `H2`, `H3` | Toggle heading level on the current line |
|
|
86
|
+
| `•≡` | Toggle bullet list on the current line |
|
|
87
|
+
| `1≡` | Toggle ordered list on the current line |
|
|
88
|
+
| `🔗` | Apply or clear a hyperlink on the current selection |
|
|
89
|
+
| `⇤`, `↔`, `⇥` | Toggle left, center, or right alignment on the current line |
|
|
90
|
+
| `MD`, `HTML` | Switch serialized output format |
|
|
91
|
+
| `Raw`, `View` | Switch between literal serialized output and rendered preview |
|
|
78
92
|
|
|
79
|
-
|
|
93
|
+
Notes:
|
|
80
94
|
|
|
81
|
-
|
|
95
|
+
- Pressing the active heading again removes that heading.
|
|
96
|
+
- Pressing the active list or alignment button again clears it.
|
|
97
|
+
- The toolbar is horizontally scrollable by default.
|
|
98
|
+
- Image insertion is not part of the built-in toolbar right now.
|
|
99
|
+
|
|
100
|
+
## Output Panel
|
|
101
|
+
|
|
102
|
+
The panel below the input can show either:
|
|
103
|
+
|
|
104
|
+
- literal serialized output (`Raw`)
|
|
105
|
+
- rendered rich output (`View`)
|
|
106
|
+
|
|
107
|
+
It can serialize as either:
|
|
108
|
+
|
|
109
|
+
- Markdown
|
|
110
|
+
- HTML
|
|
111
|
+
|
|
112
|
+
Useful props:
|
|
113
|
+
|
|
114
|
+
| Prop | Description |
|
|
115
|
+
| --- | --- |
|
|
116
|
+
| `showOutputPreview` | Show or hide the output panel entirely |
|
|
117
|
+
| `outputFormat` | Controlled output format |
|
|
118
|
+
| `defaultOutputFormat` | Initial output format for uncontrolled usage |
|
|
119
|
+
| `outputPreviewMode` | Controlled preview mode: `'literal'` or `'rendered'` |
|
|
120
|
+
| `defaultOutputPreviewMode` | Initial preview mode for uncontrolled usage |
|
|
121
|
+
| `maxOutputHeight` | Max height of the output panel before it scrolls |
|
|
122
|
+
| `onChangeOutput` | Called whenever serialized output changes |
|
|
123
|
+
| `onChangeOutputFormat` | Called when toolbar output format changes |
|
|
124
|
+
| `onChangeOutputPreviewMode` | Called when toolbar preview mode changes |
|
|
125
|
+
|
|
126
|
+
Example:
|
|
82
127
|
|
|
83
128
|
```tsx
|
|
84
|
-
import {
|
|
129
|
+
import React, { useState } from 'react';
|
|
130
|
+
import { RichTextInput, type OutputFormat } from 'react-native-richify';
|
|
85
131
|
|
|
86
|
-
function
|
|
87
|
-
const
|
|
88
|
-
onChangeText: (text) => console.log(text),
|
|
89
|
-
});
|
|
132
|
+
export function ControlledOutputEditor() {
|
|
133
|
+
const [format, setFormat] = useState<OutputFormat>('html');
|
|
90
134
|
|
|
91
135
|
return (
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
</>
|
|
136
|
+
<RichTextInput
|
|
137
|
+
outputFormat={format}
|
|
138
|
+
onChangeOutputFormat={setFormat}
|
|
139
|
+
defaultOutputPreviewMode="rendered"
|
|
140
|
+
maxOutputHeight={220}
|
|
141
|
+
onChangeOutput={(output) => {
|
|
142
|
+
console.log(output);
|
|
143
|
+
}}
|
|
144
|
+
/>
|
|
102
145
|
);
|
|
103
146
|
}
|
|
104
147
|
```
|
|
105
148
|
|
|
106
|
-
|
|
149
|
+
## Links
|
|
150
|
+
|
|
151
|
+
Link formatting is selection-based. Select text, then press the link button.
|
|
152
|
+
|
|
153
|
+
There are two ways to use it:
|
|
107
154
|
|
|
108
|
-
|
|
155
|
+
1. Provide `onRequestLink` and show your own prompt, sheet, or modal.
|
|
156
|
+
2. Rely on the built-in fallback, which only auto-links when the selected text already looks like a URL, domain, or email address.
|
|
157
|
+
|
|
158
|
+
Custom link flow:
|
|
109
159
|
|
|
110
160
|
```tsx
|
|
111
|
-
|
|
161
|
+
<RichTextInput
|
|
162
|
+
onRequestLink={({ selectedText, currentUrl, applyLink }) => {
|
|
163
|
+
console.log('Selected:', selectedText);
|
|
164
|
+
console.log('Existing URL:', currentUrl);
|
|
112
165
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
166
|
+
// Replace this with your own modal, bottom sheet, or form.
|
|
167
|
+
applyLink('https://example.com');
|
|
168
|
+
}}
|
|
169
|
+
/>
|
|
170
|
+
```
|
|
117
171
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
172
|
+
If the selection already has a link, pressing the link button clears it.
|
|
173
|
+
|
|
174
|
+
## Sizing and TextInput Behavior
|
|
175
|
+
|
|
176
|
+
`RichTextInput` uses a normal visible `TextInput`.
|
|
177
|
+
|
|
178
|
+
- `minHeight` controls the minimum editor height.
|
|
179
|
+
- `maxHeight` limits the editor height before the input itself scrolls.
|
|
180
|
+
- `maxOutputHeight` limits the output panel height before the preview scrolls.
|
|
181
|
+
- `textInputProps` lets you pass through additional native `TextInput` props.
|
|
182
|
+
|
|
183
|
+
Example:
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
<RichTextInput
|
|
187
|
+
minHeight={140}
|
|
188
|
+
maxHeight={280}
|
|
189
|
+
maxOutputHeight={200}
|
|
190
|
+
textInputProps={{
|
|
191
|
+
autoCapitalize: 'sentences',
|
|
192
|
+
keyboardType: 'default',
|
|
193
|
+
}}
|
|
194
|
+
/>
|
|
126
195
|
```
|
|
127
196
|
|
|
128
|
-
|
|
197
|
+
## API Overview
|
|
198
|
+
|
|
199
|
+
### `<RichTextInput />`
|
|
200
|
+
|
|
201
|
+
Most common props:
|
|
129
202
|
|
|
130
|
-
|
|
|
131
|
-
|
|
132
|
-
| `
|
|
203
|
+
| Prop | Type | Default | Description |
|
|
204
|
+
| --- | --- | --- | --- |
|
|
205
|
+
| `initialSegments` | `StyledSegment[]` | empty content | Initial document value |
|
|
206
|
+
| `onChangeSegments` | `(segments) => void` | - | Called with the rich document model |
|
|
207
|
+
| `onChangeText` | `(text) => void` | - | Called with plain text |
|
|
208
|
+
| `placeholder` | `string` | `"Start typing..."` | Placeholder text |
|
|
209
|
+
| `showToolbar` | `boolean` | `true` | Show the built-in toolbar |
|
|
210
|
+
| `toolbarPosition` | `'top' \| 'bottom'` | `'top'` | Place toolbar above or below the editor |
|
|
211
|
+
| `toolbarItems` | `ToolbarItem[]` | default items | Replace the built-in toolbar item list |
|
|
212
|
+
| `theme` | `RichTextTheme` | default theme | Visual overrides |
|
|
213
|
+
| `showOutputPreview` | `boolean` | `true` | Enable the output panel |
|
|
214
|
+
| `multiline` | `boolean` | `true` | Standard `TextInput` multiline editing |
|
|
215
|
+
| `minHeight` | `number` | `120` | Minimum editor height |
|
|
216
|
+
| `maxHeight` | `number` | - | Maximum editor height before scrolling |
|
|
217
|
+
| `autoFocus` | `boolean` | `false` | Focus input on mount |
|
|
218
|
+
| `textInputProps` | `TextInputProps` subset | - | Additional native input props |
|
|
219
|
+
| `renderToolbar` | `(props) => ReactElement` | - | Render a completely custom toolbar |
|
|
220
|
+
| `onReady` | `(actions) => void` | - | Access editor actions outside the toolbar |
|
|
221
|
+
|
|
222
|
+
### Actions from `onReady` or `useRichText`
|
|
223
|
+
|
|
224
|
+
| Action | Description |
|
|
225
|
+
| --- | --- |
|
|
226
|
+
| `toggleFormat(format)` | Toggle `bold`, `italic`, `underline`, `strikethrough`, or `code` |
|
|
227
|
+
| `setHeading(level)` | Toggle `h1`, `h2`, `h3`, or clear with `none` |
|
|
228
|
+
| `setListType(type)` | Toggle `bullet`, `ordered`, or clear with `none` |
|
|
229
|
+
| `setTextAlign(align)` | Toggle `left`, `center`, or `right` alignment |
|
|
230
|
+
| `setLink(url?)` | Apply or clear hyperlink formatting |
|
|
133
231
|
| `setColor(color)` | Set text color |
|
|
134
232
|
| `setBackgroundColor(color)` | Set highlight color |
|
|
135
233
|
| `setFontSize(size)` | Set font size |
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
141
|
-
| `
|
|
142
|
-
| `
|
|
234
|
+
| `handleTextChange(text)` | Sync plain text input changes |
|
|
235
|
+
| `handleSelectionChange(selection)` | Sync `TextInput` selection changes |
|
|
236
|
+
| `isFormatActive(format)` | Check active state for inline formats |
|
|
237
|
+
| `getSelectionStyle()` | Read the common style for the current selection |
|
|
238
|
+
| `getOutput(format?)` | Serialize current content as Markdown or HTML |
|
|
239
|
+
| `getPlainText()` | Get plain text only |
|
|
240
|
+
| `exportJSON()` | Export the current segment array |
|
|
241
|
+
| `importJSON(segments)` | Replace content from a saved segment array |
|
|
242
|
+
| `clear()` | Reset the editor |
|
|
243
|
+
|
|
244
|
+
## Custom Toolbar
|
|
245
|
+
|
|
246
|
+
Use `toolbarItems` when you only want to replace the built-in buttons, or `renderToolbar` when you want full control.
|
|
247
|
+
|
|
248
|
+
### `toolbarItems`
|
|
249
|
+
|
|
250
|
+
Supported built-in item fields:
|
|
251
|
+
|
|
252
|
+
- `format`
|
|
253
|
+
- `heading`
|
|
254
|
+
- `listType`
|
|
255
|
+
- `textAlign`
|
|
256
|
+
- `outputFormat`
|
|
257
|
+
- `outputPreviewMode`
|
|
258
|
+
- `actionType: 'link'`
|
|
259
|
+
- `onPress`
|
|
260
|
+
- `renderButton`
|
|
261
|
+
|
|
262
|
+
Example:
|
|
263
|
+
|
|
264
|
+
```tsx
|
|
265
|
+
<RichTextInput
|
|
266
|
+
toolbarItems={[
|
|
267
|
+
{ id: 'bold', label: 'B', format: 'bold' },
|
|
268
|
+
{ id: 'h1', label: 'H1', heading: 'h1' },
|
|
269
|
+
{ id: 'bullet', label: '•≡', listType: 'bullet' },
|
|
270
|
+
{ id: 'center', label: '↔', textAlign: 'center' },
|
|
271
|
+
{ id: 'html', label: 'HTML', outputFormat: 'html' },
|
|
272
|
+
{ id: 'link', label: '🔗', actionType: 'link' },
|
|
273
|
+
]}
|
|
274
|
+
/>
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### `renderToolbar`
|
|
278
|
+
|
|
279
|
+
Use `renderToolbar` if you need a custom layout or your own button components.
|
|
280
|
+
|
|
281
|
+
```tsx
|
|
282
|
+
<RichTextInput
|
|
283
|
+
renderToolbar={({
|
|
284
|
+
actions,
|
|
285
|
+
outputFormat,
|
|
286
|
+
outputPreviewMode,
|
|
287
|
+
onOutputFormatChange,
|
|
288
|
+
onOutputPreviewModeChange,
|
|
289
|
+
}) => (
|
|
290
|
+
<>
|
|
291
|
+
<MyButton onPress={() => actions.toggleFormat('bold')} label="Bold" />
|
|
292
|
+
<MyButton onPress={() => actions.setHeading('h2')} label="Heading" />
|
|
293
|
+
<MyButton
|
|
294
|
+
onPress={() => onOutputFormatChange('html')}
|
|
295
|
+
label={outputFormat === 'html' ? 'HTML on' : 'HTML'}
|
|
296
|
+
/>
|
|
297
|
+
<MyButton
|
|
298
|
+
onPress={() => onOutputPreviewModeChange('rendered')}
|
|
299
|
+
label={outputPreviewMode === 'rendered' ? 'Preview on' : 'Preview'}
|
|
300
|
+
/>
|
|
301
|
+
</>
|
|
302
|
+
)}
|
|
303
|
+
/>
|
|
304
|
+
```
|
|
143
305
|
|
|
144
306
|
## Theming
|
|
145
307
|
|
|
146
|
-
|
|
308
|
+
The theme object lets you style the editor container, input, toolbar, output panel, and button states.
|
|
147
309
|
|
|
148
310
|
```tsx
|
|
149
311
|
<RichTextInput
|
|
150
312
|
theme={{
|
|
151
313
|
colors: {
|
|
152
|
-
primary: '#
|
|
153
|
-
background: '#
|
|
154
|
-
text: '#
|
|
155
|
-
placeholder: '#
|
|
156
|
-
toolbarBackground: '#
|
|
157
|
-
toolbarBorder: '#
|
|
158
|
-
|
|
314
|
+
primary: '#0F766E',
|
|
315
|
+
background: '#FFFFFF',
|
|
316
|
+
text: '#0F172A',
|
|
317
|
+
placeholder: '#94A3B8',
|
|
318
|
+
toolbarBackground: '#F8FAFC',
|
|
319
|
+
toolbarBorder: '#CBD5E1',
|
|
320
|
+
link: '#0EA5E9',
|
|
321
|
+
cursor: '#0F766E',
|
|
159
322
|
},
|
|
160
323
|
containerStyle: {
|
|
161
324
|
borderRadius: 16,
|
|
162
|
-
borderWidth:
|
|
163
|
-
borderColor: '#
|
|
325
|
+
borderWidth: 1,
|
|
326
|
+
borderColor: '#CBD5E1',
|
|
327
|
+
},
|
|
328
|
+
outputContainerStyle: {
|
|
329
|
+
marginHorizontal: 12,
|
|
330
|
+
marginBottom: 12,
|
|
331
|
+
padding: 12,
|
|
332
|
+
borderRadius: 12,
|
|
333
|
+
backgroundColor: '#F8FAFC',
|
|
164
334
|
},
|
|
165
335
|
toolbarButtonActiveStyle: {
|
|
166
|
-
backgroundColor: '#
|
|
336
|
+
backgroundColor: '#CCFBF1',
|
|
167
337
|
},
|
|
168
338
|
}}
|
|
169
339
|
/>
|
|
170
340
|
```
|
|
171
341
|
|
|
172
|
-
|
|
342
|
+
Useful theme keys:
|
|
343
|
+
|
|
344
|
+
- `containerStyle`
|
|
345
|
+
- `inputStyle`
|
|
346
|
+
- `baseTextStyle`
|
|
347
|
+
- `outputContainerStyle`
|
|
348
|
+
- `outputLabelStyle`
|
|
349
|
+
- `outputTextStyle`
|
|
350
|
+
- `renderedOutputStyle`
|
|
351
|
+
- `toolbarStyle`
|
|
352
|
+
- `toolbarButtonStyle`
|
|
353
|
+
- `toolbarButtonActiveStyle`
|
|
354
|
+
- `toolbarButtonTextStyle`
|
|
355
|
+
- `toolbarButtonActiveTextStyle`
|
|
356
|
+
- `codeStyle`
|
|
357
|
+
- `colors.primary`
|
|
358
|
+
- `colors.link`
|
|
359
|
+
|
|
360
|
+
## Headless Hook
|
|
361
|
+
|
|
362
|
+
Use `useRichText` when you want to build your own editor UI.
|
|
173
363
|
|
|
174
364
|
```tsx
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
365
|
+
import React from 'react';
|
|
366
|
+
import { TextInput, View, Button } from 'react-native';
|
|
367
|
+
import { useRichText } from 'react-native-richify';
|
|
368
|
+
|
|
369
|
+
export function HeadlessEditor() {
|
|
370
|
+
const { state, actions } = useRichText({
|
|
371
|
+
onChangeText: (text) => console.log(text),
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
return (
|
|
375
|
+
<View>
|
|
376
|
+
<Button title="Bold" onPress={() => actions.toggleFormat('bold')} />
|
|
377
|
+
<Button title="Bullet" onPress={() => actions.setListType('bullet')} />
|
|
378
|
+
<TextInput
|
|
379
|
+
multiline
|
|
380
|
+
value={actions.getPlainText()}
|
|
381
|
+
onChangeText={actions.handleTextChange}
|
|
382
|
+
onSelectionChange={(event) =>
|
|
383
|
+
actions.handleSelectionChange(event.nativeEvent.selection)
|
|
384
|
+
}
|
|
385
|
+
style={{ minHeight: 120, borderWidth: 1, padding: 12 }}
|
|
386
|
+
/>
|
|
387
|
+
</View>
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## Context API
|
|
393
|
+
|
|
394
|
+
`RichTextProvider` and `useRichTextContext()` are for custom UIs built on the hook.
|
|
395
|
+
|
|
396
|
+
Important: the shipped `<RichTextInput />` manages its own state. Wrapping it in `RichTextProvider` does not automatically bind it to external context state.
|
|
397
|
+
|
|
398
|
+
Use context when you want to split a custom editor into multiple components:
|
|
399
|
+
|
|
400
|
+
```tsx
|
|
401
|
+
import React from 'react';
|
|
402
|
+
import { Button, TextInput, View } from 'react-native';
|
|
403
|
+
import {
|
|
404
|
+
RichTextProvider,
|
|
405
|
+
useRichTextContext,
|
|
406
|
+
} from 'react-native-richify';
|
|
407
|
+
|
|
408
|
+
function ToolbarRow() {
|
|
409
|
+
const { actions } = useRichTextContext();
|
|
410
|
+
return <Button title="Italic" onPress={() => actions.toggleFormat('italic')} />;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function EditorField() {
|
|
414
|
+
const { actions } = useRichTextContext();
|
|
415
|
+
|
|
416
|
+
return (
|
|
417
|
+
<TextInput
|
|
418
|
+
multiline
|
|
419
|
+
value={actions.getPlainText()}
|
|
420
|
+
onChangeText={actions.handleTextChange}
|
|
421
|
+
onSelectionChange={(event) =>
|
|
422
|
+
actions.handleSelectionChange(event.nativeEvent.selection)
|
|
423
|
+
}
|
|
424
|
+
style={{ minHeight: 120, borderWidth: 1, padding: 12 }}
|
|
425
|
+
/>
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export function SplitEditor() {
|
|
430
|
+
return (
|
|
431
|
+
<RichTextProvider>
|
|
432
|
+
<View>
|
|
433
|
+
<ToolbarRow />
|
|
434
|
+
<EditorField />
|
|
435
|
+
</View>
|
|
436
|
+
</RichTextProvider>
|
|
437
|
+
);
|
|
438
|
+
}
|
|
193
439
|
```
|
|
194
440
|
|
|
195
441
|
## Data Model
|
|
196
442
|
|
|
197
|
-
Content is stored as
|
|
443
|
+
Content is stored as segments:
|
|
198
444
|
|
|
199
|
-
```
|
|
200
|
-
|
|
445
|
+
```ts
|
|
446
|
+
type StyledSegment = {
|
|
201
447
|
text: string;
|
|
202
448
|
styles: {
|
|
203
449
|
bold?: boolean;
|
|
@@ -209,23 +455,40 @@ interface StyledSegment {
|
|
|
209
455
|
backgroundColor?: string;
|
|
210
456
|
fontSize?: number;
|
|
211
457
|
heading?: 'h1' | 'h2' | 'h3' | 'none';
|
|
458
|
+
listType?: 'bullet' | 'ordered' | 'none';
|
|
459
|
+
textAlign?: 'left' | 'center' | 'right';
|
|
460
|
+
link?: string;
|
|
212
461
|
};
|
|
213
|
-
}
|
|
462
|
+
};
|
|
214
463
|
```
|
|
215
464
|
|
|
216
|
-
|
|
465
|
+
This makes it easy to:
|
|
466
|
+
|
|
467
|
+
- store and restore content with `exportJSON()` and `importJSON()`
|
|
468
|
+
- inspect formatting state in your own UI
|
|
469
|
+
- serialize on demand to Markdown or HTML
|
|
470
|
+
|
|
471
|
+
Example:
|
|
472
|
+
|
|
473
|
+
```tsx
|
|
474
|
+
const saved = actions.exportJSON();
|
|
475
|
+
actions.importJSON(saved);
|
|
476
|
+
|
|
477
|
+
const markdown = actions.getOutput('markdown');
|
|
478
|
+
const html = actions.getOutput('html');
|
|
479
|
+
```
|
|
217
480
|
|
|
218
|
-
|
|
481
|
+
## Current Scope
|
|
219
482
|
|
|
220
|
-
|
|
221
|
-
2. **Top**: A transparent `<TextInput>` captures user input and selection
|
|
483
|
+
Stable built-in features documented above are the recommended way to use the library.
|
|
222
484
|
|
|
223
|
-
|
|
485
|
+
- Rich text formatting, headings, lists, links, alignment, theming, and output serialization are supported.
|
|
486
|
+
- The built-in toolbar does not currently expose image insertion.
|
|
224
487
|
|
|
225
488
|
## Contributing
|
|
226
489
|
|
|
227
|
-
See [
|
|
490
|
+
See [AGENTS.md](AGENTS.md) for contributor notes and repository workflow guidance.
|
|
228
491
|
|
|
229
492
|
## License
|
|
230
493
|
|
|
231
|
-
MIT
|
|
494
|
+
MIT
|
|
@@ -177,10 +177,6 @@ const RichTextInput = ({
|
|
|
177
177
|
inputRange: [0, 1],
|
|
178
178
|
outputRange: [0, 12]
|
|
179
179
|
}),
|
|
180
|
-
marginBottom: previewProgress.interpolate({
|
|
181
|
-
inputRange: [0, 1],
|
|
182
|
-
outputRange: [0, 16]
|
|
183
|
-
}),
|
|
184
180
|
transform: [{
|
|
185
181
|
translateY: previewProgress.interpolate({
|
|
186
182
|
inputRange: [0, 1],
|