react-native-nitro-markdown 0.4.3 → 0.5.1

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 (175) hide show
  1. package/README.md +417 -25
  2. package/android/src/main/java/com/margelo/nitro/com/nitromarkdown/HybridMarkdownSession.kt +46 -8
  3. package/android/src/main/java/com/nitromarkdown/NitroMarkdownPackage.kt +2 -1
  4. package/cpp/bindings/HybridMarkdownParser.cpp +216 -66
  5. package/cpp/bindings/HybridMarkdownParser.hpp +2 -0
  6. package/ios/HybridMarkdownSession.swift +51 -7
  7. package/lib/commonjs/MarkdownContext.js.map +1 -1
  8. package/lib/commonjs/headless.js +61 -5
  9. package/lib/commonjs/headless.js.map +1 -1
  10. package/lib/commonjs/index.js +9 -1
  11. package/lib/commonjs/index.js.map +1 -1
  12. package/lib/commonjs/markdown-stream.js +107 -13
  13. package/lib/commonjs/markdown-stream.js.map +1 -1
  14. package/lib/commonjs/markdown.js +191 -26
  15. package/lib/commonjs/markdown.js.map +1 -1
  16. package/lib/commonjs/renderers/code.js +21 -2
  17. package/lib/commonjs/renderers/code.js.map +1 -1
  18. package/lib/commonjs/renderers/table/cell-content.js +32 -0
  19. package/lib/commonjs/renderers/table/cell-content.js.map +1 -0
  20. package/lib/commonjs/renderers/table/index.js +310 -0
  21. package/lib/commonjs/renderers/table/index.js.map +1 -0
  22. package/lib/commonjs/renderers/table/table-reducer.js +29 -0
  23. package/lib/commonjs/renderers/table/table-reducer.js.map +1 -0
  24. package/lib/commonjs/renderers/table/table-utils.js +68 -0
  25. package/lib/commonjs/renderers/table/table-utils.js.map +1 -0
  26. package/lib/commonjs/renderers/table/types.js +6 -0
  27. package/lib/commonjs/renderers/table/types.js.map +1 -0
  28. package/lib/commonjs/renderers/table.js +6 -306
  29. package/lib/commonjs/renderers/table.js.map +1 -1
  30. package/lib/commonjs/theme.js +10 -1
  31. package/lib/commonjs/theme.js.map +1 -1
  32. package/lib/commonjs/use-markdown-stream.js +9 -1
  33. package/lib/commonjs/use-markdown-stream.js.map +1 -1
  34. package/lib/commonjs/utils/code-highlight.js +101 -0
  35. package/lib/commonjs/utils/code-highlight.js.map +1 -0
  36. package/lib/commonjs/utils/incremental-ast.js +153 -0
  37. package/lib/commonjs/utils/incremental-ast.js.map +1 -0
  38. package/lib/module/MarkdownContext.js.map +1 -1
  39. package/lib/module/headless.js +56 -4
  40. package/lib/module/headless.js.map +1 -1
  41. package/lib/module/index.js +1 -0
  42. package/lib/module/index.js.map +1 -1
  43. package/lib/module/markdown-stream.js +108 -14
  44. package/lib/module/markdown-stream.js.map +1 -1
  45. package/lib/module/markdown.js +193 -28
  46. package/lib/module/markdown.js.map +1 -1
  47. package/lib/module/renderers/code.js +21 -2
  48. package/lib/module/renderers/code.js.map +1 -1
  49. package/lib/module/renderers/table/cell-content.js +27 -0
  50. package/lib/module/renderers/table/cell-content.js.map +1 -0
  51. package/lib/module/renderers/table/index.js +305 -0
  52. package/lib/module/renderers/table/index.js.map +1 -0
  53. package/lib/module/renderers/table/table-reducer.js +24 -0
  54. package/lib/module/renderers/table/table-reducer.js.map +1 -0
  55. package/lib/module/renderers/table/table-utils.js +62 -0
  56. package/lib/module/renderers/table/table-utils.js.map +1 -0
  57. package/lib/module/renderers/table/types.js +4 -0
  58. package/lib/module/renderers/table/types.js.map +1 -0
  59. package/lib/module/renderers/table.js +1 -305
  60. package/lib/module/renderers/table.js.map +1 -1
  61. package/lib/module/theme.js +10 -1
  62. package/lib/module/theme.js.map +1 -1
  63. package/lib/module/use-markdown-stream.js +9 -1
  64. package/lib/module/use-markdown-stream.js.map +1 -1
  65. package/lib/module/utils/code-highlight.js +97 -0
  66. package/lib/module/utils/code-highlight.js.map +1 -0
  67. package/lib/module/utils/incremental-ast.js +147 -0
  68. package/lib/module/utils/incremental-ast.js.map +1 -0
  69. package/lib/typescript/commonjs/Markdown.nitro.d.ts +2 -0
  70. package/lib/typescript/commonjs/Markdown.nitro.d.ts.map +1 -1
  71. package/lib/typescript/commonjs/MarkdownContext.d.ts +6 -0
  72. package/lib/typescript/commonjs/MarkdownContext.d.ts.map +1 -1
  73. package/lib/typescript/commonjs/headless.d.ts +18 -0
  74. package/lib/typescript/commonjs/headless.d.ts.map +1 -1
  75. package/lib/typescript/commonjs/index.d.ts +4 -0
  76. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  77. package/lib/typescript/commonjs/markdown-stream.d.ts +6 -1
  78. package/lib/typescript/commonjs/markdown-stream.d.ts.map +1 -1
  79. package/lib/typescript/commonjs/markdown.d.ts +77 -1
  80. package/lib/typescript/commonjs/markdown.d.ts.map +1 -1
  81. package/lib/typescript/commonjs/renderers/code.d.ts.map +1 -1
  82. package/lib/typescript/commonjs/renderers/table/cell-content.d.ts +15 -0
  83. package/lib/typescript/commonjs/renderers/table/cell-content.d.ts.map +1 -0
  84. package/lib/typescript/commonjs/renderers/table/index.d.ts +11 -0
  85. package/lib/typescript/commonjs/renderers/table/index.d.ts.map +1 -0
  86. package/lib/typescript/commonjs/renderers/table/table-reducer.d.ts +5 -0
  87. package/lib/typescript/commonjs/renderers/table/table-reducer.d.ts.map +1 -0
  88. package/lib/typescript/commonjs/renderers/table/table-utils.d.ts +10 -0
  89. package/lib/typescript/commonjs/renderers/table/table-utils.d.ts.map +1 -0
  90. package/lib/typescript/commonjs/renderers/table/types.d.ts +24 -0
  91. package/lib/typescript/commonjs/renderers/table/types.d.ts.map +1 -0
  92. package/lib/typescript/commonjs/renderers/table.d.ts +1 -11
  93. package/lib/typescript/commonjs/renderers/table.d.ts.map +1 -1
  94. package/lib/typescript/commonjs/specs/MarkdownSession.nitro.d.ts +14 -2
  95. package/lib/typescript/commonjs/specs/MarkdownSession.nitro.d.ts.map +1 -1
  96. package/lib/typescript/commonjs/theme.d.ts +18 -2
  97. package/lib/typescript/commonjs/theme.d.ts.map +1 -1
  98. package/lib/typescript/commonjs/use-markdown-stream.d.ts +4 -0
  99. package/lib/typescript/commonjs/use-markdown-stream.d.ts.map +1 -1
  100. package/lib/typescript/commonjs/utils/code-highlight.d.ts +8 -0
  101. package/lib/typescript/commonjs/utils/code-highlight.d.ts.map +1 -0
  102. package/lib/typescript/commonjs/utils/incremental-ast.d.ts +12 -0
  103. package/lib/typescript/commonjs/utils/incremental-ast.d.ts.map +1 -0
  104. package/lib/typescript/module/Markdown.nitro.d.ts +2 -0
  105. package/lib/typescript/module/Markdown.nitro.d.ts.map +1 -1
  106. package/lib/typescript/module/MarkdownContext.d.ts +6 -0
  107. package/lib/typescript/module/MarkdownContext.d.ts.map +1 -1
  108. package/lib/typescript/module/headless.d.ts +18 -0
  109. package/lib/typescript/module/headless.d.ts.map +1 -1
  110. package/lib/typescript/module/index.d.ts +4 -0
  111. package/lib/typescript/module/index.d.ts.map +1 -1
  112. package/lib/typescript/module/markdown-stream.d.ts +6 -1
  113. package/lib/typescript/module/markdown-stream.d.ts.map +1 -1
  114. package/lib/typescript/module/markdown.d.ts +77 -1
  115. package/lib/typescript/module/markdown.d.ts.map +1 -1
  116. package/lib/typescript/module/renderers/code.d.ts.map +1 -1
  117. package/lib/typescript/module/renderers/table/cell-content.d.ts +15 -0
  118. package/lib/typescript/module/renderers/table/cell-content.d.ts.map +1 -0
  119. package/lib/typescript/module/renderers/table/index.d.ts +11 -0
  120. package/lib/typescript/module/renderers/table/index.d.ts.map +1 -0
  121. package/lib/typescript/module/renderers/table/table-reducer.d.ts +5 -0
  122. package/lib/typescript/module/renderers/table/table-reducer.d.ts.map +1 -0
  123. package/lib/typescript/module/renderers/table/table-utils.d.ts +10 -0
  124. package/lib/typescript/module/renderers/table/table-utils.d.ts.map +1 -0
  125. package/lib/typescript/module/renderers/table/types.d.ts +24 -0
  126. package/lib/typescript/module/renderers/table/types.d.ts.map +1 -0
  127. package/lib/typescript/module/renderers/table.d.ts +1 -11
  128. package/lib/typescript/module/renderers/table.d.ts.map +1 -1
  129. package/lib/typescript/module/specs/MarkdownSession.nitro.d.ts +14 -2
  130. package/lib/typescript/module/specs/MarkdownSession.nitro.d.ts.map +1 -1
  131. package/lib/typescript/module/theme.d.ts +18 -2
  132. package/lib/typescript/module/theme.d.ts.map +1 -1
  133. package/lib/typescript/module/use-markdown-stream.d.ts +4 -0
  134. package/lib/typescript/module/use-markdown-stream.d.ts.map +1 -1
  135. package/lib/typescript/module/utils/code-highlight.d.ts +8 -0
  136. package/lib/typescript/module/utils/code-highlight.d.ts.map +1 -0
  137. package/lib/typescript/module/utils/incremental-ast.d.ts +12 -0
  138. package/lib/typescript/module/utils/incremental-ast.d.ts.map +1 -0
  139. package/nitrogen/generated/android/NitroMarkdownOnLoad.cpp +38 -26
  140. package/nitrogen/generated/android/NitroMarkdownOnLoad.hpp +13 -4
  141. package/nitrogen/generated/android/c++/JFunc_void_double_double.hpp +75 -0
  142. package/nitrogen/generated/android/c++/JHybridMarkdownSessionSpec.cpp +49 -34
  143. package/nitrogen/generated/android/c++/JHybridMarkdownSessionSpec.hpp +25 -24
  144. package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/nitromarkdown/Func_void_double_double.kt +80 -0
  145. package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/nitromarkdown/HybridMarkdownSessionSpec.kt +34 -21
  146. package/nitrogen/generated/ios/NitroMarkdown-Swift-Cxx-Bridge.cpp +8 -0
  147. package/nitrogen/generated/ios/NitroMarkdown-Swift-Cxx-Bridge.hpp +31 -0
  148. package/nitrogen/generated/ios/c++/HybridMarkdownSessionSpecSwift.hpp +34 -2
  149. package/nitrogen/generated/ios/swift/Func_void_double_double.swift +46 -0
  150. package/nitrogen/generated/ios/swift/HybridMarkdownSessionSpec.swift +6 -2
  151. package/nitrogen/generated/ios/swift/HybridMarkdownSessionSpec_cxx.swift +57 -9
  152. package/nitrogen/generated/shared/c++/HybridMarkdownParserSpec.cpp +2 -0
  153. package/nitrogen/generated/shared/c++/HybridMarkdownParserSpec.hpp +2 -0
  154. package/nitrogen/generated/shared/c++/HybridMarkdownSessionSpec.cpp +4 -0
  155. package/nitrogen/generated/shared/c++/HybridMarkdownSessionSpec.hpp +6 -2
  156. package/package.json +9 -5
  157. package/react-native-nitro-markdown.podspec +1 -1
  158. package/src/Markdown.nitro.ts +2 -0
  159. package/src/MarkdownContext.ts +6 -0
  160. package/src/headless.ts +54 -4
  161. package/src/index.ts +10 -0
  162. package/src/markdown-stream.tsx +163 -15
  163. package/src/markdown.tsx +381 -26
  164. package/src/renderers/code.tsx +32 -3
  165. package/src/renderers/table/cell-content.tsx +38 -0
  166. package/src/renderers/table/index.tsx +419 -0
  167. package/src/renderers/table/table-reducer.ts +36 -0
  168. package/src/renderers/table/table-utils.ts +81 -0
  169. package/src/renderers/table/types.ts +24 -0
  170. package/src/renderers/table.tsx +1 -401
  171. package/src/specs/MarkdownSession.nitro.ts +16 -2
  172. package/src/theme.ts +29 -1
  173. package/src/use-markdown-stream.ts +10 -0
  174. package/src/utils/code-highlight.ts +102 -0
  175. package/src/utils/incremental-ast.ts +224 -0
@@ -1,401 +1 @@
1
- import {
2
- useEffect,
3
- useMemo,
4
- useRef,
5
- useReducer,
6
- useCallback,
7
- type FC,
8
- type ComponentType,
9
- } from "react";
10
- import {
11
- View,
12
- Text,
13
- StyleSheet,
14
- ScrollView,
15
- Platform,
16
- type StyleProp,
17
- type TextStyle,
18
- type ViewStyle,
19
- type LayoutChangeEvent,
20
- } from "react-native";
21
- import { useMarkdownContext, type NodeRendererProps } from "../MarkdownContext";
22
- import type { MarkdownNode } from "../headless";
23
- import type { MarkdownTheme } from "../theme";
24
-
25
- type TableData = {
26
- headers: MarkdownNode[];
27
- rows: MarkdownNode[][];
28
- alignments: (string | undefined)[];
29
- };
30
-
31
- const extractTableData = (node: MarkdownNode): TableData => {
32
- const headers: MarkdownNode[] = [];
33
- const rows: MarkdownNode[][] = [];
34
- const alignments: (string | undefined)[] = [];
35
-
36
- for (const child of node.children ?? []) {
37
- if (child.type === "table_head") {
38
- for (const row of child.children ?? []) {
39
- if (row.type === "table_row") {
40
- for (const cell of row.children ?? []) {
41
- headers.push(cell);
42
- alignments.push(cell.align);
43
- }
44
- }
45
- }
46
- } else if (child.type === "table_body") {
47
- for (const row of child.children ?? []) {
48
- if (row.type === "table_row") {
49
- const rowCells: MarkdownNode[] = [];
50
- for (const cell of row.children ?? []) {
51
- rowCells.push(cell);
52
- }
53
- rows.push(rowCells);
54
- }
55
- }
56
- }
57
- }
58
-
59
- return { headers, rows, alignments };
60
- };
61
-
62
- type TableRendererProps = {
63
- node: MarkdownNode;
64
- Renderer: ComponentType<NodeRendererProps>;
65
- style?: ViewStyle;
66
- };
67
-
68
- type ColumnWidthsAction = {
69
- type: "SET_WIDTHS";
70
- payload: number[];
71
- };
72
-
73
- const columnWidthsReducer = (state: number[], action: ColumnWidthsAction) => {
74
- if (action.type === "SET_WIDTHS") return action.payload;
75
- return state;
76
- };
77
-
78
- export const TableRenderer: FC<TableRendererProps> = ({
79
- node,
80
- Renderer,
81
- style,
82
- }) => {
83
- const { theme } = useMarkdownContext();
84
- const { headers, rows, alignments } = useMemo(
85
- () => extractTableData(node),
86
- [node],
87
- );
88
-
89
- const columnCount = headers.length;
90
- const styles = useMemo(() => createTableStyles(theme), [theme]);
91
-
92
- const [columnWidths, dispatch] = useReducer(columnWidthsReducer, []);
93
- const measuredWidths = useRef<Map<string, number>>(new Map());
94
- const measuredCells = useRef<Set<string>>(new Set());
95
- const widthsCalculated = useRef(false);
96
-
97
- const expectedCellKeys = useMemo(() => {
98
- const keys: string[] = [];
99
-
100
- headers.forEach((_, colIndex) => {
101
- keys.push(`header-${colIndex}`);
102
- });
103
-
104
- rows.forEach((row, rowIndex) => {
105
- row.forEach((_, colIndex) => {
106
- keys.push(`cell-${rowIndex}-${colIndex}`);
107
- });
108
- });
109
-
110
- return keys;
111
- }, [headers, rows]);
112
-
113
- useEffect(() => {
114
- measuredWidths.current.clear();
115
- measuredCells.current.clear();
116
- widthsCalculated.current = false;
117
- dispatch({ type: "SET_WIDTHS", payload: [] });
118
- }, [expectedCellKeys]);
119
-
120
- const onCellLayout = useCallback(
121
- (cellKey: string, width: number) => {
122
- if (widthsCalculated.current) return;
123
-
124
- measuredWidths.current.set(cellKey, width);
125
- if (!measuredCells.current.has(cellKey)) {
126
- measuredCells.current.add(cellKey);
127
- }
128
-
129
- if (measuredCells.current.size < expectedCellKeys.length) return;
130
-
131
- const allCellsMeasured = expectedCellKeys.every((key) =>
132
- measuredCells.current.has(key),
133
- );
134
- if (!allCellsMeasured) return;
135
-
136
- const maxWidths: number[] = new Array(columnCount).fill(0);
137
-
138
- for (let col = 0; col < columnCount; col++) {
139
- const headerWidth = measuredWidths.current.get(`header-${col}`);
140
- if (headerWidth && headerWidth > 0) {
141
- maxWidths[col] = Math.max(maxWidths[col], headerWidth);
142
- }
143
-
144
- for (let row = 0; row < rows.length; row++) {
145
- if (col >= rows[row].length) continue;
146
- const cellWidth = measuredWidths.current.get(`cell-${row}-${col}`);
147
- if (cellWidth && cellWidth > 0) {
148
- maxWidths[col] = Math.max(maxWidths[col], cellWidth);
149
- }
150
- }
151
-
152
- maxWidths[col] = Math.max(maxWidths[col] + 8, 60);
153
- }
154
-
155
- widthsCalculated.current = true;
156
- dispatch({ type: "SET_WIDTHS", payload: maxWidths });
157
- },
158
- [columnCount, expectedCellKeys, rows],
159
- );
160
-
161
- const getAlignment = (
162
- index: number,
163
- ): "flex-start" | "center" | "flex-end" => {
164
- const align = alignments[index];
165
- if (align === "center") return "center";
166
- if (align === "right") return "flex-end";
167
- return "flex-start";
168
- };
169
-
170
- if (columnCount === 0) return null;
171
-
172
- const hasWidths = columnWidths.length === columnCount;
173
-
174
- return (
175
- <View style={[styles.container, style]}>
176
- {!hasWidths && (
177
- <View style={styles.measurementContainer}>
178
- <View style={styles.measurementRow}>
179
- {headers.map((cell, colIndex) => (
180
- <View
181
- key={`measure-header-${colIndex}`}
182
- style={styles.measurementCell}
183
- onLayout={(e: LayoutChangeEvent) => {
184
- onCellLayout(
185
- `header-${colIndex}`,
186
- e.nativeEvent.layout.width,
187
- );
188
- }}
189
- >
190
- <CellContent node={cell} Renderer={Renderer} styles={styles} />
191
- </View>
192
- ))}
193
- </View>
194
- {rows.map((row, rowIndex) => (
195
- <View key={`measure-row-${rowIndex}`} style={styles.measurementRow}>
196
- {row.map((cell, colIndex) => (
197
- <View
198
- key={`measure-cell-${rowIndex}-${colIndex}`}
199
- style={styles.measurementCell}
200
- onLayout={(e: LayoutChangeEvent) => {
201
- onCellLayout(
202
- `cell-${rowIndex}-${colIndex}`,
203
- e.nativeEvent.layout.width,
204
- );
205
- }}
206
- >
207
- <CellContent
208
- node={cell}
209
- Renderer={Renderer}
210
- styles={styles}
211
- />
212
- </View>
213
- ))}
214
- </View>
215
- ))}
216
- </View>
217
- )}
218
-
219
- {hasWidths ? (
220
- <ScrollView
221
- horizontal
222
- showsHorizontalScrollIndicator
223
- style={styles.tableScroll}
224
- bounces={false}
225
- >
226
- <View style={styles.table}>
227
- <View style={styles.headerRow}>
228
- {headers.map((cell, colIndex) => (
229
- <View
230
- key={`header-${colIndex}`}
231
- style={[
232
- styles.headerCell,
233
- {
234
- width: columnWidths[colIndex],
235
- alignItems: getAlignment(colIndex),
236
- },
237
- colIndex === columnCount - 1 && styles.lastCell,
238
- ]}
239
- >
240
- <CellContent
241
- node={cell}
242
- Renderer={Renderer}
243
- styles={styles}
244
- textStyle={styles.headerText}
245
- />
246
- </View>
247
- ))}
248
- </View>
249
-
250
- {rows.map((row, rowIndex) => (
251
- <View
252
- key={`row-${rowIndex}`}
253
- style={[
254
- styles.bodyRow,
255
- rowIndex === rows.length - 1 && styles.lastRow,
256
- rowIndex % 2 === 1 && styles.oddRow,
257
- ]}
258
- >
259
- {row.map((cell, colIndex) => (
260
- <View
261
- key={`cell-${rowIndex}-${colIndex}`}
262
- style={[
263
- styles.bodyCell,
264
- {
265
- width: columnWidths[colIndex],
266
- alignItems: getAlignment(colIndex),
267
- },
268
- colIndex === columnCount - 1 && styles.lastCell,
269
- ]}
270
- >
271
- <CellContent
272
- node={cell}
273
- Renderer={Renderer}
274
- styles={styles}
275
- textStyle={styles.cellText}
276
- />
277
- </View>
278
- ))}
279
- </View>
280
- ))}
281
- </View>
282
- </ScrollView>
283
- ) : null}
284
- </View>
285
- );
286
- };
287
-
288
- const CellContent: FC<{
289
- node: MarkdownNode;
290
- Renderer: ComponentType<NodeRendererProps>;
291
- styles: ReturnType<typeof createTableStyles>;
292
- textStyle?: StyleProp<TextStyle>;
293
- }> = ({ node, Renderer, styles, textStyle }) => {
294
- if (!node.children || node.children.length === 0) {
295
- return <Text style={textStyle}>{node.content ?? ""}</Text>;
296
- }
297
-
298
- return (
299
- <View style={styles.cellContentWrapper}>
300
- {node.children.map((child, idx) => (
301
- <Renderer
302
- key={idx}
303
- node={child}
304
- depth={0}
305
- inListItem={false}
306
- parentIsText={false}
307
- />
308
- ))}
309
- </View>
310
- );
311
- };
312
-
313
- const createTableStyles = (theme: MarkdownTheme) => {
314
- const colors = theme?.colors || {};
315
- const borderRadius = theme?.borderRadius || { m: 8 };
316
-
317
- return StyleSheet.create({
318
- container: {
319
- marginVertical: theme.spacing.s,
320
- },
321
- measurementContainer: {
322
- position: "absolute",
323
- opacity: 0,
324
- pointerEvents: "none",
325
- left: -9999,
326
- },
327
- measurementRow: {
328
- flexDirection: "row",
329
- },
330
- measurementCell: {
331
- paddingVertical: 10,
332
- paddingHorizontal: 12,
333
- },
334
- tableScroll: {
335
- flexGrow: 0,
336
- },
337
- table: {
338
- borderRadius: borderRadius.m,
339
- overflow: "hidden",
340
- borderWidth: 1,
341
- borderColor: colors.tableBorder || "#374151",
342
- backgroundColor: colors.surface || "#111827",
343
- },
344
- headerRow: {
345
- flexDirection: "row",
346
- backgroundColor: colors.tableHeader || "#1f2937",
347
- borderBottomWidth: 1,
348
- borderBottomColor: colors.tableBorder || "#374151",
349
- },
350
- bodyRow: {
351
- flexDirection: "row",
352
- borderBottomWidth: 1,
353
- borderBottomColor: colors.tableBorder || "#374151",
354
- },
355
- oddRow: {
356
- backgroundColor: colors.tableRowOdd || "rgba(255,255,255,0.02)",
357
- },
358
- lastRow: {
359
- borderBottomWidth: 0,
360
- },
361
- headerCell: {
362
- flexShrink: 0,
363
- paddingVertical: 10,
364
- paddingHorizontal: 12,
365
- minWidth: 60,
366
- borderRightWidth: 1,
367
- borderRightColor: colors.tableBorder || "#374151",
368
- },
369
- bodyCell: {
370
- flexShrink: 0,
371
- paddingVertical: 10,
372
- paddingHorizontal: 12,
373
- minWidth: 60,
374
- borderRightWidth: 1,
375
- borderRightColor: colors.tableBorder || "#374151",
376
- justifyContent: "center",
377
- },
378
- lastCell: {
379
- borderRightWidth: 0,
380
- },
381
- headerText: {
382
- color: colors.tableHeaderText || "#9ca3af",
383
- fontSize: 12,
384
- fontWeight: "600",
385
- fontFamily: theme.fontFamilies?.regular,
386
- ...(Platform.OS === "android" && { includeFontPadding: false }),
387
- },
388
- cellText: {
389
- color: colors.text || "#e5e7eb",
390
- fontSize: 14,
391
- lineHeight: 20,
392
- fontFamily: theme.fontFamilies?.regular,
393
- ...(Platform.OS === "android" && { includeFontPadding: false }),
394
- },
395
- cellContentWrapper: {
396
- flexDirection: "row",
397
- flexWrap: "wrap",
398
- alignItems: "center",
399
- },
400
- });
401
- };
1
+ export { TableRenderer } from "./table/index";
@@ -1,14 +1,28 @@
1
1
  import type { HybridObject } from "react-native-nitro-modules";
2
2
 
3
+ export type MarkdownSessionListener = (from: number, to: number) => void;
4
+
3
5
  export interface MarkdownSession extends HybridObject<{
4
6
  ios: "swift";
5
7
  android: "kotlin";
6
8
  }> {
7
- append(chunk: string): void;
9
+ append(chunk: string): number;
8
10
  clear(): void;
9
11
  getAllText(): string;
12
+ getLength(): number;
13
+ getTextRange(from: number, to: number): string;
10
14
 
11
15
  highlightPosition: number;
12
16
 
13
- addListener(listener: () => void): () => void;
17
+ addListener(listener: MarkdownSessionListener): () => void;
18
+
19
+ /**
20
+ * Replaces the entire buffer with new text and notifies listeners with (0, newLength).
21
+ */
22
+ reset(text: string): void;
23
+ /**
24
+ * Replaces the text in [from, to) with the given text.
25
+ * Returns the new total buffer length.
26
+ */
27
+ replace(from: number, to: number, text: string): number;
14
28
  }
package/src/theme.ts CHANGED
@@ -20,6 +20,16 @@ export type MarkdownTheme = {
20
20
  tableHeaderText: string | undefined;
21
21
  tableRowEven: string | undefined;
22
22
  tableRowOdd: string | undefined;
23
+ codeTokenColors?: {
24
+ keyword?: string;
25
+ string?: string;
26
+ comment?: string;
27
+ number?: string;
28
+ operator?: string;
29
+ punctuation?: string;
30
+ type?: string;
31
+ default?: string;
32
+ };
23
33
  };
24
34
  spacing: {
25
35
  xs: number;
@@ -74,6 +84,15 @@ export const defaultMarkdownTheme: MarkdownTheme = {
74
84
  tableHeaderText: "#64748b",
75
85
  tableRowEven: "transparent",
76
86
  tableRowOdd: "#f8fafc",
87
+ codeTokenColors: {
88
+ keyword: '#c792ea',
89
+ string: '#c3e88d',
90
+ comment: '#546e7a',
91
+ number: '#f78c6c',
92
+ operator: '#89ddff',
93
+ punctuation: '#89ddff',
94
+ type: '#ffcb6b',
95
+ },
77
96
  },
78
97
  spacing: {
79
98
  xs: 4,
@@ -129,8 +148,17 @@ export type PartialMarkdownTheme = {
129
148
  : MarkdownTheme[K];
130
149
  };
131
150
 
151
+ type TextNodeType =
152
+ | "text" | "bold" | "italic" | "strikethrough" | "link"
153
+ | "code_inline" | "heading" | "paragraph" | "math_inline" | "html_inline";
154
+ type ViewNodeType =
155
+ | "document" | "blockquote" | "code_block" | "horizontal_rule"
156
+ | "image" | "list" | "list_item" | "task_list_item" | "table"
157
+ | "table_head" | "table_body" | "table_row" | "table_cell"
158
+ | "math_block" | "html_block" | "line_break" | "soft_break";
159
+
132
160
  export type NodeStyleOverrides = Partial<
133
- Record<MarkdownNode["type"], ViewStyle | TextStyle>
161
+ { [K in TextNodeType]: TextStyle } & { [K in ViewNodeType]: ViewStyle }
134
162
  >;
135
163
 
136
164
  export type StylingStrategy = "opinionated" | "minimal";
@@ -39,6 +39,14 @@ export function useMarkdownSession() {
39
39
 
40
40
  const getSession = useCallback(() => sessionRef.current!, []);
41
41
 
42
+ const reset = useCallback((text: string) => {
43
+ sessionRef.current!.reset(text);
44
+ }, []);
45
+
46
+ const replace = useCallback((from: number, to: number, text: string) => {
47
+ return sessionRef.current!.replace(from, to, text);
48
+ }, []);
49
+
42
50
  return {
43
51
  getSession,
44
52
  isStreaming,
@@ -46,6 +54,8 @@ export function useMarkdownSession() {
46
54
  stop,
47
55
  clear,
48
56
  setHighlight,
57
+ reset,
58
+ replace,
49
59
  };
50
60
  }
51
61
 
@@ -0,0 +1,102 @@
1
+ export type TokenType =
2
+ | 'keyword'
3
+ | 'string'
4
+ | 'comment'
5
+ | 'number'
6
+ | 'operator'
7
+ | 'punctuation'
8
+ | 'type'
9
+ | 'default';
10
+
11
+ export type HighlightedToken = { text: string; type: TokenType };
12
+ export type CodeHighlighter = (language: string, code: string) => HighlightedToken[];
13
+
14
+ const JS_KEYWORDS = new Set([
15
+ 'const', 'let', 'var', 'function', 'return', 'if', 'else', 'for', 'while',
16
+ 'class', 'extends', 'import', 'export', 'from', 'default', 'new', 'this',
17
+ 'typeof', 'instanceof', 'null', 'undefined', 'true', 'false', 'async',
18
+ 'await', 'try', 'catch', 'throw', 'switch', 'case', 'break', 'continue',
19
+ 'type', 'interface', 'enum', 'as', 'in', 'of', 'void',
20
+ ]);
21
+
22
+ const PYTHON_KEYWORDS = new Set([
23
+ 'def', 'class', 'import', 'from', 'return', 'if', 'elif', 'else', 'for',
24
+ 'while', 'try', 'except', 'finally', 'with', 'as', 'pass', 'break',
25
+ 'continue', 'and', 'or', 'not', 'in', 'is', 'None', 'True', 'False',
26
+ 'lambda', 'yield', 'async', 'await',
27
+ ]);
28
+
29
+ const SHELL_KEYWORDS = new Set([
30
+ 'if', 'then', 'else', 'elif', 'fi', 'for', 'do', 'done', 'while',
31
+ 'case', 'esac', 'function', 'return', 'exit', 'echo', 'export',
32
+ ]);
33
+
34
+ function getKeywords(language: string): Set<string> {
35
+ const lang = language.toLowerCase();
36
+ if (lang === 'python' || lang === 'py') return PYTHON_KEYWORDS;
37
+ if (lang === 'bash' || lang === 'sh' || lang === 'shell' || lang === 'zsh') return SHELL_KEYWORDS;
38
+ return JS_KEYWORDS; // default for js/ts/jsx/tsx/java/c/cpp etc.
39
+ }
40
+
41
+ // Tokenize a single line into tokens
42
+ function tokenizeLine(line: string, language: string): HighlightedToken[] {
43
+ const keywords = getKeywords(language);
44
+ const tokens: HighlightedToken[] = [];
45
+
46
+ // Full-line comment detection
47
+ const trimmed = line.trimStart();
48
+ const lang = language.toLowerCase();
49
+ const isPythonLike = lang === 'python' || lang === 'py';
50
+ const isShellLike = lang === 'bash' || lang === 'sh' || lang === 'shell' || lang === 'zsh';
51
+
52
+ if (trimmed.startsWith('//') || trimmed.startsWith('#') && (isShellLike || isPythonLike)) {
53
+ return [{ text: line, type: 'comment' }];
54
+ }
55
+
56
+ // Simple token regex: strings, numbers, operators, identifiers, punctuation
57
+ const tokenRegex = /("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)|(\b\d+(?:\.\d+)?\b)|(\/\/[^\n]*|#[^\n]*)|([a-zA-Z_$][a-zA-Z0-9_$]*)|([+\-*/%=<>!&|^~?:]+)|([()\[\]{},;.])|(\s+)|(.)/g;
58
+
59
+ let match: RegExpExecArray | null;
60
+ while ((match = tokenRegex.exec(line)) !== null) {
61
+ const [full, str, num, comment, ident, op, punct] = match;
62
+ if (str) {
63
+ tokens.push({ text: full, type: 'string' });
64
+ } else if (num) {
65
+ tokens.push({ text: full, type: 'number' });
66
+ } else if (comment) {
67
+ tokens.push({ text: full, type: 'comment' });
68
+ } else if (ident) {
69
+ const type: TokenType = keywords.has(ident)
70
+ ? 'keyword'
71
+ : /^[A-Z][A-Za-z0-9]*$/.test(ident)
72
+ ? 'type'
73
+ : 'default';
74
+ tokens.push({ text: full, type });
75
+ } else if (op) {
76
+ tokens.push({ text: full, type: 'operator' });
77
+ } else if (punct) {
78
+ tokens.push({ text: full, type: 'punctuation' });
79
+ } else {
80
+ tokens.push({ text: full, type: 'default' });
81
+ }
82
+ }
83
+
84
+ return tokens;
85
+ }
86
+
87
+ export function defaultHighlighter(language: string, code: string): HighlightedToken[] {
88
+ if (!language || language === 'text' || language === 'plain') {
89
+ return [{ text: code, type: 'default' }];
90
+ }
91
+
92
+ // Process line by line, re-inserting newlines as default tokens
93
+ const lines = code.split('\n');
94
+ const result: HighlightedToken[] = [];
95
+ for (let i = 0; i < lines.length; i++) {
96
+ result.push(...tokenizeLine(lines[i]!, language));
97
+ if (i < lines.length - 1) {
98
+ result.push({ text: '\n', type: 'default' });
99
+ }
100
+ }
101
+ return result;
102
+ }