react-native-nitro-markdown 0.5.4 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,9 +1,14 @@
1
+ # react-native-nitro-markdown
2
+
3
+ [![npm](https://img.shields.io/badge/npm-v0.5.6-orange?style=flat-square)](https://www.npmjs.com/package/react-native-nitro-markdown)
4
+ [![license](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](./LICENSE)
5
+ [![react-native](https://img.shields.io/badge/react--native-%3E%3D0.75-1677a4?style=flat-square)](https://reactnative.dev/)
6
+ [![nitro-modules](https://img.shields.io/badge/nitro--modules-%3E%3D0.35.5-black?style=flat-square)](https://github.com/mrousavy/nitro)
7
+
1
8
  <p align="center">
2
- <img src="./readme/demo.gif" alt="react-native-nitro-markdown demo" width="400" />
9
+ <img src="https://raw.githubusercontent.com/JoaoPauloCMarra/react-native-nitro-markdown/main/readme/demo.gif" alt="react-native-nitro-markdown demo" width="400" />
3
10
  </p>
4
11
 
5
- # react-native-nitro-markdown
6
-
7
12
  Native Markdown parsing and rendering for React Native, powered by [md4c](https://github.com/mity/md4c) (C++) through [Nitro Modules](https://github.com/mrousavy/nitro) (JSI).
8
13
 
9
14
  ## Features
@@ -24,7 +29,7 @@ Native Markdown parsing and rendering for React Native, powered by [md4c](https:
24
29
  | Dependency | Version |
25
30
  |---|---|
26
31
  | React Native | `>=0.75.0` |
27
- | react-native-nitro-modules | `>=0.35.4` |
32
+ | react-native-nitro-modules | `>=0.35.5` |
28
33
  | react-native-mathjax-svg *(optional)* | `>=0.9.0` |
29
34
  | react-native-svg *(optional, for math)* | `>=13.0.0` |
30
35
 
@@ -103,6 +108,7 @@ import { Markdown } from "react-native-nitro-markdown";
103
108
  | `options` | `ParserOptions` | -- | Parser flags (`gfm`, `math`, `html`) |
104
109
  | `plugins` | `MarkdownPlugin[]` | -- | Plugin hooks (`beforeParse`, `afterParse`) |
105
110
  | `sourceAst` | `MarkdownNode` | -- | Pre-parsed AST; skips native parse when provided |
111
+ | `parseCache` | `boolean` | `true` | Enable internal parse result caching for repeated inputs |
106
112
  | `astTransform` | `AstTransform` | -- | Post-parse AST rewrite before render |
107
113
  | `renderers` | `CustomRenderers` | `{}` | Per-node custom renderer overrides |
108
114
  | `theme` | `PartialMarkdownTheme` | `defaultMarkdownTheme` | Theme token overrides |
@@ -121,6 +127,10 @@ import { Markdown } from "react-native-nitro-markdown";
121
127
 
122
128
  **Pipeline order:** `beforeParse` plugins (by priority desc) -> parse/sourceAst -> `afterParse` plugins (by priority desc) -> `astTransform` -> render.
123
129
 
130
+ When `sourceAst` is provided, `beforeParse` plugins are skipped because no source string is parsed. `afterParse` plugins still run on the provided AST.
131
+
132
+ `parseCache` defaults to `true` and caches parse results for repeated markdown inputs. Set `parseCache={false}` to force a fresh native parse on each input change. This flag has no effect when `sourceAst` is provided.
133
+
124
134
  ### `<MarkdownStream>`
125
135
 
126
136
  Renders markdown from a streaming session. Extends `MarkdownProps` (minus `children`).
@@ -406,6 +416,8 @@ const ast = parseMarkdownWithOptions(content, {
406
416
  <Markdown sourceAst={ast}>{"ignored when sourceAst is provided"}</Markdown>;
407
417
  ```
408
418
 
419
+ With `sourceAst`, `beforeParse` plugins are skipped, while `afterParse` and `astTransform` still apply.
420
+
409
421
  ### Virtualization (large documents)
410
422
 
411
423
  ```tsx
@@ -502,6 +514,11 @@ The `apps/example` directory contains a full demo app with these screens:
502
514
  | Custom | `app/render-custom.tsx` | HTML AST rendering, custom renderers, AST transform |
503
515
  | Stream | `app/render-stream.tsx` | Live streaming with token append |
504
516
 
517
+ ## Release Checks
518
+
519
+ - `bun run harness` runs lint, typecheck, JS coverage, benchmark checks, and C++ coverage.
520
+ - `bun run publish-package -- --dry-run --yes` validates release docs, runs publish verification, builds the package, and performs an npm dry run.
521
+
505
522
  ## Contributing
506
523
 
507
524
  See [CONTRIBUTING.md](./CONTRIBUTING.md).
@@ -30,6 +30,7 @@ target_include_directories(MD4CCore PUBLIC
30
30
  # Preprocessor definitions
31
31
  target_compile_definitions(MD4CCore PRIVATE
32
32
  MD4C_USE_UTF8=1
33
+ NITRO_MARKDOWN_TESTING=1
33
34
  )
34
35
 
35
36
  # Create the test executable
@@ -43,4 +44,4 @@ target_link_libraries(MD4CParserTest PRIVATE MD4CCore)
43
44
  # Include directories for the test
44
45
  target_include_directories(MD4CParserTest PRIVATE
45
46
  "${CPP_ROOT}/core"
46
- )
47
+ )
@@ -489,6 +489,14 @@ MD4CParser::MD4CParser() : impl_(std::make_unique<Impl>()) {}
489
489
  MD4CParser::~MD4CParser() = default;
490
490
 
491
491
  std::shared_ptr<MarkdownNode> MD4CParser::parse(const std::string& markdown, const ParserOptions& options) {
492
+ return parseWithFlags(markdown, options, 0);
493
+ }
494
+
495
+ std::shared_ptr<MarkdownNode> MD4CParser::parseWithFlags(
496
+ const std::string& markdown,
497
+ const ParserOptions& options,
498
+ unsigned int extraFlags
499
+ ) {
492
500
  impl_->reset();
493
501
  impl_->inputText = markdown.c_str();
494
502
  size_t inputSize = clampInputSize(markdown.size());
@@ -506,6 +514,7 @@ std::shared_ptr<MarkdownNode> MD4CParser::parse(const std::string& markdown, con
506
514
  if (options.math) {
507
515
  flags |= MD_FLAG_LATEXMATHSPANS;
508
516
  }
517
+ flags |= extraFlags;
509
518
 
510
519
  MD_PARSER parser = {
511
520
  0,
@@ -533,4 +542,44 @@ std::shared_ptr<MarkdownNode> MD4CParser::parse(const std::string& markdown, con
533
542
  return impl_->root;
534
543
  }
535
544
 
545
+ #ifdef NITRO_MARKDOWN_TESTING
546
+ std::shared_ptr<MarkdownNode> MD4CParser::parseWithExtraFlagsForTest(
547
+ const std::string& markdown,
548
+ const ParserOptions& options,
549
+ unsigned int extraFlags
550
+ ) {
551
+ return parseWithFlags(markdown, options, extraFlags);
552
+ }
553
+
554
+ int MD4CParser::enterBlockNullUserdataForTest() {
555
+ return Impl::enterBlock(MD_BLOCK_DOC, nullptr, 0, nullptr);
556
+ }
557
+
558
+ int MD4CParser::leaveBlockNullUserdataForTest() {
559
+ return Impl::leaveBlock(MD_BLOCK_DOC, nullptr, 0, nullptr);
560
+ }
561
+
562
+ int MD4CParser::enterSpanNullUserdataForTest() {
563
+ return Impl::enterSpan(MD_SPAN_EM, nullptr, 0, nullptr);
564
+ }
565
+
566
+ int MD4CParser::leaveSpanNullUserdataForTest() {
567
+ return Impl::leaveSpan(MD_SPAN_EM, nullptr, 0, nullptr);
568
+ }
569
+
570
+ int MD4CParser::textNullUserdataForTest() {
571
+ return Impl::text(MD_TEXT_NORMAL, "x", 1, nullptr);
572
+ }
573
+
574
+ int MD4CParser::offsetBeforeBaseForTest() {
575
+ char buffer[2] = {'a', 'b'};
576
+ return safeOffset(buffer, buffer + 1, 1);
577
+ }
578
+
579
+ int MD4CParser::offsetPastBaseForTest() {
580
+ char buffer[2] = {'a', 'b'};
581
+ return safeOffset(buffer + 1, buffer, 0);
582
+ }
583
+ #endif
584
+
536
585
  } // namespace NitroMarkdown
@@ -19,6 +19,18 @@ public:
19
19
  std::shared_ptr<MarkdownNode> parse(const std::string& markdown, const ParserOptions& options);
20
20
 
21
21
  #ifdef NITRO_MARKDOWN_TESTING
22
+ std::shared_ptr<MarkdownNode> parseWithExtraFlagsForTest(
23
+ const std::string& markdown,
24
+ const ParserOptions& options,
25
+ unsigned int extraFlags
26
+ );
27
+ static int enterBlockNullUserdataForTest();
28
+ static int leaveBlockNullUserdataForTest();
29
+ static int enterSpanNullUserdataForTest();
30
+ static int leaveSpanNullUserdataForTest();
31
+ static int textNullUserdataForTest();
32
+ static int offsetBeforeBaseForTest();
33
+ static int offsetPastBaseForTest();
22
34
  static size_t clampInputSizeForTest(size_t inputSize) {
23
35
  size_t maxSize = static_cast<size_t>(std::numeric_limits<MD_SIZE>::max());
24
36
  return inputSize > maxSize ? maxSize : inputSize;
@@ -27,6 +39,11 @@ public:
27
39
 
28
40
  private:
29
41
  class Impl;
42
+ std::shared_ptr<MarkdownNode> parseWithFlags(
43
+ const std::string& markdown,
44
+ const ParserOptions& options,
45
+ unsigned int extraFlags
46
+ );
30
47
  std::unique_ptr<Impl> impl_;
31
48
  };
32
49
 
@@ -29,6 +29,16 @@ var _reactNativeNitroModules = require("react-native-nitro-modules");
29
29
  * Each node has a type and optional properties depending on the node type.
30
30
  */
31
31
 
32
+ const createEmptyDocument = () => ({
33
+ type: "document",
34
+ children: []
35
+ });
36
+ function reportNativeParserFailure(methodName, error) {
37
+ if (__DEV__) {
38
+ // eslint-disable-next-line no-console
39
+ console.error(`[NitroMarkdown] ${methodName}: native parser failed.`, error);
40
+ }
41
+ }
32
42
  let MarkdownParserModule = exports.MarkdownParserModule = null;
33
43
  try {
34
44
  exports.MarkdownParserModule = MarkdownParserModule = _reactNativeNitroModules.NitroModules.createHybridObject("MarkdownParser");
@@ -56,17 +66,19 @@ function parseMarkdown(text, options) {
56
66
  return parseMarkdownWithOptions(text, options);
57
67
  }
58
68
  if (MarkdownParserModule != null && typeof MarkdownParserModule.parse === "function") {
59
- const jsonStr = MarkdownParserModule.parse(text);
60
- return JSON.parse(jsonStr);
69
+ try {
70
+ const jsonStr = MarkdownParserModule.parse(text);
71
+ return JSON.parse(jsonStr);
72
+ } catch (error) {
73
+ reportNativeParserFailure("parseMarkdown", error);
74
+ return createEmptyDocument();
75
+ }
61
76
  }
62
77
  if (__DEV__) {
63
78
  // eslint-disable-next-line no-console
64
79
  console.error("[NitroMarkdown] parseMarkdown: native parser unavailable — check installation.");
65
80
  }
66
- return {
67
- type: "document",
68
- children: []
69
- };
81
+ return createEmptyDocument();
70
82
  }
71
83
 
72
84
  /**
@@ -77,17 +89,19 @@ function parseMarkdown(text, options) {
77
89
  */
78
90
  function parseMarkdownWithOptions(text, options) {
79
91
  if (MarkdownParserModule != null && typeof MarkdownParserModule.parseWithOptions === "function") {
80
- const jsonStr = MarkdownParserModule.parseWithOptions(text, options);
81
- return JSON.parse(jsonStr);
92
+ try {
93
+ const jsonStr = MarkdownParserModule.parseWithOptions(text, options);
94
+ return JSON.parse(jsonStr);
95
+ } catch (error) {
96
+ reportNativeParserFailure("parseMarkdownWithOptions", error);
97
+ return createEmptyDocument();
98
+ }
82
99
  }
83
100
  if (__DEV__) {
84
101
  // eslint-disable-next-line no-console
85
102
  console.error("[NitroMarkdown] parseMarkdownWithOptions: native parser unavailable — check installation.");
86
103
  }
87
- return {
88
- type: "document",
89
- children: []
90
- };
104
+ return createEmptyDocument();
91
105
  }
92
106
 
93
107
  /**
@@ -96,7 +110,11 @@ function parseMarkdownWithOptions(text, options) {
96
110
  */
97
111
  function extractPlainText(text) {
98
112
  if (MarkdownParserModule != null && typeof MarkdownParserModule.extractPlainText === "function") {
99
- return MarkdownParserModule.extractPlainText(text);
113
+ try {
114
+ return MarkdownParserModule.extractPlainText(text);
115
+ } catch (error) {
116
+ reportNativeParserFailure("extractPlainText", error);
117
+ }
100
118
  }
101
119
  return getFlattenedText(parseMarkdown(text));
102
120
  }
@@ -106,7 +124,11 @@ function extractPlainText(text) {
106
124
  */
107
125
  function extractPlainTextWithOptions(text, options) {
108
126
  if (MarkdownParserModule != null && typeof MarkdownParserModule.extractPlainTextWithOptions === "function") {
109
- return MarkdownParserModule.extractPlainTextWithOptions(text, options);
127
+ try {
128
+ return MarkdownParserModule.extractPlainTextWithOptions(text, options);
129
+ } catch (error) {
130
+ reportNativeParserFailure("extractPlainTextWithOptions", error);
131
+ }
110
132
  }
111
133
  return getFlattenedText(parseMarkdownWithOptions(text, options));
112
134
  }
@@ -130,7 +152,8 @@ const getFlattenedText = node => {
130
152
  return node.content ?? "";
131
153
  }
132
154
  if (node.type === "code_block" || node.type === "math_block" || node.type === "html_block") {
133
- return (node.content ?? "").trim() + "\n\n";
155
+ const blockContent = node.content ?? node.children?.map(getFlattenedText).join("") ?? "";
156
+ return blockContent.trim() + "\n\n";
134
157
  }
135
158
  if (node.type === "line_break") return "\n";
136
159
  if (node.type === "soft_break") return " ";
@@ -1 +1 @@
1
- {"version":3,"names":["_reactNativeNitroModules","require","MarkdownParserModule","exports","NitroModules","createHybridObject","e","__DEV__","console","error","parseMarkdown","text","options","parseMarkdownWithOptions","parse","jsonStr","JSON","type","children","parseWithOptions","extractPlainText","getFlattenedText","extractPlainTextWithOptions","getTextContent","node","content","map","join","trim","alt","title","childrenText","stripSourceOffsets","beg","_beg","end","_end","rest"],"sourceRoot":"../../src","sources":["headless.ts"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["_reactNativeNitroModules","require","createEmptyDocument","type","children","reportNativeParserFailure","methodName","error","__DEV__","console","MarkdownParserModule","exports","NitroModules","createHybridObject","e","parseMarkdown","text","options","parseMarkdownWithOptions","parse","jsonStr","JSON","parseWithOptions","extractPlainText","getFlattenedText","extractPlainTextWithOptions","getTextContent","node","content","map","join","blockContent","trim","alt","title","childrenText","stripSourceOffsets","beg","_beg","end","_end","rest"],"sourceRoot":"../../src","sources":["headless.ts"],"mappings":"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","ignoreList":[]}
@@ -103,14 +103,17 @@ const getCachedParsedAst = (text, options) => {
103
103
  return parseWithNativeParser(text, options);
104
104
  }
105
105
  const cacheKey = `${getParserOptionsKey(options)}|${text.length}|${hashString(text)}`;
106
- const cachedNode = parseAstCache.get(cacheKey);
107
- if (cachedNode) {
106
+ const cachedEntry = parseAstCache.get(cacheKey);
107
+ if (cachedEntry?.text === text) {
108
108
  parseAstCache.delete(cacheKey);
109
- parseAstCache.set(cacheKey, cachedNode);
110
- return cloneMarkdownNode(cachedNode);
109
+ parseAstCache.set(cacheKey, cachedEntry);
110
+ return cloneMarkdownNode(cachedEntry.ast);
111
111
  }
112
112
  const parsedNode = parseWithNativeParser(text, options);
113
- parseAstCache.set(cacheKey, parsedNode);
113
+ parseAstCache.set(cacheKey, {
114
+ text,
115
+ ast: parsedNode
116
+ });
114
117
  if (parseAstCache.size > MAX_PARSE_CACHE_ENTRIES) {
115
118
  const oldestCacheKey = parseAstCache.keys().next().value;
116
119
  if (typeof oldestCacheKey === "string") {
@@ -119,13 +122,18 @@ const getCachedParsedAst = (text, options) => {
119
122
  }
120
123
  return cloneMarkdownNode(parsedNode);
121
124
  };
122
- const applyBeforeParsePlugins = (markdown, plugins, onError) => {
125
+ const sortPluginsByPriority = plugins => {
123
126
  if (!plugins || plugins.length === 0) {
127
+ return undefined;
128
+ }
129
+ return [...plugins].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
130
+ };
131
+ const applyBeforeParsePlugins = (markdown, sortedPlugins, onError) => {
132
+ if (!sortedPlugins || sortedPlugins.length === 0) {
124
133
  return markdown;
125
134
  }
126
- const sorted = [...plugins].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
127
135
  let nextMarkdown = markdown;
128
- for (const plugin of sorted) {
136
+ for (const plugin of sortedPlugins) {
129
137
  if (!plugin.beforeParse) continue;
130
138
  try {
131
139
  const transformed = plugin.beforeParse(nextMarkdown);
@@ -140,13 +148,12 @@ const applyBeforeParsePlugins = (markdown, plugins, onError) => {
140
148
  }
141
149
  return nextMarkdown;
142
150
  };
143
- const applyAfterParsePlugins = (ast, plugins, onError) => {
144
- if (!plugins || plugins.length === 0) {
151
+ const applyAfterParsePlugins = (ast, sortedPlugins, onError) => {
152
+ if (!sortedPlugins || sortedPlugins.length === 0) {
145
153
  return ast;
146
154
  }
147
- const sorted = [...plugins].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
148
155
  let nextAst = ast;
149
- for (const plugin of sorted) {
156
+ for (const plugin of sortedPlugins) {
150
157
  if (!plugin.afterParse) continue;
151
158
  try {
152
159
  const transformed = plugin.afterParse(nextAst);
@@ -166,6 +173,7 @@ const Markdown = ({
166
173
  options,
167
174
  plugins,
168
175
  sourceAst,
176
+ parseCache = true,
169
177
  astTransform,
170
178
  renderers = EMPTY_RENDERERS,
171
179
  theme: userTheme,
@@ -191,14 +199,15 @@ const Markdown = ({
191
199
  onErrorRef.current = onError;
192
200
  const parseResult = (0, _react.useMemo)(() => {
193
201
  try {
194
- const markdownToParse = applyBeforeParsePlugins(children, plugins, onErrorRef.current);
202
+ const sortedPlugins = sortPluginsByPriority(plugins);
203
+ const markdownToParse = sourceAst ? children : applyBeforeParsePlugins(children, sortedPlugins, onErrorRef.current);
195
204
  const parserOptions = normalizeParserOptions({
196
205
  gfm: parserOptionGfm,
197
206
  math: parserOptionMath,
198
207
  html: parserOptionHtml
199
208
  });
200
- let parsedAst = sourceAst ? cloneMarkdownNode(sourceAst) : getCachedParsedAst(markdownToParse, parserOptions);
201
- parsedAst = applyAfterParsePlugins(parsedAst, plugins, onErrorRef.current);
209
+ let parsedAst = sourceAst ? cloneMarkdownNode(sourceAst) : parseCache ? getCachedParsedAst(markdownToParse, parserOptions) : parseWithNativeParser(markdownToParse, parserOptions);
210
+ parsedAst = applyAfterParsePlugins(parsedAst, sortedPlugins, onErrorRef.current);
202
211
  let ast = parsedAst;
203
212
  if (astTransform) {
204
213
  try {
@@ -220,7 +229,7 @@ const Markdown = ({
220
229
  ast: null
221
230
  };
222
231
  }
223
- }, [children, parserOptionGfm, parserOptionMath, parserOptionHtml, sourceAst, astTransform, plugins]);
232
+ }, [children, parserOptionGfm, parserOptionMath, parserOptionHtml, sourceAst, parseCache, astTransform, plugins]);
224
233
  /* eslint-enable react-hooks/refs */
225
234
 
226
235
  (0, _react.useEffect)(() => {
@@ -409,23 +418,22 @@ const NodeRendererComponent = ({
409
418
  return result;
410
419
  }
411
420
  }
412
- const nodeStyleOverride = nodeStyles?.[node.type];
413
421
  switch (node.type) {
414
422
  case "document":
415
423
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
416
- style: [baseStyles.document, nodeStyleOverride],
424
+ style: [baseStyles.document, nodeStyles?.document],
417
425
  children: renderChildren(node.children, false, false)
418
426
  });
419
427
  case "heading":
420
428
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_heading.Heading, {
421
429
  level: node.level ?? 1,
422
- style: nodeStyleOverride,
430
+ style: nodeStyles?.heading,
423
431
  children: renderChildren(node.children, inListItem, true)
424
432
  });
425
433
  case "paragraph":
426
434
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_paragraph.Paragraph, {
427
435
  inListItem: inListItem,
428
- style: nodeStyleOverride,
436
+ style: nodeStyles?.paragraph,
429
437
  children: renderChildren(node.children, inListItem, false)
430
438
  });
431
439
  case "text":
@@ -435,28 +443,28 @@ const NodeRendererComponent = ({
435
443
  });
436
444
  }
437
445
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
438
- style: [baseStyles.text, nodeStyleOverride],
446
+ style: [baseStyles.text, nodeStyles?.text],
439
447
  children: node.content
440
448
  });
441
449
  case "bold":
442
450
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
443
- style: [baseStyles.bold, nodeStyleOverride],
451
+ style: [baseStyles.bold, nodeStyles?.bold],
444
452
  children: renderChildren(node.children, inListItem, true)
445
453
  });
446
454
  case "italic":
447
455
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
448
- style: [baseStyles.italic, nodeStyleOverride],
456
+ style: [baseStyles.italic, nodeStyles?.italic],
449
457
  children: renderChildren(node.children, inListItem, true)
450
458
  });
451
459
  case "strikethrough":
452
460
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
453
- style: [baseStyles.strikethrough, nodeStyleOverride],
461
+ style: [baseStyles.strikethrough, nodeStyles?.strikethrough],
454
462
  children: renderChildren(node.children, inListItem, true)
455
463
  });
456
464
  case "link":
457
465
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_link.Link, {
458
466
  href: node.href ?? "",
459
- style: nodeStyleOverride,
467
+ style: nodeStyles?.link,
460
468
  children: renderChildren(node.children, inListItem, true)
461
469
  });
462
470
  case "image":
@@ -465,27 +473,27 @@ const NodeRendererComponent = ({
465
473
  title: node.title,
466
474
  alt: node.alt,
467
475
  Renderer: NodeRenderer,
468
- style: nodeStyleOverride
476
+ style: nodeStyles?.image
469
477
  });
470
478
  case "code_inline":
471
479
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_code.InlineCode, {
472
- style: nodeStyleOverride,
480
+ style: nodeStyles?.code_inline,
473
481
  children: node.content
474
482
  });
475
483
  case "code_block":
476
484
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_code.CodeBlock, {
477
485
  language: node.language,
478
486
  content: (0, _headless.getTextContent)(node),
479
- style: nodeStyleOverride
487
+ style: nodeStyles?.code_block
480
488
  });
481
489
  case "blockquote":
482
490
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_blockquote.Blockquote, {
483
- style: nodeStyleOverride,
491
+ style: nodeStyles?.blockquote,
484
492
  children: renderChildren(node.children, inListItem, false)
485
493
  });
486
494
  case "horizontal_rule":
487
495
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_horizontalRule.HorizontalRule, {
488
- style: nodeStyleOverride
496
+ style: nodeStyles?.horizontal_rule
489
497
  });
490
498
  case "line_break":
491
499
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
@@ -502,20 +510,20 @@ const NodeRendererComponent = ({
502
510
  mathContent = mathContent.replace(/^\$+|\$+$/g, "").trim();
503
511
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_math.MathInline, {
504
512
  content: mathContent,
505
- style: nodeStyleOverride
513
+ style: nodeStyles?.math_inline
506
514
  });
507
515
  }
508
516
  case "math_block":
509
517
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_math.MathBlock, {
510
518
  content: (0, _headless.getTextContent)(node),
511
- style: nodeStyleOverride
519
+ style: nodeStyles?.math_block
512
520
  });
513
521
  case "list":
514
522
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_list.List, {
515
523
  ordered: node.ordered ?? false,
516
524
  start: node.start,
517
525
  depth: depth,
518
- style: nodeStyleOverride,
526
+ style: nodeStyles?.list,
519
527
  children: node.children?.map((child, index) => {
520
528
  if (child.type === "task_list_item") {
521
529
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(NodeRenderer, {
@@ -545,14 +553,14 @@ const NodeRendererComponent = ({
545
553
  case "task_list_item":
546
554
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_list.TaskListItem, {
547
555
  checked: node.checked ?? false,
548
- style: nodeStyleOverride,
556
+ style: nodeStyles?.task_list_item,
549
557
  children: renderChildren(node.children, true, false)
550
558
  });
551
559
  case "table":
552
560
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_table.TableRenderer, {
553
561
  node: node,
554
562
  Renderer: NodeRenderer,
555
- style: nodeStyleOverride
563
+ style: nodeStyles?.table
556
564
  });
557
565
  case "table_head":
558
566
  case "table_body":