react-native-nitro-markdown 0.4.2 → 0.4.3

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 (155) hide show
  1. package/README.md +310 -352
  2. package/lib/commonjs/MarkdownContext.js +2 -1
  3. package/lib/commonjs/MarkdownContext.js.map +1 -1
  4. package/lib/commonjs/index.js.map +1 -1
  5. package/lib/commonjs/markdown-stream.js +3 -1
  6. package/lib/commonjs/markdown-stream.js.map +1 -1
  7. package/lib/commonjs/markdown.js +51 -35
  8. package/lib/commonjs/markdown.js.map +1 -1
  9. package/lib/commonjs/renderers/code.js +3 -3
  10. package/lib/commonjs/renderers/code.js.map +1 -1
  11. package/lib/commonjs/renderers/heading.js +1 -1
  12. package/lib/commonjs/renderers/heading.js.map +1 -1
  13. package/lib/commonjs/renderers/image.js +7 -5
  14. package/lib/commonjs/renderers/image.js.map +1 -1
  15. package/lib/commonjs/renderers/link.js +15 -3
  16. package/lib/commonjs/renderers/link.js.map +1 -1
  17. package/lib/commonjs/renderers/list.js +2 -2
  18. package/lib/commonjs/renderers/list.js.map +1 -1
  19. package/lib/commonjs/renderers/table.js +36 -23
  20. package/lib/commonjs/renderers/table.js.map +1 -1
  21. package/lib/commonjs/use-markdown-stream.js +16 -14
  22. package/lib/commonjs/use-markdown-stream.js.map +1 -1
  23. package/lib/commonjs/utils/link-security.js +21 -0
  24. package/lib/commonjs/utils/link-security.js.map +1 -0
  25. package/lib/commonjs/utils/stream-timeline.js +62 -0
  26. package/lib/commonjs/utils/stream-timeline.js.map +1 -0
  27. package/lib/module/MarkdownContext.js +2 -1
  28. package/lib/module/MarkdownContext.js.map +1 -1
  29. package/lib/module/index.js.map +1 -1
  30. package/lib/module/markdown-stream.js +3 -1
  31. package/lib/module/markdown-stream.js.map +1 -1
  32. package/lib/module/markdown.js +52 -36
  33. package/lib/module/markdown.js.map +1 -1
  34. package/lib/module/renderers/blockquote.js.map +1 -1
  35. package/lib/module/renderers/code.js +3 -3
  36. package/lib/module/renderers/code.js.map +1 -1
  37. package/lib/module/renderers/heading.js +1 -1
  38. package/lib/module/renderers/heading.js.map +1 -1
  39. package/lib/module/renderers/image.js +7 -5
  40. package/lib/module/renderers/image.js.map +1 -1
  41. package/lib/module/renderers/link.js +15 -3
  42. package/lib/module/renderers/link.js.map +1 -1
  43. package/lib/module/renderers/list.js +2 -2
  44. package/lib/module/renderers/list.js.map +1 -1
  45. package/lib/module/renderers/paragraph.js.map +1 -1
  46. package/lib/module/renderers/table.js +37 -24
  47. package/lib/module/renderers/table.js.map +1 -1
  48. package/lib/module/use-markdown-stream.js +16 -14
  49. package/lib/module/use-markdown-stream.js.map +1 -1
  50. package/lib/module/utils/link-security.js +15 -0
  51. package/lib/module/utils/link-security.js.map +1 -0
  52. package/lib/module/utils/stream-timeline.js +56 -0
  53. package/lib/module/utils/stream-timeline.js.map +1 -0
  54. package/lib/typescript/commonjs/Markdown.nitro.d.ts +3 -3
  55. package/lib/typescript/commonjs/Markdown.nitro.d.ts.map +1 -1
  56. package/lib/typescript/commonjs/MarkdownContext.d.ts +26 -25
  57. package/lib/typescript/commonjs/MarkdownContext.d.ts.map +1 -1
  58. package/lib/typescript/commonjs/headless.d.ts +2 -2
  59. package/lib/typescript/commonjs/headless.d.ts.map +1 -1
  60. package/lib/typescript/commonjs/index.d.ts +1 -1
  61. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  62. package/lib/typescript/commonjs/markdown-stream.d.ts +2 -2
  63. package/lib/typescript/commonjs/markdown-stream.d.ts.map +1 -1
  64. package/lib/typescript/commonjs/markdown.d.ts +9 -4
  65. package/lib/typescript/commonjs/markdown.d.ts.map +1 -1
  66. package/lib/typescript/commonjs/renderers/blockquote.d.ts +3 -3
  67. package/lib/typescript/commonjs/renderers/blockquote.d.ts.map +1 -1
  68. package/lib/typescript/commonjs/renderers/code.d.ts +5 -5
  69. package/lib/typescript/commonjs/renderers/code.d.ts.map +1 -1
  70. package/lib/typescript/commonjs/renderers/heading.d.ts +3 -3
  71. package/lib/typescript/commonjs/renderers/heading.d.ts.map +1 -1
  72. package/lib/typescript/commonjs/renderers/horizontal-rule.d.ts +2 -2
  73. package/lib/typescript/commonjs/renderers/horizontal-rule.d.ts.map +1 -1
  74. package/lib/typescript/commonjs/renderers/image.d.ts +2 -2
  75. package/lib/typescript/commonjs/renderers/image.d.ts.map +1 -1
  76. package/lib/typescript/commonjs/renderers/link.d.ts +3 -3
  77. package/lib/typescript/commonjs/renderers/link.d.ts.map +1 -1
  78. package/lib/typescript/commonjs/renderers/list.d.ts +7 -7
  79. package/lib/typescript/commonjs/renderers/list.d.ts.map +1 -1
  80. package/lib/typescript/commonjs/renderers/math.d.ts +4 -4
  81. package/lib/typescript/commonjs/renderers/math.d.ts.map +1 -1
  82. package/lib/typescript/commonjs/renderers/paragraph.d.ts +3 -3
  83. package/lib/typescript/commonjs/renderers/paragraph.d.ts.map +1 -1
  84. package/lib/typescript/commonjs/renderers/table.d.ts +3 -3
  85. package/lib/typescript/commonjs/renderers/table.d.ts.map +1 -1
  86. package/lib/typescript/commonjs/theme.d.ts +2 -2
  87. package/lib/typescript/commonjs/theme.d.ts.map +1 -1
  88. package/lib/typescript/commonjs/use-markdown-stream.d.ts.map +1 -1
  89. package/lib/typescript/commonjs/utils/link-security.d.ts +3 -0
  90. package/lib/typescript/commonjs/utils/link-security.d.ts.map +1 -0
  91. package/lib/typescript/commonjs/utils/stream-timeline.d.ts +11 -0
  92. package/lib/typescript/commonjs/utils/stream-timeline.d.ts.map +1 -0
  93. package/lib/typescript/module/Markdown.nitro.d.ts +3 -3
  94. package/lib/typescript/module/Markdown.nitro.d.ts.map +1 -1
  95. package/lib/typescript/module/MarkdownContext.d.ts +26 -25
  96. package/lib/typescript/module/MarkdownContext.d.ts.map +1 -1
  97. package/lib/typescript/module/headless.d.ts +2 -2
  98. package/lib/typescript/module/headless.d.ts.map +1 -1
  99. package/lib/typescript/module/index.d.ts +1 -1
  100. package/lib/typescript/module/index.d.ts.map +1 -1
  101. package/lib/typescript/module/markdown-stream.d.ts +2 -2
  102. package/lib/typescript/module/markdown-stream.d.ts.map +1 -1
  103. package/lib/typescript/module/markdown.d.ts +9 -4
  104. package/lib/typescript/module/markdown.d.ts.map +1 -1
  105. package/lib/typescript/module/renderers/blockquote.d.ts +3 -3
  106. package/lib/typescript/module/renderers/blockquote.d.ts.map +1 -1
  107. package/lib/typescript/module/renderers/code.d.ts +5 -5
  108. package/lib/typescript/module/renderers/code.d.ts.map +1 -1
  109. package/lib/typescript/module/renderers/heading.d.ts +3 -3
  110. package/lib/typescript/module/renderers/heading.d.ts.map +1 -1
  111. package/lib/typescript/module/renderers/horizontal-rule.d.ts +2 -2
  112. package/lib/typescript/module/renderers/horizontal-rule.d.ts.map +1 -1
  113. package/lib/typescript/module/renderers/image.d.ts +2 -2
  114. package/lib/typescript/module/renderers/image.d.ts.map +1 -1
  115. package/lib/typescript/module/renderers/link.d.ts +3 -3
  116. package/lib/typescript/module/renderers/link.d.ts.map +1 -1
  117. package/lib/typescript/module/renderers/list.d.ts +7 -7
  118. package/lib/typescript/module/renderers/list.d.ts.map +1 -1
  119. package/lib/typescript/module/renderers/math.d.ts +4 -4
  120. package/lib/typescript/module/renderers/math.d.ts.map +1 -1
  121. package/lib/typescript/module/renderers/paragraph.d.ts +3 -3
  122. package/lib/typescript/module/renderers/paragraph.d.ts.map +1 -1
  123. package/lib/typescript/module/renderers/table.d.ts +3 -3
  124. package/lib/typescript/module/renderers/table.d.ts.map +1 -1
  125. package/lib/typescript/module/theme.d.ts +2 -2
  126. package/lib/typescript/module/theme.d.ts.map +1 -1
  127. package/lib/typescript/module/use-markdown-stream.d.ts.map +1 -1
  128. package/lib/typescript/module/utils/link-security.d.ts +3 -0
  129. package/lib/typescript/module/utils/link-security.d.ts.map +1 -0
  130. package/lib/typescript/module/utils/stream-timeline.d.ts +11 -0
  131. package/lib/typescript/module/utils/stream-timeline.d.ts.map +1 -0
  132. package/nitrogen/generated/ios/swift/Func_void.swift +0 -1
  133. package/nitrogen/generated/ios/swift/HybridMarkdownSessionSpec.swift +0 -1
  134. package/nitrogen/generated/ios/swift/HybridMarkdownSessionSpec_cxx.swift +0 -1
  135. package/package.json +4 -3
  136. package/src/Markdown.nitro.ts +5 -3
  137. package/src/MarkdownContext.ts +31 -25
  138. package/src/headless.ts +2 -2
  139. package/src/index.ts +1 -0
  140. package/src/markdown-stream.tsx +6 -10
  141. package/src/markdown.tsx +86 -45
  142. package/src/renderers/blockquote.tsx +4 -4
  143. package/src/renderers/code.tsx +11 -9
  144. package/src/renderers/heading.tsx +4 -4
  145. package/src/renderers/horizontal-rule.tsx +3 -3
  146. package/src/renderers/image.tsx +11 -9
  147. package/src/renderers/link.tsx +25 -7
  148. package/src/renderers/list.tsx +9 -12
  149. package/src/renderers/math.tsx +4 -4
  150. package/src/renderers/paragraph.tsx +3 -3
  151. package/src/renderers/table.tsx +77 -51
  152. package/src/theme.ts +3 -3
  153. package/src/use-markdown-stream.ts +22 -16
  154. package/src/utils/link-security.ts +22 -0
  155. package/src/utils/stream-timeline.ts +72 -0
package/README.md CHANGED
@@ -5,54 +5,37 @@
5
5
 
6
6
  # react-native-nitro-markdown
7
7
 
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.
8
+ Native Markdown parsing and rendering for React Native.
9
9
 
10
- ---
10
+ `react-native-nitro-markdown` uses `md4c` (C++) through Nitro Modules (JSI) to parse Markdown synchronously into a typed AST, then render it with customizable React Native components.
11
11
 
12
- ## Why this package exists
12
+ ## Why use it
13
13
 
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.
15
-
16
- **How it works:**
17
-
18
- Markdown string -> md4c C++ parser -> JSON AST -> React Native renderers
19
-
20
- ---
21
-
22
- ## Features
23
-
24
- - Native C++ parser with JSI access (fast, synchronous parsing)
25
- - Full renderer included (Markdown component)
26
- - Headless API for custom renderers or processing
14
+ - Native parser (`md4c`) for lower JS thread overhead on large documents
15
+ - End-to-end solution: parser + renderer + streaming session API
16
+ - Headless API for custom rendering and text processing
27
17
  - 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
32
-
33
- ---
18
+ - Optional math rendering with `react-native-mathjax-svg`
34
19
 
35
20
  ## Requirements
36
21
 
37
- - React Native >= 0.75
38
- - react-native-nitro-modules
22
+ - React Native `>=0.75.0`
23
+ - `react-native-nitro-modules`
39
24
 
40
- Optional (for math rendering):
25
+ Optional for math rendering:
41
26
 
42
- - react-native-mathjax-svg
43
- - react-native-svg
44
-
45
- ---
27
+ - `react-native-mathjax-svg >=0.9.0`
28
+ - `react-native-svg >=13.0.0`
46
29
 
47
30
  ## Installation
48
31
 
49
- Install the package and Nitro Modules:
32
+ ### React Native
50
33
 
51
34
  ```bash
52
35
  bun add react-native-nitro-markdown react-native-nitro-modules
53
36
  ```
54
37
 
55
- Optional math dependencies:
38
+ Optional math support:
56
39
 
57
40
  ```bash
58
41
  bun add react-native-mathjax-svg react-native-svg
@@ -64,168 +47,135 @@ iOS pods:
64
47
  cd ios && pod install
65
48
  ```
66
49
 
67
- Expo (requires a development build):
50
+ ### Expo (development build)
68
51
 
69
52
  ```bash
70
53
  bunx expo install react-native-nitro-markdown react-native-nitro-modules
71
54
  bunx expo prebuild
72
55
  ```
73
56
 
74
- ---
57
+ Optional math support:
58
+
59
+ ```bash
60
+ bunx expo install react-native-mathjax-svg react-native-svg
61
+ ```
75
62
 
76
63
  ## Quick Start
77
64
 
78
65
  ```tsx
79
66
  import { Markdown } from "react-native-nitro-markdown";
80
67
 
81
- export function MyComponent() {
68
+ export function Example() {
82
69
  return (
83
70
  <Markdown options={{ gfm: true }}>
84
- {"# Hello World\nThis is **bold** text."}
71
+ {"# Hello\nThis is **native** markdown."}
85
72
  </Markdown>
86
73
  );
87
74
  }
88
75
  ```
89
76
 
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.
110
-
111
- ### 2) Styling and themes
112
-
113
- You can override tokens using the `theme` prop. Only provide the tokens you want to change.
77
+ ## Package Exports
78
+
79
+ ### Main Entry (`react-native-nitro-markdown`)
80
+
81
+ - Parser and headless helpers:
82
+ - `parseMarkdown`
83
+ - `parseMarkdownWithOptions`
84
+ - `getTextContent`
85
+ - `getFlattenedText`
86
+ - `MarkdownParserModule`
87
+ - Components:
88
+ - `Markdown`
89
+ - `MarkdownStream`
90
+ - Hooks and sessions:
91
+ - `useMarkdownSession`
92
+ - `useStream`
93
+ - `createMarkdownSession`
94
+ - Context:
95
+ - `MarkdownContext`
96
+ - `useMarkdownContext`
97
+ - Theme:
98
+ - `defaultMarkdownTheme`
99
+ - `minimalMarkdownTheme`
100
+ - `mergeThemes`
101
+ - Built-in renderers:
102
+ - `Heading`, `Paragraph`, `Link`, `Blockquote`, `HorizontalRule`
103
+ - `CodeBlock`, `InlineCode`
104
+ - `List`, `ListItem`, `TaskListItem`
105
+ - `TableRenderer`, `Image`, `MathInline`, `MathBlock`
106
+ - Types:
107
+ - `MarkdownNode`, `ParserOptions`, `MarkdownParser`
108
+ - `CustomRenderers`, `CustomRenderer`, `CustomRendererProps`
109
+ - `NodeRendererProps`, `BaseCustomRendererProps`, `EnhancedRendererProps`
110
+ - `HeadingRendererProps`, `LinkRendererProps`, `ImageRendererProps`
111
+ - `CodeBlockRendererProps`, `InlineCodeRendererProps`
112
+ - `ListRendererProps`, `TaskListItemRendererProps`
113
+ - `LinkPressHandler`, `MarkdownContextValue`
114
+ - `MarkdownTheme`, `PartialMarkdownTheme`, `NodeStyleOverrides`, `StylingStrategy`
115
+ - `MarkdownSession`
116
+
117
+ ### Headless Entry (`react-native-nitro-markdown/headless`)
118
+
119
+ Exports only parser-related API (`parseMarkdown`, `parseMarkdownWithOptions`, `getTextContent`, `getFlattenedText`, types). Use this when you do not need built-in UI rendering.
120
+
121
+ ## Component API
122
+
123
+ ## `Markdown`
114
124
 
115
125
  ```tsx
116
126
  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
127
  ```
130
128
 
131
- If you use a custom heading font on Android and do not load a bold variant, set `headingWeight: "normal"` to avoid font fallback.
129
+ | Prop | Type | Default | Description |
130
+ | --------------------- | ---------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------- | -------------------- |
131
+ | `children` | `string` | required | Markdown input string. |
132
+ | `options` | `ParserOptions` | `undefined` | Parser flags (`gfm`, `math`). |
133
+ | `renderers` | `CustomRenderers` | `{}` | Per-node custom renderers. |
134
+ | `theme` | `PartialMarkdownTheme` | `defaultMarkdownTheme` or `minimalMarkdownTheme` | Theme token overrides. |
135
+ | `styles` | `NodeStyleOverrides` | `undefined` | Per-node style overrides. |
136
+ | `stylingStrategy` | `"opinionated" | "minimal"` | `"opinionated"` | Base styling preset. |
137
+ | `style` | `StyleProp<ViewStyle>` | `undefined` | Container style for the root `View`. |
138
+ | `onParsingInProgress` | `() => void` | `undefined` | Called when parse inputs change. |
139
+ | `onParseComplete` | `(result) => void` | `undefined` | Called with `{ raw, ast, text }` after successful parse. |
140
+ | `onLinkPress` | `LinkPressHandler` | `undefined` | Intercepts link press before default open behavior. Return `false` to block default open. |
132
141
 
133
- ### 3) Per-node style overrides
142
+ Notes:
134
143
 
135
- Override the styles for specific node types with `styles`.
144
+ - Parse failures are caught and rendered as a fallback message (`Error parsing markdown`).
145
+ - `text` in `onParseComplete` is produced by `getFlattenedText(ast)`.
136
146
 
137
- ```tsx
138
- <Markdown
139
- styles={{
140
- heading: { color: "#0ea5e9", fontWeight: "900" },
141
- code_block: { backgroundColor: "#e2e8f0", borderRadius: 16 },
142
- blockquote: { borderLeftColor: "#0ea5e9" },
143
- }}
144
- >
145
- {content}
146
- </Markdown>
147
- ```
148
-
149
- ### 4) Custom renderers
150
-
151
- Provide a custom renderer for any node type. You get pre-mapped props for common values.
147
+ ## `MarkdownStream`
152
148
 
153
149
  ```tsx
154
- import {
155
- Markdown,
156
- CodeBlock,
157
- type HeadingRendererProps,
158
- type CodeBlockRendererProps,
159
- } from "react-native-nitro-markdown";
160
-
161
- const renderers = {
162
- heading: ({ level, children }: HeadingRendererProps) => (
163
- <MyHeading level={level}>{children}</MyHeading>
164
- ),
165
- code_block: ({ content, language }: CodeBlockRendererProps) => (
166
- <CodeBlock content={content} language={language} />
167
- ),
168
- };
169
-
170
- <Markdown renderers={renderers}>{content}</Markdown>;
171
- ```
172
-
173
- Custom renderer behavior:
174
-
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.
178
-
179
- Pre-mapped props by node type:
180
-
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` |
190
-
191
- ### 5) Built-in renderers
192
-
193
- All built-in renderers are exported so you can reuse them in custom renderers.
194
-
195
- ```tsx
196
- import { Heading, CodeBlock, InlineCode } from "react-native-nitro-markdown";
150
+ import { MarkdownStream } from "react-native-nitro-markdown";
197
151
  ```
198
152
 
199
- Available renderers:
200
-
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`
153
+ `MarkdownStreamProps` extends `MarkdownProps` except `children`.
215
154
 
216
- ### 6) Streaming (LLM tokens)
155
+ | Prop | Type | Default | Description |
156
+ | ---------------------- | ----------------- | -------- | -------------------------------------------------- | --------------------------------------------------------- |
157
+ | `session` | `MarkdownSession` | required | Session object that supplies streamed text chunks. |
158
+ | `updateIntervalMs` | `number` | `50` | Flush interval when `updateStrategy="interval"`. |
159
+ | `updateStrategy` | `"interval" | "raf"` | `"interval"` | Update cadence (`setTimeout` vs `requestAnimationFrame`). |
160
+ | `useTransitionUpdates` | `boolean` | `false` | Applies `startTransition` to streamed UI updates. |
217
161
 
218
- Use `MarkdownStream` plus `useMarkdownSession` to stream updates efficiently.
162
+ ### Streaming Example
219
163
 
220
164
  ```tsx
221
165
  import { useEffect } from "react";
222
- import { MarkdownStream, useMarkdownSession } from "react-native-nitro-markdown";
166
+ import {
167
+ MarkdownStream,
168
+ useMarkdownSession,
169
+ } from "react-native-nitro-markdown";
223
170
 
224
- export function AIResponseStream() {
171
+ export function StreamingExample() {
225
172
  const session = useMarkdownSession();
226
173
 
227
174
  useEffect(() => {
228
- session.getSession().append("Hello **Nitro**!");
175
+ const s = session.getSession();
176
+ s.append("# Streaming\n");
177
+ s.append("This text arrives in chunks.");
178
+
229
179
  return () => session.clear();
230
180
  }, [session]);
231
181
 
@@ -240,15 +190,51 @@ export function AIResponseStream() {
240
190
  }
241
191
  ```
242
192
 
243
- Recommended streaming defaults:
193
+ ## Hooks and Session API
194
+
195
+ ## `createMarkdownSession()`
196
+
197
+ Creates and returns a native `MarkdownSession` instance.
198
+
199
+ ```tsx
200
+ import { createMarkdownSession } from "react-native-nitro-markdown";
201
+
202
+ const session = createMarkdownSession();
203
+ session.append("hello");
204
+ ```
205
+
206
+ ## `useMarkdownSession()`
207
+
208
+ Creates and owns one `MarkdownSession` for a component lifecycle.
209
+
210
+ Returns:
211
+
212
+ | Field | Type | Description |
213
+ | ---------------- | ---------------------------- | ------------------------------------------------------------- |
214
+ | `getSession` | `() => MarkdownSession` | Returns the stable native session instance. |
215
+ | `isStreaming` | `boolean` | Stateful flag for app-level streaming UI. |
216
+ | `setIsStreaming` | `(value: boolean) => void` | Setter for `isStreaming`. |
217
+ | `stop` | `() => void` | Sets `isStreaming` to `false`. |
218
+ | `clear` | `() => void` | Clears session content and resets `highlightPosition` to `0`. |
219
+ | `setHighlight` | `(position: number) => void` | Sets `session.highlightPosition`. |
244
220
 
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
221
+ ## `useStream(timestamps?)`
248
222
 
249
- ### 7) Headless parsing
223
+ Builds on `useMarkdownSession` and adds timeline sync helpers.
250
224
 
251
- Use the headless entry when you only need the AST or want to build your own renderer.
225
+ - `timestamps` type: `Record<number, number>` where key = word/token index, value = timestamp in ms.
226
+ - `sync(currentTimeMs)` computes highlight position from timestamp map.
227
+ - Uses optimized lookup for monotonic timelines and handles non-monotonic maps safely.
228
+
229
+ Additional returned fields:
230
+
231
+ | Field | Type | Description |
232
+ | -------------- | --------------------------------- | ----------------------------------------- |
233
+ | `isPlaying` | `boolean` | Playback state for timed streaming. |
234
+ | `setIsPlaying` | `(value: boolean) => void` | Setter for `isPlaying`. |
235
+ | `sync` | `(currentTimeMs: number) => void` | Applies timeline-based highlight updates. |
236
+
237
+ ## Headless API
252
238
 
253
239
  ```tsx
254
240
  import {
@@ -257,262 +243,234 @@ import {
257
243
  getTextContent,
258
244
  getFlattenedText,
259
245
  } from "react-native-nitro-markdown/headless";
260
-
261
- const ast = parseMarkdown("# Hello World");
262
- const text = getTextContent(ast);
263
- const normalized = getFlattenedText(ast);
264
246
  ```
265
247
 
266
- ### 8) Plain text extraction
248
+ | Function | Signature | Description |
249
+ | -------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------ |
250
+ | `parseMarkdown` | `(text: string) => MarkdownNode` | Parses markdown using default parser settings. |
251
+ | `parseMarkdownWithOptions` | `(text: string, options: ParserOptions) => MarkdownNode` | Parses markdown with `gfm` and/or `math` flags. |
252
+ | `getTextContent` | `(node: MarkdownNode) => string` | Concatenates text recursively without layout normalization. |
253
+ | `getFlattenedText` | `(node: MarkdownNode) => string` | Returns normalized plain text with paragraph and block separators. |
267
254
 
268
- If you are already using the `Markdown` component, you can get the plain text during parse.
255
+ ### Parser Options
269
256
 
270
- ```tsx
271
- <Markdown
272
- onParseComplete={(result) => {
273
- console.log(result.text);
274
- }}
275
- >
276
- {content}
277
- </Markdown>
257
+ ```ts
258
+ type ParserOptions = {
259
+ gfm?: boolean;
260
+ math?: boolean;
261
+ };
278
262
  ```
279
263
 
280
- ### 9) Tables, images, and math
264
+ ## Custom Renderer API
281
265
 
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.
266
+ ## `renderers` prop contract
285
267
 
286
- ---
268
+ `CustomRenderers` is:
287
269
 
288
- ## Common Recipes
270
+ ```ts
271
+ type CustomRenderers = Partial<Record<MarkdownNode["type"], CustomRenderer>>;
272
+ ```
289
273
 
290
- ### Open links with custom behavior
274
+ `CustomRenderer` receives `EnhancedRendererProps` and returns:
291
275
 
292
- ```tsx
293
- import { Markdown, type LinkRendererProps } from "react-native-nitro-markdown";
294
- import { Text, Linking } from "react-native";
276
+ - React node to override default rendering
277
+ - `undefined` to fallback to built-in renderer
278
+ - `null` to render nothing
295
279
 
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
- ),
309
- };
280
+ `EnhancedRendererProps` always includes:
310
281
 
311
- <Markdown renderers={renderers}>{content}</Markdown>;
312
- ```
282
+ - `node`: current `MarkdownNode`
283
+ - `children`: pre-rendered node children
284
+ - `Renderer`: recursive renderer component for nested custom rendering
313
285
 
314
- ### Custom image renderer (placeholder + fixed height)
286
+ And conditionally includes mapped fields by node type:
315
287
 
316
- ```tsx
317
- import { Markdown, type ImageRendererProps } from "react-native-nitro-markdown";
318
- import { View, Image, Text } from "react-native";
288
+ | Node type | Extra mapped fields |
289
+ | ---------------- | --------------------- |
290
+ | `heading` | `level` |
291
+ | `link` | `href`, `title` |
292
+ | `image` | `url`, `alt`, `title` |
293
+ | `code_block` | `content`, `language` |
294
+ | `code_inline` | `content` |
295
+ | `list` | `ordered`, `start` |
296
+ | `task_list_item` | `checked` |
319
297
 
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
- ),
329
- };
330
- ```
331
-
332
- ### Render HTML nodes as code (opt-in)
298
+ ### Example: Custom heading + code block
333
299
 
334
300
  ```tsx
335
- import { Markdown, CodeBlock, InlineCode } from "react-native-nitro-markdown";
301
+ import {
302
+ Markdown,
303
+ type HeadingRendererProps,
304
+ type CodeBlockRendererProps,
305
+ } from "react-native-nitro-markdown";
336
306
 
337
307
  const renderers = {
338
- html_block: ({ node }) => <CodeBlock content={node.content ?? \"\"} />,
339
- html_inline: ({ node }) => <InlineCode content={node.content ?? \"\"} />,
308
+ heading: ({ level, children }: HeadingRendererProps) => (
309
+ <MyHeading level={level}>{children}</MyHeading>
310
+ ),
311
+ code_block: ({ language, content }: CodeBlockRendererProps) => (
312
+ <MyCode language={language} content={content} />
313
+ ),
340
314
  };
341
- ```
342
-
343
- ### Minimal styling + custom palette
344
315
 
345
- ```tsx
346
- import { Markdown } from "react-native-nitro-markdown";
347
-
348
- <Markdown
349
- stylingStrategy="minimal"
350
- theme={{ colors: { text: "#e2e8f0", link: "#38bdf8" } }}
351
- >
352
- {content}
353
- </Markdown>;
316
+ <Markdown renderers={renderers}>{content}</Markdown>;
354
317
  ```
355
318
 
356
- ### Build a search index (headless)
319
+ ## Link Handling Behavior
357
320
 
358
- ```tsx
359
- import { parseMarkdown, getFlattenedText } from "react-native-nitro-markdown/headless";
321
+ Default link renderer behavior:
360
322
 
361
- const ast = parseMarkdown(content);
362
- const plainText = getFlattenedText(ast);
363
- ```
323
+ 1. Trims incoming href.
324
+ 2. Calls `onLinkPress(href)` if provided.
325
+ 3. Stops if handler returns `false`.
326
+ 4. Allows only protocol-based links with these schemes:
327
+ - `http:`
328
+ - `https:`
329
+ - `mailto:`
330
+ - `tel:`
331
+ - `sms:`
332
+ 5. Uses `Linking.canOpenURL` before `Linking.openURL`.
364
333
 
365
- ---
334
+ Relative URLs and anchors are ignored by default open behavior, but you can handle them in `onLinkPress`.
366
335
 
367
- ## API Reference
336
+ ## Theme API
368
337
 
369
- ### Markdown component
338
+ ## `MarkdownTheme`
370
339
 
371
340
  ```tsx
372
- import { Markdown } from "react-native-nitro-markdown";
341
+ import type {
342
+ MarkdownTheme,
343
+ PartialMarkdownTheme,
344
+ } from "react-native-nitro-markdown";
373
345
  ```
374
346
 
375
- Props:
347
+ `MarkdownTheme` fields:
376
348
 
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 }` |
349
+ - `colors`
350
+ - `text`, `textMuted`, `heading`, `link`, `code`, `codeBackground`, `codeLanguage`
351
+ - `blockquote`, `border`, `surface`, `surfaceLight`, `accent`
352
+ - `tableBorder`, `tableHeader`, `tableHeaderText`, `tableRowEven`, `tableRowOdd`
353
+ - `spacing`: `xs`, `s`, `m`, `l`, `xl`
354
+ - `fontSizes`: `xs`, `s`, `m`, `l`, `xl`, `h1`, `h2`, `h3`, `h4`, `h5`, `h6`
355
+ - `fontFamilies`: `regular`, `heading`, `mono`
356
+ - `headingWeight?`
357
+ - `borderRadius`: `s`, `m`, `l`
358
+ - `showCodeLanguage`
388
359
 
389
- ### MarkdownStream
360
+ Helpers:
390
361
 
391
- ```tsx
392
- import { MarkdownStream } from "react-native-nitro-markdown";
362
+ - `defaultMarkdownTheme`
363
+ - `minimalMarkdownTheme`
364
+ - `mergeThemes(base, partial)`
365
+
366
+ `NodeStyleOverrides` lets you override per-node styles:
367
+
368
+ ```ts
369
+ type NodeStyleOverrides = Partial<
370
+ Record<MarkdownNode["type"], ViewStyle | TextStyle>
371
+ >;
393
372
  ```
394
373
 
395
- Props (in addition to all `Markdown` props except `children`):
374
+ ## Built-in Renderer Components
375
+
376
+ Use these when composing custom renderer maps.
377
+
378
+ | Component | Key props |
379
+ | ---------------- | ------------------------------------------------ |
380
+ | `Heading` | `level`, `children`, `style` |
381
+ | `Paragraph` | `children`, `inListItem`, `style` |
382
+ | `Link` | `href`, `children`, `style` |
383
+ | `Blockquote` | `children`, `style` |
384
+ | `HorizontalRule` | `style` |
385
+ | `CodeBlock` | `language`, `content`, `node`, `style` |
386
+ | `InlineCode` | `content`, `node`, `children`, `style` |
387
+ | `List` | `ordered`, `start`, `depth`, `children`, `style` |
388
+ | `ListItem` | `children`, `index`, `ordered`, `start`, `style` |
389
+ | `TaskListItem` | `children`, `checked`, `style` |
390
+ | `TableRenderer` | `node`, `Renderer`, `style` |
391
+ | `Image` | `url`, `title`, `alt`, `Renderer`, `style` |
392
+ | `MathInline` | `content`, `style` |
393
+ | `MathBlock` | `content`, `style` |
394
+
395
+ ## Supported AST Node Types
396
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 |
397
+ `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`
403
398
 
404
- ### Hooks and sessions
399
+ Notes:
405
400
 
406
- ```tsx
407
- import { useMarkdownSession, createMarkdownSession, useStream } from "react-native-nitro-markdown";
408
- ```
401
+ - `html_inline` and `html_block` are parsed but not rendered by default.
402
+ - Table internals (`table_head`, `table_body`, `table_row`, `table_cell`) are renderer internals; override `table` for custom table UI.
409
403
 
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.
404
+ ## Recipes
413
405
 
414
- ### Headless API
406
+ ### Intercept links with `onLinkPress`
415
407
 
416
408
  ```tsx
417
- import { parseMarkdown, parseMarkdownWithOptions, getTextContent, getFlattenedText } from "react-native-nitro-markdown/headless";
409
+ import { Markdown } from "react-native-nitro-markdown";
410
+
411
+ <Markdown
412
+ onLinkPress={(href) => {
413
+ if (href.startsWith("/")) {
414
+ router.push(href);
415
+ return false;
416
+ }
417
+ }}
418
+ >
419
+ {content}
420
+ </Markdown>;
418
421
  ```
419
422
 
420
- ### Theme utilities
423
+ ### Use headless mode to build search index
421
424
 
422
425
  ```tsx
423
426
  import {
424
- defaultMarkdownTheme,
425
- minimalMarkdownTheme,
426
- mergeThemes,
427
- } from "react-native-nitro-markdown";
428
- ```
429
-
430
- ### Types
427
+ parseMarkdown,
428
+ getFlattenedText,
429
+ } from "react-native-nitro-markdown/headless";
431
430
 
432
- ```tsx
433
- import type {
434
- CustomRenderers,
435
- NodeStyleOverrides,
436
- MarkdownTheme,
437
- PartialMarkdownTheme,
438
- } from "react-native-nitro-markdown";
431
+ const ast = parseMarkdown(content);
432
+ const searchableText = getFlattenedText(ast);
439
433
  ```
440
434
 
441
- ---
442
-
443
- ## Supported Node Types
444
-
445
- You can customize any of these node types with `renderers` or `styles`.
446
-
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`
435
+ ### Minimal styling baseline
448
436
 
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.
437
+ ```tsx
438
+ import { Markdown } from "react-native-nitro-markdown";
456
439
 
457
- ```ts
458
- export interface MarkdownNode {
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";
487
- content?: string;
488
- children?: MarkdownNode[];
489
- level?: number;
490
- href?: string;
491
- title?: string;
492
- alt?: string;
493
- language?: string;
494
- ordered?: boolean;
495
- start?: number;
496
- checked?: boolean;
497
- align?: string;
498
- isHeader?: boolean;
499
- }
440
+ <Markdown
441
+ stylingStrategy="minimal"
442
+ theme={{
443
+ colors: {
444
+ text: "#0f172a",
445
+ link: "#1d4ed8",
446
+ },
447
+ }}
448
+ >
449
+ {content}
450
+ </Markdown>;
500
451
  ```
501
452
 
502
- ---
453
+ ## Performance Guidance
503
454
 
504
- ## Troubleshooting
455
+ - For streaming text, prefer `updateStrategy="raf"`.
456
+ - If you use interval strategy, `updateIntervalMs` between `50` and `100` is a good baseline.
457
+ - Batch `session.append(...)` calls instead of appending one character at a time.
458
+ - Use the headless API if you do not need built-in renderers.
505
459
 
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.
460
+ ## Troubleshooting
510
461
 
511
- ---
462
+ - Math appears as plain code-style fallback:
463
+ - Install `react-native-mathjax-svg` and `react-native-svg`.
464
+ - iOS native build issues after install:
465
+ - Run `pod install` in your iOS project.
466
+ - Expo app does not load native module:
467
+ - Use a development build (`expo prebuild` + `expo run`), not Expo Go.
468
+ - Android heading font weight looks wrong:
469
+ - Set `theme.headingWeight` explicitly (for custom fonts without bold variants, use `"normal"`).
512
470
 
513
471
  ## Contributing
514
472
 
515
- See `CONTRIBUTING.md` for the workflow and development commands.
473
+ See `/Users/jota/Workspace/Projects/RN-Packages/react-native-nitro-markdown/CONTRIBUTING.md`.
516
474
 
517
475
  ## License
518
476