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
@@ -0,0 +1,419 @@
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
+ StyleSheet,
13
+ ScrollView,
14
+ Platform,
15
+ type ViewStyle,
16
+ type LayoutChangeEvent,
17
+ } from "react-native";
18
+ import { useMarkdownContext, type NodeRendererProps } from "../../MarkdownContext";
19
+ import type { MarkdownTheme } from "../../theme";
20
+ import { CellContent } from "./cell-content";
21
+ import { extractTableData, estimateColumnWidths } from "./table-utils";
22
+ import {
23
+ columnWidthsReducer,
24
+ DEFAULT_MIN_COLUMN_WIDTH,
25
+ DEFAULT_MEASUREMENT_STABILIZE_MS,
26
+ } from "./table-reducer";
27
+
28
+ type TableRendererProps = {
29
+ node: import("../../headless").MarkdownNode;
30
+ Renderer: ComponentType<NodeRendererProps>;
31
+ style?: ViewStyle;
32
+ };
33
+
34
+ const COLUMN_MEASUREMENT_PADDING = 8;
35
+
36
+ const IS_ACT_TEST_ENVIRONMENT =
37
+ Reflect.get(globalThis, "IS_REACT_ACT_ENVIRONMENT") === true;
38
+ const SHOULD_DEBOUNCE_MEASUREMENT = !IS_ACT_TEST_ENVIRONMENT;
39
+
40
+ export const TableRenderer: FC<TableRendererProps> = ({
41
+ node,
42
+ Renderer,
43
+ style,
44
+ }) => {
45
+ const { theme, tableOptions } = useMarkdownContext();
46
+
47
+ const minColumnWidth =
48
+ tableOptions?.minColumnWidth ?? DEFAULT_MIN_COLUMN_WIDTH;
49
+ const measurementStabilizeMs =
50
+ tableOptions?.measurementStabilizeMs ?? DEFAULT_MEASUREMENT_STABILIZE_MS;
51
+
52
+ const { headers, rows, alignments } = useMemo(
53
+ () => extractTableData(node),
54
+ [node],
55
+ );
56
+
57
+ const columnCount = headers.length;
58
+ const styles = useMemo(() => createTableStyles(theme), [theme]);
59
+ const estimatedColumnWidths = useMemo(
60
+ () => estimateColumnWidths(headers, rows, columnCount, minColumnWidth),
61
+ [headers, rows, columnCount, minColumnWidth],
62
+ );
63
+
64
+ const [columnWidths, dispatch] = useReducer(
65
+ columnWidthsReducer,
66
+ estimatedColumnWidths,
67
+ );
68
+ const measuredWidths = useRef<Map<string, number>>(new Map());
69
+ const measuredCells = useRef<Set<string>>(new Set());
70
+ const widthsCalculated = useRef(false);
71
+ const columnWidthsRef = useRef(columnWidths);
72
+ const lastCellKeySignatureRef = useRef("");
73
+ const measurementTimerRef = useRef<ReturnType<typeof setTimeout> | null>(
74
+ null,
75
+ );
76
+ const [needsMeasurement, setNeedsMeasurement] = useReducer(
77
+ (_previous: boolean, nextValue: boolean) => nextValue,
78
+ false,
79
+ );
80
+
81
+ const expectedCellKeys = useMemo(() => {
82
+ const keys: string[] = [];
83
+
84
+ headers.forEach((_, colIndex) => {
85
+ keys.push(`header-${colIndex}`);
86
+ });
87
+
88
+ rows.forEach((row, rowIndex) => {
89
+ row.forEach((_, colIndex) => {
90
+ keys.push(`cell-${rowIndex}-${colIndex}`);
91
+ });
92
+ });
93
+
94
+ return keys;
95
+ }, [headers, rows]);
96
+
97
+ const expectedCellKeySignature = useMemo(
98
+ () => expectedCellKeys.join("|"),
99
+ [expectedCellKeys],
100
+ );
101
+
102
+ useEffect(() => {
103
+ columnWidthsRef.current = columnWidths;
104
+ }, [columnWidths]);
105
+
106
+ useEffect(() => {
107
+ const structureChanged =
108
+ lastCellKeySignatureRef.current !== expectedCellKeySignature;
109
+ lastCellKeySignatureRef.current = expectedCellKeySignature;
110
+
111
+ if (measurementTimerRef.current) {
112
+ clearTimeout(measurementTimerRef.current);
113
+ measurementTimerRef.current = null;
114
+ }
115
+
116
+ if (structureChanged) {
117
+ measuredWidths.current.clear();
118
+ measuredCells.current.clear();
119
+ widthsCalculated.current = false;
120
+ setNeedsMeasurement(false);
121
+ dispatch({ type: "RESET_WIDTHS", widths: estimatedColumnWidths });
122
+ } else {
123
+ dispatch({
124
+ type: "SET_MONOTONIC_WIDTHS",
125
+ widths: estimatedColumnWidths,
126
+ });
127
+ if (widthsCalculated.current) {
128
+ return;
129
+ }
130
+ }
131
+
132
+ if (!SHOULD_DEBOUNCE_MEASUREMENT) {
133
+ setNeedsMeasurement(true);
134
+ return;
135
+ }
136
+
137
+ measurementTimerRef.current = setTimeout(() => {
138
+ measurementTimerRef.current = null;
139
+ setNeedsMeasurement(true);
140
+ }, measurementStabilizeMs);
141
+
142
+ return () => {
143
+ if (measurementTimerRef.current) {
144
+ clearTimeout(measurementTimerRef.current);
145
+ measurementTimerRef.current = null;
146
+ }
147
+ };
148
+ }, [estimatedColumnWidths, expectedCellKeySignature, measurementStabilizeMs]);
149
+
150
+ const onCellLayout = useCallback(
151
+ (cellKey: string, width: number) => {
152
+ if (width <= 0 || widthsCalculated.current || !needsMeasurement) return;
153
+
154
+ measuredWidths.current.set(cellKey, width);
155
+ if (!measuredCells.current.has(cellKey)) {
156
+ measuredCells.current.add(cellKey);
157
+ }
158
+
159
+ if (measuredCells.current.size < expectedCellKeys.length) return;
160
+
161
+ const allCellsMeasured = expectedCellKeys.every((key) =>
162
+ measuredCells.current.has(key),
163
+ );
164
+ if (!allCellsMeasured) return;
165
+
166
+ const maxWidths: number[] = [...columnWidthsRef.current];
167
+
168
+ for (let col = 0; col < columnCount; col++) {
169
+ const headerWidth = measuredWidths.current.get(`header-${col}`);
170
+ if (headerWidth && headerWidth > 0) {
171
+ maxWidths[col] = Math.max(maxWidths[col], headerWidth);
172
+ }
173
+
174
+ for (let row = 0; row < rows.length; row++) {
175
+ if (col >= rows[row].length) continue;
176
+ const cellWidth = measuredWidths.current.get(`cell-${row}-${col}`);
177
+ if (cellWidth && cellWidth > 0) {
178
+ maxWidths[col] = Math.max(maxWidths[col], cellWidth);
179
+ }
180
+ }
181
+
182
+ maxWidths[col] = Math.max(
183
+ maxWidths[col] + COLUMN_MEASUREMENT_PADDING,
184
+ minColumnWidth,
185
+ );
186
+ }
187
+
188
+ widthsCalculated.current = true;
189
+ setNeedsMeasurement(false);
190
+ dispatch({ type: "RESET_WIDTHS", widths: maxWidths });
191
+ },
192
+ [columnCount, expectedCellKeys, needsMeasurement, rows, minColumnWidth],
193
+ );
194
+
195
+ const getAlignment = (
196
+ index: number,
197
+ ): "flex-start" | "center" | "flex-end" => {
198
+ const align = alignments[index];
199
+ if (align === "center") return "center";
200
+ if (align === "right") return "flex-end";
201
+ return "flex-start";
202
+ };
203
+
204
+ if (columnCount === 0) return null;
205
+
206
+ const hasWidths = columnWidths.length === columnCount;
207
+ const resolvedWidths = hasWidths ? columnWidths : estimatedColumnWidths;
208
+
209
+ return (
210
+ <View style={[styles.container, style]}>
211
+ {needsMeasurement ? (
212
+ <View style={styles.measurementContainer}>
213
+ <View style={styles.measurementRow}>
214
+ {headers.map((cell, colIndex) => (
215
+ <View
216
+ key={`measure-header-${colIndex}`}
217
+ style={styles.measurementCell}
218
+ onLayout={(e: LayoutChangeEvent) => {
219
+ onCellLayout(
220
+ `header-${colIndex}`,
221
+ e.nativeEvent.layout.width,
222
+ );
223
+ }}
224
+ >
225
+ <CellContent node={cell} Renderer={Renderer} styles={styles} />
226
+ </View>
227
+ ))}
228
+ </View>
229
+ {rows.map((row, rowIndex) => (
230
+ <View key={`measure-row-${rowIndex}`} style={styles.measurementRow}>
231
+ {row.map((cell, colIndex) => (
232
+ <View
233
+ key={`measure-cell-${rowIndex}-${colIndex}`}
234
+ style={styles.measurementCell}
235
+ onLayout={(e: LayoutChangeEvent) => {
236
+ onCellLayout(
237
+ `cell-${rowIndex}-${colIndex}`,
238
+ e.nativeEvent.layout.width,
239
+ );
240
+ }}
241
+ >
242
+ <CellContent
243
+ node={cell}
244
+ Renderer={Renderer}
245
+ styles={styles}
246
+ />
247
+ </View>
248
+ ))}
249
+ </View>
250
+ ))}
251
+ </View>
252
+ ) : null}
253
+
254
+ <ScrollView
255
+ horizontal
256
+ showsHorizontalScrollIndicator
257
+ style={styles.tableScroll}
258
+ bounces={false}
259
+ >
260
+ <View
261
+ style={[
262
+ styles.table,
263
+ {
264
+ backgroundColor:
265
+ style?.backgroundColor ?? theme.colors.surface ?? "#111827",
266
+ },
267
+ ]}
268
+ >
269
+ <View style={styles.headerRow}>
270
+ {headers.map((cell, colIndex) => (
271
+ <View
272
+ key={`header-${colIndex}`}
273
+ style={[
274
+ styles.headerCell,
275
+ {
276
+ width: resolvedWidths[colIndex] ?? minColumnWidth,
277
+ alignItems: getAlignment(colIndex),
278
+ },
279
+ colIndex === columnCount - 1 && styles.lastCell,
280
+ ]}
281
+ >
282
+ <CellContent
283
+ node={cell}
284
+ Renderer={Renderer}
285
+ styles={styles}
286
+ textStyle={styles.headerText}
287
+ />
288
+ </View>
289
+ ))}
290
+ </View>
291
+
292
+ {rows.map((row, rowIndex) => (
293
+ <View
294
+ key={`row-${rowIndex}`}
295
+ style={[
296
+ styles.bodyRow,
297
+ rowIndex === rows.length - 1 && styles.lastRow,
298
+ rowIndex % 2 === 0 ? styles.evenRow : styles.oddRow,
299
+ ]}
300
+ >
301
+ {row.map((cell, colIndex) => (
302
+ <View
303
+ key={`cell-${rowIndex}-${colIndex}`}
304
+ style={[
305
+ styles.bodyCell,
306
+ {
307
+ width: resolvedWidths[colIndex] ?? minColumnWidth,
308
+ alignItems: getAlignment(colIndex),
309
+ },
310
+ colIndex === columnCount - 1 && styles.lastCell,
311
+ ]}
312
+ >
313
+ <CellContent
314
+ node={cell}
315
+ Renderer={Renderer}
316
+ styles={styles}
317
+ textStyle={styles.cellText}
318
+ />
319
+ </View>
320
+ ))}
321
+ </View>
322
+ ))}
323
+ </View>
324
+ </ScrollView>
325
+ </View>
326
+ );
327
+ };
328
+
329
+ const createTableStyles = (theme: MarkdownTheme) => {
330
+ const colors = theme?.colors || {};
331
+ const borderRadius = theme?.borderRadius || { m: 8 };
332
+
333
+ return StyleSheet.create({
334
+ container: {
335
+ marginVertical: theme.spacing.s,
336
+ },
337
+ measurementContainer: {
338
+ position: "absolute",
339
+ opacity: 0,
340
+ pointerEvents: "none",
341
+ left: -9999,
342
+ },
343
+ measurementRow: {
344
+ flexDirection: "row",
345
+ },
346
+ measurementCell: {
347
+ paddingVertical: 10,
348
+ paddingHorizontal: 12,
349
+ },
350
+ tableScroll: {
351
+ flexGrow: 0,
352
+ },
353
+ table: {
354
+ borderRadius: borderRadius.m,
355
+ overflow: "hidden",
356
+ borderWidth: 1,
357
+ borderColor: colors.tableBorder || "#374151",
358
+ },
359
+ headerRow: {
360
+ flexDirection: "row",
361
+ backgroundColor: colors.tableHeader || "#1f2937",
362
+ borderBottomWidth: 1,
363
+ borderBottomColor: colors.tableBorder || "#374151",
364
+ },
365
+ bodyRow: {
366
+ flexDirection: "row",
367
+ borderBottomWidth: 1,
368
+ borderBottomColor: colors.tableBorder || "#374151",
369
+ },
370
+ evenRow: {
371
+ backgroundColor: colors.tableRowEven || "transparent",
372
+ },
373
+ oddRow: {
374
+ backgroundColor: colors.tableRowOdd || "rgba(255,255,255,0.02)",
375
+ },
376
+ lastRow: {
377
+ borderBottomWidth: 0,
378
+ },
379
+ headerCell: {
380
+ flexShrink: 0,
381
+ paddingVertical: 10,
382
+ paddingHorizontal: 12,
383
+ minWidth: 60,
384
+ borderRightWidth: 1,
385
+ borderRightColor: colors.tableBorder || "#374151",
386
+ },
387
+ bodyCell: {
388
+ flexShrink: 0,
389
+ paddingVertical: 10,
390
+ paddingHorizontal: 12,
391
+ minWidth: 60,
392
+ borderRightWidth: 1,
393
+ borderRightColor: colors.tableBorder || "#374151",
394
+ justifyContent: "center",
395
+ },
396
+ lastCell: {
397
+ borderRightWidth: 0,
398
+ },
399
+ headerText: {
400
+ color: colors.tableHeaderText || "#9ca3af",
401
+ fontSize: 12,
402
+ fontWeight: "600",
403
+ fontFamily: theme.fontFamilies?.regular,
404
+ ...(Platform.OS === "android" && { includeFontPadding: false }),
405
+ },
406
+ cellText: {
407
+ color: colors.text || "#e5e7eb",
408
+ fontSize: 14,
409
+ lineHeight: 20,
410
+ fontFamily: theme.fontFamilies?.regular,
411
+ ...(Platform.OS === "android" && { includeFontPadding: false }),
412
+ },
413
+ cellContentWrapper: {
414
+ flexDirection: "row",
415
+ flexWrap: "wrap",
416
+ alignItems: "center",
417
+ },
418
+ });
419
+ };
@@ -0,0 +1,36 @@
1
+ import type { ColumnWidthAction } from "./types";
2
+
3
+ export const DEFAULT_MIN_COLUMN_WIDTH = 60;
4
+ export const DEFAULT_MEASUREMENT_STABILIZE_MS = 140;
5
+
6
+ export function columnWidthsReducer(
7
+ state: number[],
8
+ action: ColumnWidthAction,
9
+ ): number[] {
10
+ if (action.type === "RESET_WIDTHS") {
11
+ if (
12
+ state.length === action.widths.length &&
13
+ state.every((width, index) => width === action.widths[index])
14
+ ) {
15
+ return state;
16
+ }
17
+ return action.widths;
18
+ }
19
+
20
+ if (action.type === "SET_MONOTONIC_WIDTHS") {
21
+ if (state.length !== action.widths.length) {
22
+ return action.widths;
23
+ }
24
+
25
+ const merged = action.widths.map((width, index) =>
26
+ Math.max(width, state[index] ?? width),
27
+ );
28
+
29
+ if (state.every((width, index) => width === merged[index])) {
30
+ return state;
31
+ }
32
+ return merged;
33
+ }
34
+
35
+ return state;
36
+ }
@@ -0,0 +1,81 @@
1
+ import { getTextContent, type MarkdownNode } from "../../headless";
2
+
3
+ const COLUMN_MEASUREMENT_PADDING = 8;
4
+ const APPROX_CHAR_WIDTH = 7;
5
+ const MAX_ESTIMATED_CHARS = 120;
6
+ const ESTIMATED_WIDTH_STEP = 24;
7
+
8
+ type ExtractedTableData = {
9
+ headers: MarkdownNode[];
10
+ rows: MarkdownNode[][];
11
+ alignments: (string | undefined)[];
12
+ };
13
+
14
+ export const extractTableData = (node: MarkdownNode): ExtractedTableData => {
15
+ const headers: MarkdownNode[] = [];
16
+ const rows: MarkdownNode[][] = [];
17
+ const alignments: (string | undefined)[] = [];
18
+
19
+ for (const child of node.children ?? []) {
20
+ if (child.type === "table_head") {
21
+ for (const row of child.children ?? []) {
22
+ if (row.type === "table_row") {
23
+ for (const cell of row.children ?? []) {
24
+ headers.push(cell);
25
+ alignments.push(cell.align);
26
+ }
27
+ }
28
+ }
29
+ } else if (child.type === "table_body") {
30
+ for (const row of child.children ?? []) {
31
+ if (row.type === "table_row") {
32
+ const rowCells: MarkdownNode[] = [];
33
+ for (const cell of row.children ?? []) {
34
+ rowCells.push(cell);
35
+ }
36
+ rows.push(rowCells);
37
+ }
38
+ }
39
+ }
40
+ }
41
+
42
+ return { headers, rows, alignments };
43
+ };
44
+
45
+ export const estimateColumnWidths = (
46
+ headers: MarkdownNode[],
47
+ rows: MarkdownNode[][],
48
+ columnCount: number,
49
+ minColumnWidth: number,
50
+ ) => {
51
+ const widths = new Array<number>(columnCount).fill(minColumnWidth);
52
+
53
+ for (let col = 0; col < columnCount; col++) {
54
+ const headerChars = Math.min(
55
+ getTextContent(headers[col] ?? { type: "text", content: "" }).trim()
56
+ .length,
57
+ MAX_ESTIMATED_CHARS,
58
+ );
59
+ let maxChars = headerChars;
60
+
61
+ for (let row = 0; row < rows.length; row++) {
62
+ const cell = rows[row][col];
63
+ if (!cell) continue;
64
+ const cellChars = Math.min(
65
+ getTextContent(cell).trim().length,
66
+ MAX_ESTIMATED_CHARS,
67
+ );
68
+ if (cellChars > maxChars) {
69
+ maxChars = cellChars;
70
+ }
71
+ }
72
+
73
+ const estimatedWidth =
74
+ maxChars * APPROX_CHAR_WIDTH + COLUMN_MEASUREMENT_PADDING;
75
+ const steppedEstimatedWidth =
76
+ Math.ceil(estimatedWidth / ESTIMATED_WIDTH_STEP) * ESTIMATED_WIDTH_STEP;
77
+ widths[col] = Math.max(minColumnWidth, steppedEstimatedWidth);
78
+ }
79
+
80
+ return widths;
81
+ };
@@ -0,0 +1,24 @@
1
+ import type { MarkdownNode } from "../../headless";
2
+
3
+ export type AlignmentType = "left" | "center" | "right" | "default";
4
+
5
+ export type TableCell = {
6
+ node: MarkdownNode;
7
+ align: AlignmentType;
8
+ isHeader: boolean;
9
+ };
10
+
11
+ export type TableRow = {
12
+ cells: TableCell[];
13
+ };
14
+
15
+ export type TableData = {
16
+ headers: TableCell[];
17
+ rows: TableRow[];
18
+ columnCount: number;
19
+ alignments: AlignmentType[];
20
+ };
21
+
22
+ export type ColumnWidthAction =
23
+ | { type: "RESET_WIDTHS"; widths: number[] }
24
+ | { type: "SET_MONOTONIC_WIDTHS"; widths: number[] };