react-native-nitro-markdown 0.4.0 → 0.4.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.
package/README.md CHANGED
@@ -3,77 +3,68 @@
3
3
  <img src="./readme/stream-demo.gif" alt="react-native-nitro-markdown stream demo" width="300" />
4
4
  </p>
5
5
 
6
- # react-native-nitro-markdown 🚀
6
+ # react-native-nitro-markdown
7
7
 
8
- > The fastest Markdown parser for React Native. Period.
9
-
10
- [![npm version](https://img.shields.io/npm/v/react-native-nitro-markdown?style=flat-square)](https://www.npmjs.com/package/react-native-nitro-markdown)
11
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
12
- [![Nitro Modules](https://img.shields.io/badge/Powered%20by-Nitro%20Modules-blueviolet?style=flat-square)](https://nitro.margelo.com)
13
-
14
- **react-native-nitro-markdown** is a high-performance Markdown parser built on **[md4c](https://github.com/mity/md4c)** (C++) and **[Nitro Modules](https://nitro.margelo.com)**. It parses complex Markdown, GFM, and LaTeX Math into a structured AST **synchronously** via JSI, bypassing the React Native Bridge entirely.
8
+ Fast, native Markdown parsing and rendering for React Native. Built on md4c (C++) and Nitro Modules (JSI), it turns Markdown into a typed AST synchronously and renders it with a batteries-included React Native renderer.
15
9
 
16
10
  ---
17
11
 
18
- ## Why Nitro? (Benchmarks)
12
+ ## Why this package exists
19
13
 
20
- We benchmarked this library against the most popular JavaScript parsers on a real mobile device (iPhone 15 Pro, Release Mode) using a heavy **237KB** Markdown document.
14
+ JavaScript Markdown parsers do a lot of work on the JS thread and often trigger GC pauses on large documents. This package moves parsing to native C++ and uses JSI to return the AST without going through the RN bridge.
21
15
 
22
- | Parser | Time (ms) | Speedup | Frame Drops (60fps) |
23
- | :-------------------------- | :--------- | :---------------- | :-------------------- |
24
- | **🚀 Nitro Markdown (C++)** | **~29 ms** | **1x (Baseline)** | **~1 frame** (Smooth) |
25
- | 📋 CommonMark (JS) | ~82 ms | 2.8x slower | ~5 frames (Jank) |
26
- | 🏗️ Markdown-It (JS) | ~118 ms | 4.0x slower | ~7 frames (Jank) |
27
- | 💨 Marked (JS) | ~400 ms | 13.5x slower | ~24 frames (Freeze) |
16
+ **How it works:**
28
17
 
29
- > **Takeaway:** JavaScript parsers trigger Garbage Collection pauses. Nitro uses C++ to parse efficiently with zero-copy overhead, keeping your UI thread responsive.
18
+ Markdown string -> md4c C++ parser -> JSON AST -> React Native renderers
30
19
 
31
20
  ---
32
21
 
33
- ## 📦 Installation
22
+ ## Features
34
23
 
35
- Choose your preferred package manager to install the package and its core dependency (`react-native-nitro-modules`).
24
+ - Native C++ parser with JSI access (fast, synchronous parsing)
25
+ - Full renderer included (Markdown component)
26
+ - Headless API for custom renderers or processing
27
+ - GFM support (tables, strikethrough, task lists, autolinks)
28
+ - LaTeX math parsing (inline and block)
29
+ - Streaming support for token-by-token updates
30
+ - Theming and per-node style overrides
31
+ - Built-in renderers exposed for reuse
36
32
 
37
- ### **1. Install Dependencies**
33
+ ---
38
34
 
39
- **npm**
35
+ ## Requirements
40
36
 
41
- ```bash
42
- npm install react-native-nitro-markdown react-native-nitro-modules
43
- ```
37
+ - React Native >= 0.75
38
+ - react-native-nitro-modules
44
39
 
45
- > **Note:** If you want to use **Math** (LaTeX) or certain **Image** features, you should also install the optional peer dependencies:
46
- > `npm install react-native-svg react-native-mathjax-svg`
40
+ Optional (for math rendering):
47
41
 
48
- **Yarn**
42
+ - react-native-mathjax-svg
43
+ - react-native-svg
49
44
 
50
- ```bash
51
- yarn add react-native-nitro-markdown react-native-nitro-modules
52
- ```
45
+ ---
46
+
47
+ ## Installation
53
48
 
54
- **Bun**
49
+ Install the package and Nitro Modules:
55
50
 
56
51
  ```bash
57
52
  bun add react-native-nitro-markdown react-native-nitro-modules
58
53
  ```
59
54
 
60
- **pnpm**
55
+ Optional math dependencies:
61
56
 
62
57
  ```bash
63
- pnpm add react-native-nitro-markdown react-native-nitro-modules
58
+ bun add react-native-mathjax-svg react-native-svg
64
59
  ```
65
60
 
66
- ### **2. Install Native Pods (iOS)**
67
-
68
- **Standard**
61
+ iOS pods:
69
62
 
70
63
  ```bash
71
64
  cd ios && pod install
72
65
  ```
73
66
 
74
- ### **3. Expo Users**
75
-
76
- If you are using Expo, you must run a **Prebuild** (Development Build) because this package contains native C++ code.
67
+ Expo (requires a development build):
77
68
 
78
69
  ```bash
79
70
  bunx expo install react-native-nitro-markdown react-native-nitro-modules
@@ -82,11 +73,7 @@ bunx expo prebuild
82
73
 
83
74
  ---
84
75
 
85
- ## 💻 Usage
86
-
87
- ### Option 1: Batteries Included (Simplest)
88
-
89
- Use the `Markdown` component with clean, neutral styling that stays out of the way:
76
+ ## Quick Start
90
77
 
91
78
  ```tsx
92
79
  import { Markdown } from "react-native-nitro-markdown";
@@ -100,11 +87,52 @@ export function MyComponent() {
100
87
  }
101
88
  ```
102
89
 
103
- If you're rendering on a dark surface, override `theme.colors.text` (or use the `styles` prop) to match your app's palette.
90
+ ---
91
+
92
+ ## Feature Guide
93
+
94
+ Start here (pick the approach that matches your use case):
95
+
96
+ - `Markdown` for static or preloaded content
97
+ - `MarkdownStream` + `useMarkdownSession` for streaming tokens
98
+ - `headless` API for custom rendering or data processing
99
+
100
+ ### 1) Parsing options (GFM and Math)
101
+
102
+ Parsing options are passed using the `options` prop.
103
+
104
+ ```tsx
105
+ <Markdown options={{ gfm: true, math: true }}>{content}</Markdown>
106
+ ```
107
+
108
+ - `gfm` enables GitHub Flavored Markdown features supported by md4c.
109
+ - `math` enables `$...$` and `$$...$$` parsing into math nodes.
104
110
 
105
- ### Option 2: Style Overrides per Node Type
111
+ ### 2) Styling and themes
106
112
 
107
- Apply quick style overrides to specific node types without writing custom renderers:
113
+ You can override tokens using the `theme` prop. Only provide the tokens you want to change.
114
+
115
+ ```tsx
116
+ import { Markdown } from "react-native-nitro-markdown";
117
+
118
+ const theme = {
119
+ colors: {
120
+ text: "#0f172a",
121
+ heading: "#0f172a",
122
+ link: "#2563eb",
123
+ codeBackground: "#f1f5f9",
124
+ },
125
+ showCodeLanguage: true,
126
+ };
127
+
128
+ <Markdown theme={theme}>{content}</Markdown>;
129
+ ```
130
+
131
+ If you use a custom heading font on Android and do not load a bold variant, set `headingWeight: "normal"` to avoid font fallback.
132
+
133
+ ### 3) Per-node style overrides
134
+
135
+ Override the styles for specific node types with `styles`.
108
136
 
109
137
  ```tsx
110
138
  <Markdown
@@ -114,13 +142,13 @@ Apply quick style overrides to specific node types without writing custom render
114
142
  blockquote: { borderLeftColor: "#0ea5e9" },
115
143
  }}
116
144
  >
117
- {markdown}
145
+ {content}
118
146
  </Markdown>
119
147
  ```
120
148
 
121
- ### Option 3: Custom Renderers
149
+ ### 4) Custom renderers
122
150
 
123
- Override specific node types with full control. Custom renderers receive **pre-mapped props** for common values:
151
+ Provide a custom renderer for any node type. You get pre-mapped props for common values.
124
152
 
125
153
  ```tsx
126
154
  import {
@@ -131,136 +159,67 @@ import {
131
159
  } from "react-native-nitro-markdown";
132
160
 
133
161
  const renderers = {
134
- // Pre-mapped `level` prop - no need for node.level!
135
162
  heading: ({ level, children }: HeadingRendererProps) => (
136
163
  <MyHeading level={level}>{children}</MyHeading>
137
164
  ),
138
-
139
- // Pre-mapped `content` and `language` - no getTextContent() needed!
140
165
  code_block: ({ content, language }: CodeBlockRendererProps) => (
141
- <CodeBlock
142
- content={content}
143
- language={language}
144
- style={{ borderWidth: 2 }}
145
- />
166
+ <CodeBlock content={content} language={language} />
146
167
  ),
147
168
  };
148
169
 
149
- <Markdown renderers={renderers} options={{ gfm: true }}>
150
- {markdown}
151
- </Markdown>;
170
+ <Markdown renderers={renderers}>{content}</Markdown>;
152
171
  ```
153
172
 
154
- **Pre-mapped Props by Node Type:**
173
+ Custom renderer behavior:
155
174
 
156
- - `heading` `level` (1-6)
157
- - `link` `href`, `title`
158
- - `image` `url`, `alt`, `title`
159
- - `code_block` → `content`, `language`
160
- - `code_inline` → `content`
161
- - `list` → `ordered`, `start`
162
- - `task_list_item` → `checked`
175
+ - Return `undefined` to fall back to the built-in renderer.
176
+ - Return `null` to render nothing for that node.
177
+ - The `Renderer` prop lets you render nested children the same way the default renderer does.
163
178
 
164
- ### Option 4: Token Overrides (Theme)
179
+ Pre-mapped props by node type:
165
180
 
166
- Customize the look and feel by passing a partial `theme` object:
181
+ | Node type | Extra props |
182
+ | --- | --- |
183
+ | `heading` | `level` |
184
+ | `link` | `href`, `title` |
185
+ | `image` | `url`, `alt`, `title` |
186
+ | `code_block` | `content`, `language` |
187
+ | `code_inline` | `content` |
188
+ | `list` | `ordered`, `start` |
189
+ | `task_list_item` | `checked` |
167
190
 
168
- ```tsx
169
- import { Markdown } from "react-native-nitro-markdown";
191
+ ### 5) Built-in renderers
170
192
 
171
- const myTheme = {
172
- colors: {
173
- text: "#0f172a",
174
- heading: "#0f172a",
175
- link: "#0ea5e9",
176
- codeBackground: "#e2e8f0",
177
- },
178
- showCodeLanguage: false,
179
- };
180
-
181
- <Markdown theme={myTheme}>{"# Custom Themed Markdown"}</Markdown>;
182
- ```
183
-
184
- Defaults live in `defaultMarkdownTheme` and are intentionally neutral so you can layer your own palette on top.
185
-
186
- **Theme Properties:**
187
-
188
- - `colors` - All color tokens (text, heading, link, code, codeBackground, codeLanguage, etc.)
189
- - `spacing` - Spacing tokens (xs, s, m, l, xl)
190
- - `fontSizes` - Font sizes (xs, s, m, l, xl, h1-h6)
191
- - `fontFamilies` - Font families for regular, heading, and mono text
192
- - `borderRadius` - Border radius tokens (s, m, l)
193
- - `showCodeLanguage` - Show/hide code block language labels
194
-
195
- ### Option 5: Minimal Styling Strategy
196
-
197
- Start with a clean slate using the `stylingStrategy` prop:
198
-
199
- ```tsx
200
- <Markdown stylingStrategy="minimal" theme={myLightTheme}>
201
- {content}
202
- </Markdown>
203
- ```
204
-
205
- This zeros out all spacing and removes opinionated colors, letting you build up from scratch.
206
-
207
- ### Option 6: Style Props on Individual Renderers
208
-
209
- All built-in renderers accept a `style` prop for fine-grained overrides:
193
+ All built-in renderers are exported so you can reuse them in custom renderers.
210
194
 
211
195
  ```tsx
212
196
  import { Heading, CodeBlock, InlineCode } from "react-native-nitro-markdown";
213
-
214
- // Works in custom renderers
215
- <Heading level={1} style={{ color: "hotpink" }}>Title</Heading>
216
- <CodeBlock content={code} style={{ borderRadius: 0 }} />
217
- <InlineCode style={{ backgroundColor: "#ff0" }}>code</InlineCode>
218
197
  ```
219
198
 
220
- ### Option 7: Auto Content Extraction for Code
199
+ Available renderers:
221
200
 
222
- The `CodeBlock` and `InlineCode` components now accept a `node` prop for automatic content extraction:
201
+ - `Heading`
202
+ - `Paragraph`
203
+ - `Link`
204
+ - `Blockquote`
205
+ - `HorizontalRule`
206
+ - `CodeBlock`
207
+ - `InlineCode`
208
+ - `List`
209
+ - `ListItem`
210
+ - `TaskListItem`
211
+ - `TableRenderer`
212
+ - `Image`
213
+ - `MathInline`
214
+ - `MathBlock`
223
215
 
224
- ```tsx
225
- // Before: Manual extraction required
226
- code_block: ({ node }) => (
227
- <CodeBlock content={getTextContent(node)} language={node.language} />
228
- );
229
-
230
- // After: Just pass the node
231
- code_block: ({ node }) => <CodeBlock node={node} />;
232
-
233
- // Or use the pre-mapped content prop (recommended)
234
- code_block: ({ content, language }) => (
235
- <CodeBlock content={content} language={language} />
236
- );
237
- ```
238
-
239
- ### Option 8: Headless (Minimal Bundle)
216
+ ### 6) Streaming (LLM tokens)
240
217
 
241
- For maximum control, data processing, or minimal JS overhead:
218
+ Use `MarkdownStream` plus `useMarkdownSession` to stream updates efficiently.
242
219
 
243
220
  ```tsx
244
- import {
245
- parseMarkdown,
246
- getTextContent,
247
- getFlattenedText,
248
- } from "react-native-nitro-markdown/headless";
249
-
250
- const ast = parseMarkdown("# Hello World");
251
- const text = getTextContent(ast); // "Hello World"
252
- const fullText = getFlattenedText(ast); // "Hello World\n\n" (Normalized with line breaks)
253
- ```
254
-
255
- ### Option 9: High-Performance Streaming (LLMs)
256
-
257
- When streaming text token-by-token (e.g., from ChatGPT or Gemini), you should batch UI updates to avoid re-rendering on every token:
258
-
259
- ```tsx
260
- import {
261
- MarkdownStream,
262
- useMarkdownSession,
263
- } from "react-native-nitro-markdown";
221
+ import { useEffect } from "react";
222
+ import { MarkdownStream, useMarkdownSession } from "react-native-nitro-markdown";
264
223
 
265
224
  export function AIResponseStream() {
266
225
  const session = useMarkdownSession();
@@ -274,7 +233,6 @@ export function AIResponseStream() {
274
233
  <MarkdownStream
275
234
  session={session.getSession()}
276
235
  options={{ gfm: true }}
277
- updateIntervalMs={60}
278
236
  updateStrategy="raf"
279
237
  useTransitionUpdates
280
238
  />
@@ -282,183 +240,280 @@ export function AIResponseStream() {
282
240
  }
283
241
  ```
284
242
 
285
- **Recommended defaults for token streaming:**
286
- - `updateStrategy="raf"` for smooth, frame-aligned updates
287
- - `updateIntervalMs={50–100}` when using `updateStrategy="interval"`
288
- - Avoid per-token UI updates by batching token appends
243
+ Recommended streaming defaults:
289
244
 
290
- **Streaming props:**
245
+ - `updateStrategy="raf"` for frame-aligned updates
246
+ - `updateIntervalMs` between `50` and `100` when using interval strategy
247
+ - Avoid per-token UI updates by batching appends
291
248
 
292
- | Prop | Type | Default | Description |
293
- | :-- | :-- | :-- | :-- |
294
- | `updateIntervalMs` | `number` | `50` | Throttle UI updates when using interval strategy |
295
- | `updateStrategy` | `"interval" \| "raf"` | `"interval"` | Interval batching or frame-aligned updates |
296
- | `useTransitionUpdates` | `boolean` | `false` | Use React transitions to keep input responsive |
249
+ ### 7) Headless parsing
250
+
251
+ Use the headless entry when you only need the AST or want to build your own renderer.
252
+
253
+ ```tsx
254
+ import {
255
+ parseMarkdown,
256
+ parseMarkdownWithOptions,
257
+ getTextContent,
258
+ getFlattenedText,
259
+ } from "react-native-nitro-markdown/headless";
297
260
 
298
- ### Option 10: Extracting Plain Text
261
+ const ast = parseMarkdown("# Hello World");
262
+ const text = getTextContent(ast);
263
+ const normalized = getFlattenedText(ast);
264
+ ```
265
+
266
+ ### 8) Plain text extraction
299
267
 
300
- You can extract the plain text representation (with proper line breaks) using the `onParseComplete` callback. This is useful for "Copy All" buttons or TTS.
268
+ If you are already using the `Markdown` component, you can get the plain text during parse.
301
269
 
302
270
  ```tsx
303
271
  <Markdown
304
272
  onParseComplete={(result) => {
305
- console.log(result.text); // "Hello World\n\nThis is bold text."
306
- console.log(result.ast); // Full AST
273
+ console.log(result.text);
307
274
  }}
308
275
  >
309
- {markdown}
276
+ {content}
310
277
  </Markdown>
311
278
  ```
312
279
 
280
+ ### 9) Tables, images, and math
281
+
282
+ - Tables are rendered with a horizontal scroll view and measured column widths.
283
+ - Images use React Native `Image` and try to preserve the real aspect ratio.
284
+ - Math nodes render with `react-native-mathjax-svg` if installed; otherwise they fall back to a code-style look.
285
+
313
286
  ---
314
287
 
315
- ## 🎨 Using Context in Custom Renderers
288
+ ## Common Recipes
316
289
 
317
- Access theme and context in custom renderers:
290
+ ### Open links with custom behavior
318
291
 
319
292
  ```tsx
320
- import {
321
- useMarkdownContext,
322
- MarkdownContext,
323
- } from "react-native-nitro-markdown";
324
-
325
- const MyCustomRenderer = ({ children }) => {
326
- const { theme, stylingStrategy } = useMarkdownContext();
293
+ import { Markdown, type LinkRendererProps } from "react-native-nitro-markdown";
294
+ import { Text, Linking } from "react-native";
327
295
 
328
- return <View style={{ padding: theme.spacing.m }}>{children}</View>;
296
+ const renderers = {
297
+ link: ({ href, children }: LinkRendererProps) => (
298
+ <Text
299
+ style={{ textDecorationLine: "underline" }}
300
+ onPress={async () => {
301
+ if (href && (await Linking.canOpenURL(href))) {
302
+ Linking.openURL(href);
303
+ }
304
+ }}
305
+ >
306
+ {children}
307
+ </Text>
308
+ ),
329
309
  };
330
- ```
331
310
 
332
- ---
311
+ <Markdown renderers={renderers}>{content}</Markdown>;
312
+ ```
333
313
 
334
- ## 🛠️ Exported Utilities
314
+ ### Custom image renderer (placeholder + fixed height)
335
315
 
336
316
  ```tsx
337
- // Parser and utilities
338
- export {
339
- parseMarkdown,
340
- parseMarkdownWithOptions,
341
- getTextContent,
342
- getFlattenedText,
343
- } from "./headless";
317
+ import { Markdown, type ImageRendererProps } from "react-native-nitro-markdown";
318
+ import { View, Image, Text } from "react-native";
344
319
 
345
- // Theme tokens
346
- export {
347
- defaultMarkdownTheme,
348
- minimalMarkdownTheme,
349
- mergeThemes,
320
+ const renderers = {
321
+ image: ({ url, title, alt }: ImageRendererProps) => (
322
+ <View>
323
+ <Image source={{ uri: url }} style={{ height: 220, borderRadius: 12 }} />
324
+ {(title || alt) && (
325
+ <Text style={{ marginTop: 6, opacity: 0.6 }}>{title || alt}</Text>
326
+ )}
327
+ </View>
328
+ ),
350
329
  };
330
+ ```
351
331
 
352
- // Context
353
- export { useMarkdownContext, MarkdownContext };
332
+ ### Render HTML nodes as code (opt-in)
354
333
 
355
- // Individual renderers
356
- export {
357
- Heading,
358
- Paragraph,
359
- Link,
360
- Blockquote,
361
- HorizontalRule,
362
- CodeBlock,
363
- InlineCode,
364
- List,
365
- ListItem,
366
- TaskListItem,
367
- TableRenderer,
368
- Image,
369
- MathInline,
370
- MathBlock,
334
+ ```tsx
335
+ import { Markdown, CodeBlock, InlineCode } from "react-native-nitro-markdown";
336
+
337
+ const renderers = {
338
+ html_block: ({ node }) => <CodeBlock content={node.content ?? \"\"} />,
339
+ html_inline: ({ node }) => <InlineCode content={node.content ?? \"\"} />,
371
340
  };
372
341
  ```
373
342
 
374
- ---
343
+ ### Minimal styling + custom palette
375
344
 
376
- ## 🛠️ Headless vs. Non-Headless
345
+ ```tsx
346
+ import { Markdown } from "react-native-nitro-markdown";
377
347
 
378
- | Feature | **Headless** (`/headless`) | **Non-Headless** (`default`) |
379
- | :-------------- | :-------------------------- | :--------------------------------- |
380
- | **Logic** | Raw C++ md4c Parser | Parser + Full UI Renderer |
381
- | **Output** | JSON AST Tree | React Native Views |
382
- | **Best For** | Search Indexing, Custom UIs | Fast Implementation, Documentation |
383
- | **JS Overhead** | ~4 KB | ~60 KB |
348
+ <Markdown
349
+ stylingStrategy="minimal"
350
+ theme={{ colors: { text: "#e2e8f0", link: "#38bdf8" } }}
351
+ >
352
+ {content}
353
+ </Markdown>;
354
+ ```
355
+
356
+ ### Build a search index (headless)
357
+
358
+ ```tsx
359
+ import { parseMarkdown, getFlattenedText } from "react-native-nitro-markdown/headless";
360
+
361
+ const ast = parseMarkdown(content);
362
+ const plainText = getFlattenedText(ast);
363
+ ```
384
364
 
385
365
  ---
386
366
 
387
- ### Basic Parsing API
367
+ ## API Reference
388
368
 
389
- The parsing is synchronous and instant. It returns a fully typed JSON AST:
369
+ ### Markdown component
390
370
 
391
- ```typescript
392
- import { parseMarkdown } from "react-native-nitro-markdown/headless";
371
+ ```tsx
372
+ import { Markdown } from "react-native-nitro-markdown";
373
+ ```
393
374
 
394
- const ast = parseMarkdown(`
395
- # Hello World
396
- This is **bold** text and a [link](https://github.com).
397
- `);
375
+ Props:
376
+
377
+ | Prop | Type | Default | Description |
378
+ | --- | --- | --- | --- |
379
+ | `children` | `string` | required | Markdown string to parse and render |
380
+ | `options` | `{ gfm?: boolean; math?: boolean }` | `undefined` | Parser options |
381
+ | `renderers` | `CustomRenderers` | `{}` | Custom renderers by node type |
382
+ | `theme` | `PartialMarkdownTheme` | `defaultMarkdownTheme` | Theme token overrides |
383
+ | `styles` | `NodeStyleOverrides` | `undefined` | Per-node style overrides |
384
+ | `stylingStrategy` | `"opinionated" \| "minimal"` | `"opinionated"` | Styling baseline |
385
+ | `style` | `StyleProp<ViewStyle>` | `undefined` | Container style |
386
+ | `onParsingInProgress` | `() => void` | `undefined` | Called when parsing starts |
387
+ | `onParseComplete` | `(result) => void` | `undefined` | Called with `{ raw, ast, text }` |
388
+
389
+ ### MarkdownStream
390
+
391
+ ```tsx
392
+ import { MarkdownStream } from "react-native-nitro-markdown";
393
+ ```
394
+
395
+ Props (in addition to all `Markdown` props except `children`):
396
+
397
+ | Prop | Type | Default | Description |
398
+ | --- | --- | --- | --- |
399
+ | `session` | `MarkdownSession` | required | Active session for streaming text |
400
+ | `updateIntervalMs` | `number` | `50` | Throttle interval for updates |
401
+ | `updateStrategy` | `"interval" \| "raf"` | `"interval"` | Update strategy |
402
+ | `useTransitionUpdates` | `boolean` | `false` | Use React transitions |
403
+
404
+ ### Hooks and sessions
405
+
406
+ ```tsx
407
+ import { useMarkdownSession, createMarkdownSession, useStream } from "react-native-nitro-markdown";
398
408
  ```
399
409
 
400
- ### Options
410
+ - `useMarkdownSession()` returns a managed session and helpers: `getSession`, `setIsStreaming`, `stop`, `clear`, `setHighlight`.
411
+ - `createMarkdownSession()` creates a session without React hooks.
412
+ - `useStream(timestamps)` adds a simple sync helper for time-based streaming.
401
413
 
402
- | Option | Type | Default | Description |
403
- | :----- | :-------- | :------ | :----------------------------------------------------------------------------- |
404
- | `gfm` | `boolean` | `false` | Enable GitHub Flavored Markdown (Tables, Strikethrough, Autolinks, TaskLists). |
405
- | `math` | `boolean` | `false` | Enable LaTeX Math support (`$` and `$$`). |
414
+ ### Headless API
415
+
416
+ ```tsx
417
+ import { parseMarkdown, parseMarkdownWithOptions, getTextContent, getFlattenedText } from "react-native-nitro-markdown/headless";
418
+ ```
419
+
420
+ ### Theme utilities
421
+
422
+ ```tsx
423
+ import {
424
+ defaultMarkdownTheme,
425
+ minimalMarkdownTheme,
426
+ mergeThemes,
427
+ } from "react-native-nitro-markdown";
428
+ ```
429
+
430
+ ### Types
431
+
432
+ ```tsx
433
+ import type {
434
+ CustomRenderers,
435
+ NodeStyleOverrides,
436
+ MarkdownTheme,
437
+ PartialMarkdownTheme,
438
+ } from "react-native-nitro-markdown";
439
+ ```
406
440
 
407
441
  ---
408
442
 
409
- ## 📐 AST Structure
443
+ ## Supported Node Types
410
444
 
411
- The parser returns a `MarkdownNode` tree:
445
+ You can customize any of these node types with `renderers` or `styles`.
412
446
 
413
- ```typescript
447
+ `document`, `heading`, `paragraph`, `text`, `bold`, `italic`, `strikethrough`, `link`, `image`, `code_inline`, `code_block`, `blockquote`, `horizontal_rule`, `line_break`, `soft_break`, `table`, `table_head`, `table_body`, `table_row`, `table_cell`, `list`, `list_item`, `task_list_item`, `math_inline`, `math_block`, `html_block`, `html_inline`
448
+
449
+ Note: `html_inline` and `html_block` are parsed but not rendered by default.
450
+
451
+ ---
452
+
453
+ ## AST Shape
454
+
455
+ The headless API returns a typed AST. This is the core shape used by the renderer.
456
+
457
+ ```ts
414
458
  export interface MarkdownNode {
415
- type: NodeType;
459
+ type:
460
+ | "document"
461
+ | "heading"
462
+ | "paragraph"
463
+ | "text"
464
+ | "bold"
465
+ | "italic"
466
+ | "strikethrough"
467
+ | "link"
468
+ | "image"
469
+ | "code_inline"
470
+ | "code_block"
471
+ | "blockquote"
472
+ | "horizontal_rule"
473
+ | "line_break"
474
+ | "soft_break"
475
+ | "table"
476
+ | "table_head"
477
+ | "table_body"
478
+ | "table_row"
479
+ | "table_cell"
480
+ | "list"
481
+ | "list_item"
482
+ | "task_list_item"
483
+ | "math_inline"
484
+ | "math_block"
485
+ | "html_block"
486
+ | "html_inline";
416
487
  content?: string;
417
488
  children?: MarkdownNode[];
418
489
  level?: number;
419
490
  href?: string;
420
- checked?: boolean;
491
+ title?: string;
492
+ alt?: string;
421
493
  language?: string;
422
- align?: "left" | "center" | "right";
494
+ ordered?: boolean;
495
+ start?: number;
496
+ checked?: boolean;
497
+ align?: string;
423
498
  isHeader?: boolean;
424
499
  }
425
500
  ```
426
501
 
427
502
  ---
428
503
 
429
- ## 🧮 LaTeX Math Support
504
+ ## Troubleshooting
430
505
 
431
- We parse math delimiters (`$` and `$$`) natively using the `MD_FLAG_LATEXMATHSPANS` flag in `md4c`.
432
-
433
- To render the math, use a library like `react-native-mathjax-svg`:
434
-
435
- ```tsx
436
- case 'math_inline':
437
- return <MathView math={node.content} style={styles.math} />;
438
- case 'math_block':
439
- return <MathView math={node.content} style={styles.mathBlock} />;
440
- ```
506
+ - Math renders as plain text: install `react-native-mathjax-svg` and `react-native-svg`.
507
+ - iOS build errors: run `pod install` after installing dependencies.
508
+ - Expo: you must use a development build (`expo prebuild` + `expo run`), not Expo Go.
509
+ - Android heading font looks wrong: set `headingWeight: "normal"` when your font has no bold variant.
441
510
 
442
511
  ---
443
512
 
444
- ## 📊 Package Size
445
-
446
- | Metric | Size |
447
- | :------------------- | :------ |
448
- | **Packed (tarball)** | ~75 kB |
449
- | **Unpacked** | ~325 kB |
450
- | **Total files** | 55 |
513
+ ## Contributing
451
514
 
452
- ---
515
+ See `CONTRIBUTING.md` for the workflow and development commands.
453
516
 
454
- ## 🤝 Contributing
455
-
456
- See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
457
-
458
- ## 📄 License
517
+ ## License
459
518
 
460
519
  MIT
461
-
462
- ---
463
-
464
- Built with ❤️ using [Nitro Modules](https://nitro.margelo.com) and [md4c](https://github.com/mity/md4c).