react-native-enriched-markdown 0.7.0 → 0.7.2

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.
Files changed (37) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +215 -0
  3. package/app.plugin.js +2 -1
  4. package/docs/ACCESSIBILITY.md +172 -0
  5. package/docs/API_REFERENCE.md +948 -0
  6. package/docs/COPY_OPTIONS.md +127 -0
  7. package/docs/ELEMENTS_STRUCTURE.md +112 -0
  8. package/docs/IMAGE_CACHING.md +25 -0
  9. package/docs/INPUT.md +233 -0
  10. package/docs/LATEX_MATH.md +136 -0
  11. package/docs/MACOS.md +19 -0
  12. package/docs/MARKDOWN_STREAMING.md +36 -0
  13. package/docs/MENTIONS.md +97 -0
  14. package/docs/RTL.md +82 -0
  15. package/docs/STYLES.md +519 -0
  16. package/docs/TEXT.md +110 -0
  17. package/docs/WEB.md +49 -0
  18. package/package.json +7 -4
  19. package/plugin/build/withAndroidMath.js +19 -0
  20. package/plugin/build/withIosMath.js +28 -0
  21. package/plugin/build/withReactNativeEnrichedMarkdown.js +11 -0
  22. package/plugin/tsconfig.build.json +16 -0
  23. package/lib/module/plugin/withAndroidMath.js +0 -20
  24. package/lib/module/plugin/withAndroidMath.js.map +0 -1
  25. package/lib/module/plugin/withIosMath.js +0 -23
  26. package/lib/module/plugin/withIosMath.js.map +0 -1
  27. package/lib/module/plugin/withReactNativeEnrichedMarkdown.js +0 -16
  28. package/lib/module/plugin/withReactNativeEnrichedMarkdown.js.map +0 -1
  29. package/lib/typescript/src/plugin/withAndroidMath.d.ts +0 -5
  30. package/lib/typescript/src/plugin/withAndroidMath.d.ts.map +0 -1
  31. package/lib/typescript/src/plugin/withIosMath.d.ts +0 -5
  32. package/lib/typescript/src/plugin/withIosMath.d.ts.map +0 -1
  33. package/lib/typescript/src/plugin/withReactNativeEnrichedMarkdown.d.ts +0 -6
  34. package/lib/typescript/src/plugin/withReactNativeEnrichedMarkdown.d.ts.map +0 -1
  35. /package/{src/plugin → plugin/src}/withAndroidMath.ts +0 -0
  36. /package/{src/plugin → plugin/src}/withIosMath.ts +0 -0
  37. /package/{src/plugin → plugin/src}/withReactNativeEnrichedMarkdown.ts +0 -0
@@ -0,0 +1,948 @@
1
+ # API Reference
2
+
3
+ ## EnrichedMarkdownText
4
+
5
+ ### Props
6
+
7
+ ### `markdown`
8
+
9
+ The Markdown content to render.
10
+
11
+ | Type | Default Value | Platform |
12
+ | -------- | ------------- | -------- |
13
+ | `string` | Required | Both |
14
+
15
+ ### `markdownStyle`
16
+
17
+ Style configuration for Markdown elements. See the [Style Properties Reference](STYLES.md) for a detailed overview of all available style properties.
18
+
19
+ | Type | Default Value | Platform |
20
+ | ---------------- | ------------- | -------- |
21
+ | `MarkdownStyle` | `{}` | Both |
22
+
23
+ ### `containerStyle`
24
+
25
+ Style for the container view.
26
+
27
+ | Type | Default Value | Platform |
28
+ | ------------- | ------------- | -------- |
29
+ | `ViewStyle` | - | Both |
30
+
31
+ ### `onLinkPress`
32
+
33
+ Callback when a link is pressed. Access URL via `event.url`.
34
+
35
+ | Type | Default Value | Platform |
36
+ | --------------------------------------- | ------------- | -------- |
37
+ | `(event: LinkPressEvent) => void` | - | Both |
38
+
39
+ > **Note:** For handling long-press gestures on links, see [`onLinkLongPress`](#onlinklongpress). On iOS, providing `onLinkLongPress` automatically disables the system link preview.
40
+
41
+ **Example:**
42
+
43
+ ```tsx
44
+ <EnrichedMarkdownText
45
+ markdown="Check out [React Native](https://reactnative.dev)!"
46
+ onLinkPress={({ url }) => {
47
+ Alert.alert('Link pressed', url);
48
+ Linking.openURL(url);
49
+ }}
50
+ />
51
+ ```
52
+
53
+ ### `onLinkLongPress`
54
+
55
+ Callback when a link is long pressed. Access URL via `event.url`. On iOS, automatically disables the system link preview.
56
+
57
+ | Type | Default Value | Platform |
58
+ | -------------------------------------------- | ------------- | -------- |
59
+ | `(event: LinkLongPressEvent) => void` | - | Both |
60
+
61
+ **Example:**
62
+
63
+ ```tsx
64
+ <EnrichedMarkdownText
65
+ markdown="Check out [React Native](https://reactnative.dev)!"
66
+ onLinkLongPress={({ url }) => {
67
+ Alert.alert('Link long pressed', url);
68
+ }}
69
+ />
70
+ ```
71
+
72
+ ### `onTaskListItemPress`
73
+
74
+ Callback when a task list checkbox is tapped. Receives `index` (0-based), `checked` (new state after toggling), and `text` (item text).
75
+
76
+ | Type | Default Value | Platform |
77
+ | ----------------------------------------------- | ------------- | -------- |
78
+ | `(event: TaskListItemPressEvent) => void` | - | Both |
79
+
80
+ ### `enableLinkPreview`
81
+
82
+ Controls the native link preview on long press (iOS only). Automatically set to `false` when `onLinkLongPress` is provided.
83
+
84
+ | Type | Default Value | Platform |
85
+ | --------- | ------------- | -------- |
86
+ | `boolean` | `true` | iOS |
87
+
88
+ By default, long-pressing a link on iOS shows the native system link preview. When you provide `onLinkLongPress`, the system preview is automatically disabled so your handler can fire instead.
89
+
90
+ You can also control this behavior explicitly without providing a handler:
91
+
92
+ ```tsx
93
+ // Disable system link preview without providing a handler
94
+ <EnrichedMarkdownText
95
+ markdown={content}
96
+ enableLinkPreview={false}
97
+ />
98
+ ```
99
+
100
+ ### `selectable`
101
+
102
+ Whether text can be selected.
103
+
104
+ | Type | Default Value | Platform |
105
+ | --------- | ------------- | -------- |
106
+ | `boolean` | `true` | Both |
107
+
108
+ ### `selectionColor`
109
+
110
+ Color of the text selection highlight. On iOS, this also affects the caret and selection handle colors (they share a single tint). On macOS, only the selection background is affected. On Android, use `selectionHandleColor` to override the handle color independently.
111
+
112
+ | Type | Default Value | Platform |
113
+ | ------------ | ------------- | ------------------ |
114
+ | `ColorValue` | - | Both, macOS, Web |
115
+
116
+ ### `selectionHandleColor`
117
+
118
+ Color of the selection handles (drag anchors). No-op on Android API levels below 29.
119
+
120
+ | Type | Default Value | Platform |
121
+ | ------------ | ------------- | -------- |
122
+ | `ColorValue` | - | Android |
123
+
124
+ ### `md4cFlags`
125
+
126
+ Configuration for md4c parser extension flags.
127
+
128
+ | Type | Default Value | Platform |
129
+ | ------------- | ------------------------ | -------- |
130
+ | `Md4cFlags` | `{ underline: false, superscript: false, subscript: false, highlight: false, latexMath: true }` | Both |
131
+
132
+ **Properties:**
133
+
134
+ - **`underline`**: When `true`, treats `_text_` as underline instead of emphasis. When enabled, only `*text*` works for italic emphasis.
135
+ - **`superscript`**: When `true`, parses `^text^` as superscript. Visual appearance can be tuned with the `superscript` style prop — see [Superscript-specific](./STYLES.md#superscript-specific).
136
+ - **`subscript`**: When `true`, parses `~text~` as subscript. When disabled, single and double tildes remain strikethrough markers. Visual appearance can be tuned with the `subscript` style prop — see [Subscript-specific](./STYLES.md#subscript-specific).
137
+ - **`highlight`**: When `true`, parses `==text==` as highlighted spans. When disabled, double equals signs are treated as plain text. Visual appearance can be tuned with the `highlight` style prop — see [Highlight-specific](./STYLES.md#highlight-specific).
138
+ - **`latexMath`**: When `true`, parses `$...$` and `$$...$$` as LaTeX math spans.
139
+
140
+ **Example:**
141
+
142
+ ```tsx
143
+ // Default: _text_ is treated as italic
144
+ <EnrichedMarkdownText
145
+ markdown="This is _italic_ text"
146
+ />
147
+
148
+ // With underline enabled: _text_ is underlined, *text* is italic
149
+ <EnrichedMarkdownText
150
+ markdown="This is _underlined_ and *italic* text"
151
+ md4cFlags={{ underline: true }}
152
+ />
153
+ ```
154
+
155
+ ### `allowFontScaling`
156
+
157
+ Whether fonts should scale to respect Text Size accessibility settings.
158
+
159
+ | Type | Default Value | Platform |
160
+ | --------- | ------------- | -------- |
161
+ | `boolean` | `true` | Both |
162
+
163
+ ### `maxFontSizeMultiplier`
164
+
165
+ Maximum font scale multiplier when `allowFontScaling` is enabled.
166
+
167
+ | Type | Default Value | Platform |
168
+ | -------- | ------------- | -------- |
169
+ | `number` | `undefined` | Both |
170
+
171
+ ### `allowTrailingMargin`
172
+
173
+ Whether to preserve the bottom margin of the last block element.
174
+
175
+ | Type | Default Value | Platform |
176
+ | --------- | ------------- | -------- |
177
+ | `boolean` | `false` | Both |
178
+
179
+ ### `textBreakStrategy`
180
+
181
+ Controls how Android breaks lines within paragraphs. Mirrors the prop of the same name on React Native's core `Text`. The same value is used for both the measurement pass (`StaticLayout.Builder`) and the rendered `TextView`, so measured and rendered line counts stay in sync. Requires API 23+; ignored on older Android versions.
182
+
183
+ | Type | Default Value | Platform |
184
+ | --------------------------------------------- | --------------- | -------- |
185
+ | `'simple' \| 'highQuality' \| 'balanced'` | `'highQuality'` | Android |
186
+
187
+ - **`'simple'`**: greedy, no hyphenation; cheapest.
188
+ - **`'highQuality'`** (default): full paragraph optimization with hyphenation.
189
+ - **`'balanced'`**: balances line lengths across the paragraph; no hyphenation.
190
+
191
+ ### `lineBreakStrategyIOS`
192
+
193
+ Controls iOS line-breaking refinements. Mirrors the prop of the same name on React Native's core `Text`. Maps to `NSParagraphStyle.lineBreakStrategy`. Requires iOS 14+; on earlier versions the prop is ignored.
194
+
195
+ | Type | Default Value | Platform |
196
+ | ---------------------------------------------------------- | ------------- | -------- |
197
+ | `'none' \| 'standard' \| 'hangul-word' \| 'push-out'` | `'none'` | iOS |
198
+
199
+ - **`'none'`** (default): no additional line-break strategy.
200
+ - **`'standard'`**: enables the system's standard line-break refinements.
201
+ - **`'hangul-word'`**: prefers breaking at Korean word boundaries.
202
+ - **`'push-out'`**: avoids orphaned short trailing lines by pushing words to the next line.
203
+
204
+ ### `writingDirection`
205
+
206
+ Paragraph writing direction. iOS only — Android already resolves direction per paragraph via the platform Bidi heuristic and is unaffected by this prop.
207
+
208
+ | Type | Default Value | Platform |
209
+ | ----------------------------------------------- | ---------------- | -------- |
210
+ | `'auto' \| 'ltr' \| 'rtl' \| 'first-strong'` | `'first-strong'` | iOS |
211
+
212
+ - **`'first-strong'`** (default): library extension. Each paragraph resolves its base direction from its first strong directional character — mixed Arabic/Hebrew/English documents render correctly out of the box. Paragraphs with no strong character (numbers, punctuation, block spacers) fall back to the view's Yoga-resolved layout direction, which inherits from any ancestor `<View style={{ direction: 'rtl' }}>` (defaults to `I18nManager.isRTL`). Mirrors Android's `TEXT_DIRECTION_FIRST_STRONG`.
213
+ - **`'auto'`**: React Native parity (matches `<Text writingDirection="auto">`). TextKit follows the app's `userInterfaceLayoutDirection`; mixed-direction paragraphs do not auto-resolve.
214
+ - **`'ltr'` / `'rtl'`**: forces the base direction on every paragraph in the document.
215
+
216
+ Code blocks are always rendered left-to-right regardless of this prop. Per-paragraph direction also drives list markers, blockquote borders, task-list tap targets, and the `dir` attribute emitted when copying as HTML. See [RTL Support](RTL.md) for the full behavior matrix and the copy-as-HTML caveat for mixed-direction documents.
217
+
218
+ **Example:**
219
+
220
+ ```tsx
221
+ // Mixed-direction document — each paragraph picks its own side.
222
+ <EnrichedMarkdownText
223
+ markdown={
224
+ 'هذه فقرة عربية\n\n' +
225
+ 'This English paragraph stays LTR.\n\n' +
226
+ '123 456 789.' // neutral — follows the view's layout direction
227
+ }
228
+ />
229
+
230
+ // Force RTL on every paragraph regardless of content.
231
+ <EnrichedMarkdownText writingDirection="rtl" markdown={content} />
232
+ ```
233
+
234
+ ### `flavor`
235
+
236
+ Markdown flavor. Set to `'github'` to enable GitHub Flavored Markdown table support.
237
+
238
+ | Type | Default Value | Platform |
239
+ | --------------------------------- | --------------- | -------- |
240
+ | `'commonmark' \| 'github'` | `'commonmark'` | Both |
241
+
242
+ > **Note:**
243
+ > - **`'commonmark'`**: All Markdown content is rendered as a single TextView. Selecting text will select all content in the view.
244
+ > - **`'github'`**: The Markdown AST is split into segments. Consecutive text blocks (paragraphs, headings, lists, etc.) are grouped into separate TextView segments, while tables are rendered as separate table views. This allows for granular text selection within each segment and enables interactive table features (horizontal scrolling, context menus). Text selection cannot span across segments.
245
+
246
+ ### `streamingAnimation`
247
+
248
+ When `true`, newly appended content fades in during streaming updates. Only the tail (new characters beyond the previous content) is animated. Recommended for LLM streaming use cases.
249
+
250
+ | Type | Default Value | Platform |
251
+ | --------- | ------------- | -------- |
252
+ | `boolean` | `false` | Both |
253
+
254
+ ### `streamingConfig`
255
+
256
+ Configuration for streaming behavior. Currently controls how incomplete tables are handled during streaming with `flavor="github"`.
257
+
258
+ | Type | Default Value | Platform |
259
+ | ----------------------- | ------------------------ | -------- |
260
+ | `{ tableMode: string }` | `{ tableMode: 'progressive' }` | Both |
261
+
262
+ #### `tableMode`
263
+
264
+ Controls how incomplete (still-streaming) tables are rendered:
265
+
266
+ - **`'progressive'`** (default): The table is rendered row-by-row as content arrives. Requires at least a header row and separator line before anything is shown. Incomplete trailing rows (missing closing `|` or fewer columns than the header) are trimmed. New rows fade in with animation when `streamingAnimation` is also enabled.
267
+ - **`'hidden'`**: The entire table is hidden until it is complete (followed by a blank line). This prevents visual jank from partially formed tables.
268
+
269
+ ```tsx
270
+ <EnrichedMarkdownText
271
+ markdown={streamingMarkdown}
272
+ flavor="github"
273
+ streamingAnimation
274
+ streamingConfig={{ tableMode: 'hidden' }}
275
+ />
276
+ ```
277
+
278
+ ### `spoilerOverlay`
279
+
280
+ Controls how spoiler text (`||hidden text||`) is displayed before being revealed.
281
+
282
+ | Type | Default Value | Platform |
283
+ | ----------------------------- | ------------- | -------- |
284
+ | `'particles' \| 'solid'` | `'particles'` | Both |
285
+
286
+ - **`'particles'`**: Animated particle overlay (CAEmitterLayer on iOS, Choreographer-driven Canvas particles on Android).
287
+ - **`'solid'`**: Opaque rectangle covering the text (Discord-style).
288
+
289
+ Both modes support tap-to-reveal.
290
+
291
+ ### `contextMenuItems`
292
+
293
+ Custom items to add to the text selection context menu. Items appear before the system actions (Copy, etc.). Items with `visible: false` are hidden from the menu.
294
+
295
+ > **iOS**: Requires iOS 16+. On earlier versions the prop is ignored.
296
+
297
+ | Type | Default Value | Platform |
298
+ | -------------------- | ------------- | -------- |
299
+ | `ContextMenuItem[]` | - | Both |
300
+
301
+ **`ContextMenuItem` shape:**
302
+
303
+ ```ts
304
+ interface ContextMenuItem {
305
+ /** Label shown in the context menu. */
306
+ text: string;
307
+ /**
308
+ * SF Symbol name for the icon shown next to the item label.
309
+ * Supported on iOS and macOS. Ignored on Android.
310
+ * Example: 'sparkles', 'translate', 'doc.text'
311
+ */
312
+ icon?: string;
313
+ /** Called when the item is tapped. */
314
+ onPress: (event: {
315
+ /** The selected text at the time of the press. */
316
+ text: string;
317
+ /** Absolute character range of the selection within the full content. */
318
+ selection: { start: number; end: number };
319
+ }) => void;
320
+ /** When false, the item is not shown in the menu. Defaults to true. */
321
+ visible?: boolean;
322
+ }
323
+ ```
324
+
325
+ **Example:**
326
+
327
+ ```tsx
328
+ <EnrichedMarkdownText
329
+ markdown={content}
330
+ contextMenuItems={[
331
+ {
332
+ text: 'Summarize with AI',
333
+ onPress: ({ text }) => {
334
+ console.log('Selected:', text);
335
+ },
336
+ },
337
+ {
338
+ text: 'Translate',
339
+ onPress: ({ text }) => {
340
+ translate(text);
341
+ },
342
+ },
343
+ ]}
344
+ />
345
+ ```
346
+
347
+ ### `selectionMenuConfig`
348
+
349
+ Controls built-in actions added to the native text selection menu. Custom app-provided actions are controlled separately with `contextMenuItems`.
350
+
351
+ | Type | Default Value | Platform |
352
+ | -------------------- | ---------------------------------------------- | -------- |
353
+ | `SelectionMenuConfig` | `{}` (see shape below for per-field defaults) | iOS, Android, macOS |
354
+
355
+ Each item takes an object: `{ enabled }` toggles visibility (the system `copy` item can't be hidden — only relabeled) and `label` overrides the English default. The labels apply to the main text selection menu as well as the table and math block copy menus.
356
+
357
+ > **Deprecation:** the previous boolean shape (`copyAsMarkdown: false`) is still accepted at runtime for backward compatibility but logs a one-time warning. It will be removed in 0.8 — migrate to `{ enabled: false }`.
358
+
359
+ **`SelectionMenuConfig` shape:**
360
+
361
+ ```ts
362
+ interface SelectionMenuConfig {
363
+ /** System "Copy" item — can't be hidden, only relabeled. @default { label: "Copy" } */
364
+ copy?: { label?: string };
365
+ /** "Copy as Markdown" action. @default { enabled: true, label: "Copy as Markdown" } */
366
+ copyAsMarkdown?: { enabled?: boolean; label?: string };
367
+ /** "Copy Image URL" action, shown when the selection contains images. */
368
+ copyImageUrl?: {
369
+ enabled?: boolean;
370
+ /** Label for a single image. @default "Copy Image URL" */
371
+ label?: string;
372
+ /** Forms for multiple images, chosen with Intl.PluralRules. @default { other: "Copy {count} Image URLs" } */
373
+ pluralLabels?: SelectionMenuPluralLabels;
374
+ };
375
+ }
376
+
377
+ interface SelectionMenuPluralLabels {
378
+ /** CLDR plural categories. `{count}` is replaced by the image count. Missing
379
+ * categories fall back to `other`, so only `other` is required. */
380
+ other: string;
381
+ zero?: string;
382
+ one?: string;
383
+ two?: string;
384
+ few?: string;
385
+ many?: string;
386
+ }
387
+ ```
388
+
389
+ **Example:**
390
+
391
+ ```tsx
392
+ <EnrichedMarkdownText
393
+ markdown={content}
394
+ selectionMenuConfig={{
395
+ // Hide an action:
396
+ copyAsMarkdown: { enabled: false },
397
+ // Localize the labels:
398
+ copy: { label: t('copy') },
399
+ copyImageUrl: {
400
+ label: t('copyImageUrl'),
401
+ pluralLabels: { other: t('copyImageUrls') }, // "{count}" → image count
402
+ },
403
+ }}
404
+ />
405
+ ```
406
+
407
+ See [COPY_OPTIONS.md](./COPY_OPTIONS.md#localizing-menu-labels) for details.
408
+
409
+ > **Note:** When using `flavor="github"`, `selection.start` and `selection.end` are relative to the text segment the selection is in, not the full markdown string. With `flavor="commonmark"` (default) they are always absolute within the full rendered text.
410
+
411
+ ---
412
+
413
+ ### `accessibilityLabels`
414
+
415
+ Translates every string spoken by VoiceOver (iOS) and TalkBack (Android) when navigating the rendered markdown. All fields are optional; omitted fields fall back to the English defaults defined in `accessibilityLabelDefaults.ts`. See the [Accessibility guide](ACCESSIBILITY.md#translating-announcements--accessibilitylabels) for the full defaults table and placeholder syntax.
416
+
417
+ | Type | Default Value | Platform |
418
+ | --------------------- | ---------------------------- | --------------- |
419
+ | `AccessibilityLabels` | English strings (see guide) | iOS, Android |
420
+
421
+ **`AccessibilityLabels` shape:**
422
+
423
+ ```ts
424
+ interface AccessibilityLabels {
425
+ list?: {
426
+ bulletPoint?: string; // "Bullet point"
427
+ nestedBulletPoint?: string; // "Nested bullet point"
428
+ orderedItem?: string; // "List item {n}"
429
+ nestedOrderedItem?: string; // "Nested list item {n}"
430
+ };
431
+ blockquote?: {
432
+ quote?: string; // "Blockquote"
433
+ nestedQuote?: string; // "Nested blockquote"
434
+ };
435
+ table?: {
436
+ row?: string; // "Row {n}: {content}"
437
+ };
438
+ math?: {
439
+ equation?: string; // "Math: {latex}"
440
+ };
441
+ rotor?: { // iOS only
442
+ headings?: string; // "Headings"
443
+ links?: string; // "Links"
444
+ images?: string; // "Images"
445
+ };
446
+ }
447
+ ```
448
+
449
+ Placeholders (`{n}`, `{content}`, `{latex}`) are substituted on the native side at speak time and must be preserved in translations.
450
+
451
+ **Example:**
452
+
453
+ ```tsx
454
+ <EnrichedMarkdownText
455
+ markdown={content}
456
+ accessibilityLabels={{
457
+ list: { bulletPoint: 'Punkt', orderedItem: 'Element {n}' },
458
+ blockquote: { quote: 'Zitat' },
459
+ math: { equation: 'Formel: {latex}' },
460
+ }}
461
+ />
462
+ ```
463
+
464
+ ---
465
+
466
+ ## EnrichedMarkdownTextInput
467
+
468
+ ### Props
469
+
470
+ ### `defaultValue`
471
+
472
+ Initial Markdown content for the input. The Markdown is parsed and formatting is applied on mount.
473
+
474
+ | Type | Default Value | Platform |
475
+ | -------- | ------------- | -------- |
476
+ | `string` | - | Both |
477
+
478
+ ### `placeholder`
479
+
480
+ Placeholder text displayed when the input is empty.
481
+
482
+ | Type | Default Value | Platform |
483
+ | -------- | ------------- | -------- |
484
+ | `string` | - | Both |
485
+
486
+ ### `placeholderTextColor`
487
+
488
+ Color of the placeholder text.
489
+
490
+ | Type | Default Value | Platform |
491
+ | ------------ | ------------- | -------- |
492
+ | `ColorValue` | - | Both |
493
+
494
+ ### `editable`
495
+
496
+ Whether the input is editable.
497
+
498
+ | Type | Default Value | Platform |
499
+ | --------- | ------------- | -------- |
500
+ | `boolean` | `true` | Both |
501
+
502
+ ### `autoFocus`
503
+
504
+ Whether the input should be focused on mount.
505
+
506
+ | Type | Default Value | Platform |
507
+ | --------- | ------------- | -------- |
508
+ | `boolean` | `false` | Both |
509
+
510
+ ### `scrollEnabled`
511
+
512
+ Whether the input is scrollable when content exceeds the visible area.
513
+
514
+ | Type | Default Value | Platform |
515
+ | --------- | ------------- | -------- |
516
+ | `boolean` | `true` | Both |
517
+
518
+ ### `autoCapitalize`
519
+
520
+ Auto-capitalization behavior.
521
+
522
+ | Type | Default Value | Platform |
523
+ | -------- | -------------- | -------- |
524
+ | `string` | `'sentences'` | Both |
525
+
526
+ ### `multiline`
527
+
528
+ Whether the input supports multiple lines.
529
+
530
+ | Type | Default Value | Platform |
531
+ | --------- | ------------- | -------- |
532
+ | `boolean` | `true` | Both |
533
+
534
+ ### `cursorColor`
535
+
536
+ Color of the text cursor.
537
+
538
+ | Type | Default Value | Platform |
539
+ | ------------ | ------------- | -------- |
540
+ | `ColorValue` | - | Both |
541
+
542
+ ### `selectionColor`
543
+
544
+ Color of the text selection highlight.
545
+
546
+ | Type | Default Value | Platform |
547
+ | ------------ | ------------- | -------- |
548
+ | `ColorValue` | - | Both |
549
+
550
+ ### `markdownStyle`
551
+
552
+ Style configuration for formatted text in the input.
553
+
554
+ | Type | Default Value | Platform |
555
+ | -------------------- | ------------- | -------- |
556
+ | `MarkdownTextInputStyle` | `{}` | Both |
557
+
558
+ **Properties:**
559
+
560
+ - `strong.color` — text color for bold text (defaults to the input's text color).
561
+ - `em.color` — text color for italic text (defaults to the input's text color).
562
+ - `link.color` — text color for links (defaults to `#2563EB`).
563
+ - `link.underline` — whether links are underlined (defaults to `true`).
564
+ - `link.backgroundColor` — background color for links (defaults to `transparent`).
565
+ - `linkVariants` — per-URL-pattern style overrides. Each key is a regex tested against the link URL. See [Mentions — Link Variants](MENTIONS.md#link-variants-mention-styling).
566
+ - `spoiler.color` — text color for spoiler text.
567
+ - `spoiler.backgroundColor` — background color for spoiler text.
568
+
569
+ ### `mentionIndicators`
570
+
571
+ List of trigger strings that start a mention flow (e.g. `['@', '#']`). See [Mentions](MENTIONS.md).
572
+
573
+ | Type | Default Value | Platform |
574
+ | ---------- | ------------- | -------- |
575
+ | `string[]` | `[]` | Both |
576
+
577
+ ### `style`
578
+
579
+ Style for the input view. Accepts `ViewStyle` and `TextStyle` properties (e.g., `fontSize`, `color`, `padding`).
580
+
581
+ | Type | Default Value | Platform |
582
+ | ----------------------- | ------------- | -------- |
583
+ | `ViewStyle \| TextStyle` | - | Both |
584
+
585
+ ### Events
586
+
587
+ ### `onChangeText`
588
+
589
+ Fires when the plain text content changes. Returns the text without Markdown syntax.
590
+
591
+ | Type | Default Value | Platform |
592
+ | ------------------------------- | ------------- | -------- |
593
+ | `(text: string) => void` | - | Both |
594
+
595
+ ### `onChangeMarkdown`
596
+
597
+ Fires when the Markdown representation changes. Returns the full Markdown string. Only active when the callback is provided — omitting it skips the serialization for better performance.
598
+
599
+ | Type | Default Value | Platform |
600
+ | ----------------------------------- | ------------- | -------- |
601
+ | `(markdown: string) => void` | - | Both |
602
+
603
+ ### `onChangeSelection`
604
+
605
+ Fires when the text selection changes.
606
+
607
+ | Type | Default Value | Platform |
608
+ | ----------------------------------------------------- | ------------- | -------- |
609
+ | `(selection: { start: number; end: number }) => void` | - | Both |
610
+
611
+ ### `onChangeState`
612
+
613
+ Fires when the active style state changes. The payload provides a nested object for each style with an `isActive` property.
614
+
615
+ | Type | Default Value | Platform |
616
+ | --------------------------------- | ------------- | -------- |
617
+ | `(state: StyleState) => void` | - | Both |
618
+
619
+ **`StyleState` shape:**
620
+
621
+ ```ts
622
+ interface StyleState {
623
+ bold: { isActive: boolean };
624
+ italic: { isActive: boolean };
625
+ underline: { isActive: boolean };
626
+ strikethrough: { isActive: boolean };
627
+ spoiler: { isActive: boolean };
628
+ link: { isActive: boolean };
629
+ }
630
+ ```
631
+
632
+ ### `onCaretRectChange`
633
+
634
+ Fires when the caret's pixel position changes (typing, selection change, content reflow). The rect is relative to the input's top-left corner, in density-independent pixels. The native side diffs the rect before emitting, so redundant events are suppressed.
635
+
636
+ | Type | Default Value | Platform |
637
+ | --------------------------------- | ------------- | -------- |
638
+ | `(rect: CaretRect) => void` | - | Both |
639
+
640
+ **`CaretRect` shape:**
641
+
642
+ ```ts
643
+ interface CaretRect {
644
+ x: number;
645
+ y: number;
646
+ width: number;
647
+ height: number;
648
+ }
649
+ ```
650
+
651
+ All values are in density-independent pixels, relative to the input's top-left corner.
652
+
653
+ **Example:**
654
+
655
+ ```tsx
656
+ <EnrichedMarkdownTextInput
657
+ scrollEnabled={false}
658
+ onCaretRectChange={(rect) => {
659
+ console.log('Caret at:', rect.x, rect.y);
660
+ }}
661
+ />
662
+ ```
663
+
664
+ ### `onFocus`
665
+
666
+ Fires when the input gains focus.
667
+
668
+ | Type | Default Value | Platform |
669
+ | -------------- | ------------- | -------- |
670
+ | `() => void` | - | Both |
671
+
672
+ ### `onBlur`
673
+
674
+ Fires when the input loses focus.
675
+
676
+ | Type | Default Value | Platform |
677
+ | -------------- | ------------- | -------- |
678
+ | `() => void` | - | Both |
679
+
680
+ ### `onStartMention`
681
+
682
+ Fires when a new mention flow starts. See [Mentions](MENTIONS.md#events).
683
+
684
+ | Type | Default Value | Platform |
685
+ | ---- | ------------- | -------- |
686
+ | `(event: { indicator: string }) => void` | - | Both |
687
+
688
+ ### `onChangeMention`
689
+
690
+ Fires on every keystroke while a mention flow is active.
691
+
692
+ | Type | Default Value | Platform |
693
+ | ---- | ------------- | -------- |
694
+ | `(event: { indicator: string; text: string }) => void` | - | Both |
695
+
696
+ ### `onEndMention`
697
+
698
+ Fires when the active mention flow ends.
699
+
700
+ | Type | Default Value | Platform |
701
+ | ---- | ------------- | -------- |
702
+ | `(event: { indicator: string }) => void` | - | Both |
703
+
704
+ ### `writingDirection`
705
+
706
+ Paragraph writing direction in the input. iOS only — Android's `EditText` already resolves direction per paragraph via `TEXT_DIRECTION_FIRST_STRONG` and is unaffected by this prop.
707
+
708
+ | Type | Default Value | Platform |
709
+ | ----------------------------------------------- | ---------------- | -------- |
710
+ | `'auto' \| 'ltr' \| 'rtl' \| 'first-strong'` | `'first-strong'` | iOS |
711
+
712
+ - **`'first-strong'`** (default): each paragraph resolves its base direction from its first strong directional character. Neutral-only paragraphs fall back to the view's Yoga-resolved layout direction. Mirrors Android's platform behavior.
713
+ - **`'auto'`**: React Native parity. TextKit follows the app's `userInterfaceLayoutDirection`; mixed-direction paragraphs do not auto-resolve.
714
+ - **`'ltr'` / `'rtl'`**: forces the base direction on every paragraph in the input.
715
+
716
+ See [INPUT — RTL Support](INPUT.md#rtl-support) for caveats (placeholder direction, mixed-paragraph typing).
717
+
718
+ ### `contextMenuItems`
719
+
720
+ Custom items to add to the text selection context menu. Items appear before the system actions (Copy, Cut, etc.). Items with `visible: false` are hidden from the menu.
721
+
722
+ > **iOS**: Requires iOS 16+. On earlier versions the prop is ignored.
723
+
724
+ | Type | Default Value | Platform |
725
+ | -------------------- | ------------- | -------- |
726
+ | `ContextMenuItem[]` | - | Both |
727
+
728
+ **`ContextMenuItem` shape:**
729
+
730
+ ```ts
731
+ interface ContextMenuItem {
732
+ /** Label shown in the context menu. */
733
+ text: string;
734
+ /**
735
+ * SF Symbol name for the icon shown next to the item label.
736
+ * Supported on iOS and macOS. Ignored on Android.
737
+ * Example: 'sparkles', 'translate', 'doc.text'
738
+ */
739
+ icon?: string;
740
+ /** Called when the item is tapped. */
741
+ onPress: (event: {
742
+ /** The selected text at the time of the press. */
743
+ text: string;
744
+ /** Absolute character range of the selection within the full content. */
745
+ selection: { start: number; end: number };
746
+ /** Active formatting styles at the time of the press. */
747
+ styleState: {
748
+ bold: { isActive: boolean };
749
+ italic: { isActive: boolean };
750
+ underline: { isActive: boolean };
751
+ strikethrough: { isActive: boolean };
752
+ spoiler: { isActive: boolean };
753
+ link: { isActive: boolean };
754
+ };
755
+ }) => void;
756
+ /** When false, the item is not shown in the menu. Defaults to true. */
757
+ visible?: boolean;
758
+ }
759
+ ```
760
+
761
+ **Example:**
762
+
763
+ ```tsx
764
+ <EnrichedMarkdownTextInput
765
+ contextMenuItems={[
766
+ {
767
+ text: 'Summarize with AI',
768
+ onPress: ({ text, styleState }) => {
769
+ console.log('Selected:', text, 'Bold:', styleState.bold.isActive);
770
+ },
771
+ },
772
+ ]}
773
+ />
774
+ ```
775
+
776
+ ### `selectionMenuConfig`
777
+
778
+ Controls built-in items in the text selection context menu — the **Format** submenu (Bold, Italic, …) and the **Copy as Markdown** action. Each item takes an object: `{ enabled }` toggles visibility and `label` overrides the English default; `format.label` controls the submenu title itself. Custom app-provided actions are controlled separately with `contextMenuItems`.
779
+
780
+ | Type | Default Value | Platform |
781
+ | -------------------------- | -------------------------------------- | ------------------- |
782
+ | `InputSelectionMenuConfig` | `{}` (see shape below for per-field defaults) | iOS, Android, macOS |
783
+
784
+ **`InputSelectionMenuConfig` shape:**
785
+
786
+ ```ts
787
+ interface InputSelectionMenuConfig {
788
+ /** The "Format" submenu. @default { enabled: true, label: "Format" } */
789
+ format?: { enabled?: boolean; label?: string };
790
+ /** "Copy as Markdown" action. @default { enabled: true, label: "Copy as Markdown" } */
791
+ copyAsMarkdown?: { enabled?: boolean; label?: string };
792
+ }
793
+ ```
794
+
795
+ **Example:**
796
+
797
+ ```tsx
798
+ // Hide both the Format submenu and the Copy as Markdown action
799
+ <EnrichedMarkdownTextInput
800
+ selectionMenuConfig={{
801
+ format: { enabled: false },
802
+ copyAsMarkdown: { enabled: false },
803
+ }}
804
+ />
805
+
806
+ // Localize the visible labels
807
+ <EnrichedMarkdownTextInput
808
+ selectionMenuConfig={{
809
+ format: { label: t('format') },
810
+ copyAsMarkdown: { label: t('copyAsMarkdown') },
811
+ }}
812
+ />
813
+ ```
814
+
815
+ See [COPY_OPTIONS.md](./COPY_OPTIONS.md#localizing-menu-labels) for details on the localization pattern.
816
+
817
+ ### `formatMenuConfig`
818
+
819
+ Controls which items appear inside the Format submenu and the label for each. Only effective when `selectionMenuConfig.format` is enabled (the default). Same `{ enabled?, label? }` shape as `selectionMenuConfig` above.
820
+
821
+ | Type | Default Value | Platform |
822
+ | ------------------ | ---------------------------------------------- | ------------------- |
823
+ | `FormatMenuConfig` | `{}` (see shape below for per-field defaults) | iOS, Android, macOS |
824
+
825
+ **`FormatMenuConfig` shape:**
826
+
827
+ ```ts
828
+ interface FormatMenuConfig {
829
+ /** @default { enabled: true, label: "Bold" } */
830
+ bold?: { enabled?: boolean; label?: string };
831
+ /** @default { enabled: true, label: "Italic" } */
832
+ italic?: { enabled?: boolean; label?: string };
833
+ /** @default { enabled: true, label: "Underline" } */
834
+ underline?: { enabled?: boolean; label?: string };
835
+ /** @default { enabled: true, label: "Strikethrough" } */
836
+ strikethrough?: { enabled?: boolean; label?: string };
837
+ /** @default { enabled: true, label: "Spoiler" } */
838
+ spoiler?: { enabled?: boolean; label?: string };
839
+ /** @default { enabled: true, label: "Link" } */
840
+ link?: { enabled?: boolean; label?: string };
841
+ }
842
+ ```
843
+
844
+ **Example:**
845
+
846
+ ```tsx
847
+ // Hide Spoiler and Link from the Format submenu
848
+ <EnrichedMarkdownTextInput
849
+ formatMenuConfig={{
850
+ spoiler: { enabled: false },
851
+ link: { enabled: false },
852
+ }}
853
+ />
854
+
855
+ // Localize every item
856
+ <EnrichedMarkdownTextInput
857
+ formatMenuConfig={{
858
+ bold: { label: t('bold') },
859
+ italic: { label: t('italic') },
860
+ underline: { label: t('underline') },
861
+ strikethrough: { label: t('strikethrough') },
862
+ spoiler: { label: t('spoiler') },
863
+ link: { label: t('link') },
864
+ }}
865
+ />
866
+ ```
867
+
868
+ > System **Cut / Copy / Paste / Select All** items come from the platform (UIKit `UITextView`, Android `ActionMode`) and are already localized by the device language — they are not exposed through `selectionMenuConfig`.
869
+
870
+ ### Ref Methods
871
+
872
+ All methods are called imperatively on the ref (`ref.current?.methodName()`).
873
+
874
+ ### `focus()`
875
+
876
+ Focuses the input.
877
+
878
+ ### `blur()`
879
+
880
+ Blurs the input.
881
+
882
+ ### `setValue(markdown: string)`
883
+
884
+ Sets the input content from a Markdown string. Parses the Markdown and applies formatting.
885
+
886
+ ### `getMarkdown(): Promise<string>`
887
+
888
+ Returns a Promise that resolves with the current Markdown content. The async nature is due to the native bridge — the request is sent to the native side and the result is returned via an event.
889
+
890
+ ### `getCaretRect(): Promise<CaretRect>`
891
+
892
+ Returns a Promise that resolves with the current caret's pixel position relative to the input. Useful for one-off queries; for continuous tracking, prefer `onCaretRectChange`.
893
+
894
+ ### `setSelection(start: number, end: number)`
895
+
896
+ Sets the text selection range.
897
+
898
+ ### `toggleBold()`
899
+
900
+ Toggles bold on the current selection. When no text is selected, the style is queued and applied to the next characters typed.
901
+
902
+ ### `toggleItalic()`
903
+
904
+ Toggles italic on the current selection or cursor.
905
+
906
+ ### `toggleUnderline()`
907
+
908
+ Toggles underline on the current selection or cursor.
909
+
910
+ ### `toggleStrikethrough()`
911
+
912
+ Toggles strikethrough on the current selection or cursor.
913
+
914
+ ### `toggleSpoiler()`
915
+
916
+ Toggles spoiler on the current selection or cursor.
917
+
918
+ ### `setLink(url: string)`
919
+
920
+ Applies a link URL to the currently selected text.
921
+
922
+ ### `insertLink(text: string, url: string)`
923
+
924
+ Inserts a link with the given text and URL at the current cursor position. Useful when there is no text selection.
925
+
926
+ ### `removeLink()`
927
+
928
+ Removes the link from the current selection.
929
+
930
+ ### `copyToClipboard()`
931
+
932
+ Copies the input's full content to the system clipboard, matching the result of selecting all text and pressing the context menu's copy action. The selection is left unchanged, and calling it on an empty input is a no-op.
933
+
934
+ On iOS and macOS the clipboard receives both plain text and a private Markdown pasteboard type, so pasting back into an `EnrichedMarkdownTextInput` restores the formatting; external apps receive plain text only. On Android the clipboard receives plain text only — inline styles are not preserved for any paste target.
935
+
936
+ ### `startMention(indicator: string)`
937
+
938
+ Programmatically triggers a mention flow by inserting the indicator character at the current cursor position. The indicator must be listed in the `mentionIndicators` prop. Useful for toolbar buttons.
939
+
940
+ ### `insertMention(displayText: string, url: string)`
941
+
942
+ Replaces the active mention token with a formatted link. Only works when a mention flow is active. The mention is serialized as `[displayText](url)` in Markdown output.
943
+
944
+ ---
945
+
946
+ ## Mentions
947
+
948
+ For full documentation on the mention system — setup, events, styling, and best practices — see [Mentions](MENTIONS.md).