react-native-nitro-markdown 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/README.md +68 -5
  2. package/android/src/main/cpp/cpp-adapter.cpp +4 -1
  3. package/android/src/main/java/com/margelo/nitro/com/nitromarkdown/HybridMarkdownSession.kt +19 -0
  4. package/android/src/main/java/com/nitromarkdown/NitroMarkdownPackage.kt +4 -3
  5. package/ios/HybridMarkdownSession.swift +18 -0
  6. package/lib/commonjs/MarkdownContext.js.map +1 -1
  7. package/lib/commonjs/headless.js +20 -0
  8. package/lib/commonjs/headless.js.map +1 -1
  9. package/lib/commonjs/index.js +9 -1
  10. package/lib/commonjs/index.js.map +1 -1
  11. package/lib/commonjs/markdown.js +21 -11
  12. package/lib/commonjs/markdown.js.map +1 -1
  13. package/lib/commonjs/renderers/code.js +20 -2
  14. package/lib/commonjs/renderers/code.js.map +1 -1
  15. package/lib/commonjs/renderers/table/cell-content.js +32 -0
  16. package/lib/commonjs/renderers/table/cell-content.js.map +1 -0
  17. package/lib/commonjs/renderers/table/index.js +310 -0
  18. package/lib/commonjs/renderers/table/index.js.map +1 -0
  19. package/lib/commonjs/renderers/table/table-reducer.js +29 -0
  20. package/lib/commonjs/renderers/table/table-reducer.js.map +1 -0
  21. package/lib/commonjs/renderers/table/table-utils.js +68 -0
  22. package/lib/commonjs/renderers/table/table-utils.js.map +1 -0
  23. package/lib/commonjs/renderers/table/types.js +6 -0
  24. package/lib/commonjs/renderers/table/types.js.map +1 -0
  25. package/lib/commonjs/renderers/table.js +6 -398
  26. package/lib/commonjs/renderers/table.js.map +1 -1
  27. package/lib/commonjs/theme.js +10 -1
  28. package/lib/commonjs/theme.js.map +1 -1
  29. package/lib/commonjs/use-markdown-stream.js +9 -1
  30. package/lib/commonjs/use-markdown-stream.js.map +1 -1
  31. package/lib/commonjs/utils/code-highlight.js +101 -0
  32. package/lib/commonjs/utils/code-highlight.js.map +1 -0
  33. package/lib/module/MarkdownContext.js.map +1 -1
  34. package/lib/module/headless.js +19 -0
  35. package/lib/module/headless.js.map +1 -1
  36. package/lib/module/index.js +1 -0
  37. package/lib/module/index.js.map +1 -1
  38. package/lib/module/markdown.js +21 -11
  39. package/lib/module/markdown.js.map +1 -1
  40. package/lib/module/renderers/code.js +20 -2
  41. package/lib/module/renderers/code.js.map +1 -1
  42. package/lib/module/renderers/table/cell-content.js +27 -0
  43. package/lib/module/renderers/table/cell-content.js.map +1 -0
  44. package/lib/module/renderers/table/index.js +305 -0
  45. package/lib/module/renderers/table/index.js.map +1 -0
  46. package/lib/module/renderers/table/table-reducer.js +24 -0
  47. package/lib/module/renderers/table/table-reducer.js.map +1 -0
  48. package/lib/module/renderers/table/table-utils.js +62 -0
  49. package/lib/module/renderers/table/table-utils.js.map +1 -0
  50. package/lib/module/renderers/table/types.js +4 -0
  51. package/lib/module/renderers/table/types.js.map +1 -0
  52. package/lib/module/renderers/table.js +1 -397
  53. package/lib/module/renderers/table.js.map +1 -1
  54. package/lib/module/theme.js +10 -1
  55. package/lib/module/theme.js.map +1 -1
  56. package/lib/module/use-markdown-stream.js +9 -1
  57. package/lib/module/use-markdown-stream.js.map +1 -1
  58. package/lib/module/utils/code-highlight.js +97 -0
  59. package/lib/module/utils/code-highlight.js.map +1 -0
  60. package/lib/typescript/commonjs/MarkdownContext.d.ts +6 -0
  61. package/lib/typescript/commonjs/MarkdownContext.d.ts.map +1 -1
  62. package/lib/typescript/commonjs/headless.d.ts +5 -0
  63. package/lib/typescript/commonjs/headless.d.ts.map +1 -1
  64. package/lib/typescript/commonjs/index.d.ts +2 -0
  65. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  66. package/lib/typescript/commonjs/markdown.d.ts +24 -0
  67. package/lib/typescript/commonjs/markdown.d.ts.map +1 -1
  68. package/lib/typescript/commonjs/renderers/code.d.ts.map +1 -1
  69. package/lib/typescript/commonjs/renderers/table/cell-content.d.ts +15 -0
  70. package/lib/typescript/commonjs/renderers/table/cell-content.d.ts.map +1 -0
  71. package/lib/typescript/commonjs/renderers/table/index.d.ts +11 -0
  72. package/lib/typescript/commonjs/renderers/table/index.d.ts.map +1 -0
  73. package/lib/typescript/commonjs/renderers/table/table-reducer.d.ts +5 -0
  74. package/lib/typescript/commonjs/renderers/table/table-reducer.d.ts.map +1 -0
  75. package/lib/typescript/commonjs/renderers/table/table-utils.d.ts +10 -0
  76. package/lib/typescript/commonjs/renderers/table/table-utils.d.ts.map +1 -0
  77. package/lib/typescript/commonjs/renderers/table/types.d.ts +24 -0
  78. package/lib/typescript/commonjs/renderers/table/types.d.ts.map +1 -0
  79. package/lib/typescript/commonjs/renderers/table.d.ts +1 -11
  80. package/lib/typescript/commonjs/renderers/table.d.ts.map +1 -1
  81. package/lib/typescript/commonjs/specs/MarkdownSession.nitro.d.ts +9 -0
  82. package/lib/typescript/commonjs/specs/MarkdownSession.nitro.d.ts.map +1 -1
  83. package/lib/typescript/commonjs/theme.d.ts +18 -2
  84. package/lib/typescript/commonjs/theme.d.ts.map +1 -1
  85. package/lib/typescript/commonjs/use-markdown-stream.d.ts +4 -0
  86. package/lib/typescript/commonjs/use-markdown-stream.d.ts.map +1 -1
  87. package/lib/typescript/commonjs/utils/code-highlight.d.ts +8 -0
  88. package/lib/typescript/commonjs/utils/code-highlight.d.ts.map +1 -0
  89. package/lib/typescript/module/MarkdownContext.d.ts +6 -0
  90. package/lib/typescript/module/MarkdownContext.d.ts.map +1 -1
  91. package/lib/typescript/module/headless.d.ts +5 -0
  92. package/lib/typescript/module/headless.d.ts.map +1 -1
  93. package/lib/typescript/module/index.d.ts +2 -0
  94. package/lib/typescript/module/index.d.ts.map +1 -1
  95. package/lib/typescript/module/markdown.d.ts +24 -0
  96. package/lib/typescript/module/markdown.d.ts.map +1 -1
  97. package/lib/typescript/module/renderers/code.d.ts.map +1 -1
  98. package/lib/typescript/module/renderers/table/cell-content.d.ts +15 -0
  99. package/lib/typescript/module/renderers/table/cell-content.d.ts.map +1 -0
  100. package/lib/typescript/module/renderers/table/index.d.ts +11 -0
  101. package/lib/typescript/module/renderers/table/index.d.ts.map +1 -0
  102. package/lib/typescript/module/renderers/table/table-reducer.d.ts +5 -0
  103. package/lib/typescript/module/renderers/table/table-reducer.d.ts.map +1 -0
  104. package/lib/typescript/module/renderers/table/table-utils.d.ts +10 -0
  105. package/lib/typescript/module/renderers/table/table-utils.d.ts.map +1 -0
  106. package/lib/typescript/module/renderers/table/types.d.ts +24 -0
  107. package/lib/typescript/module/renderers/table/types.d.ts.map +1 -0
  108. package/lib/typescript/module/renderers/table.d.ts +1 -11
  109. package/lib/typescript/module/renderers/table.d.ts.map +1 -1
  110. package/lib/typescript/module/specs/MarkdownSession.nitro.d.ts +9 -0
  111. package/lib/typescript/module/specs/MarkdownSession.nitro.d.ts.map +1 -1
  112. package/lib/typescript/module/theme.d.ts +18 -2
  113. package/lib/typescript/module/theme.d.ts.map +1 -1
  114. package/lib/typescript/module/use-markdown-stream.d.ts +4 -0
  115. package/lib/typescript/module/use-markdown-stream.d.ts.map +1 -1
  116. package/lib/typescript/module/utils/code-highlight.d.ts +8 -0
  117. package/lib/typescript/module/utils/code-highlight.d.ts.map +1 -0
  118. package/nitrogen/generated/android/NitroMarkdownOnLoad.cpp +37 -27
  119. package/nitrogen/generated/android/NitroMarkdownOnLoad.hpp +13 -4
  120. package/nitrogen/generated/android/c++/JHybridMarkdownSessionSpec.cpp +35 -32
  121. package/nitrogen/generated/android/c++/JHybridMarkdownSessionSpec.hpp +21 -22
  122. package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/nitromarkdown/HybridMarkdownSessionSpec.kt +23 -18
  123. package/nitrogen/generated/ios/c++/HybridMarkdownSessionSpecSwift.hpp +14 -0
  124. package/nitrogen/generated/ios/swift/HybridMarkdownSessionSpec.swift +2 -0
  125. package/nitrogen/generated/ios/swift/HybridMarkdownSessionSpec_cxx.swift +23 -0
  126. package/nitrogen/generated/shared/c++/HybridMarkdownSessionSpec.cpp +2 -0
  127. package/nitrogen/generated/shared/c++/HybridMarkdownSessionSpec.hpp +2 -0
  128. package/package.json +6 -3
  129. package/react-native-nitro-markdown.podspec +1 -1
  130. package/src/MarkdownContext.ts +6 -0
  131. package/src/headless.ts +12 -0
  132. package/src/index.ts +3 -0
  133. package/src/markdown.tsx +47 -7
  134. package/src/renderers/code.tsx +27 -2
  135. package/src/renderers/table/cell-content.tsx +38 -0
  136. package/src/renderers/table/index.tsx +419 -0
  137. package/src/renderers/table/table-reducer.ts +36 -0
  138. package/src/renderers/table/table-utils.ts +81 -0
  139. package/src/renderers/table/types.ts +24 -0
  140. package/src/renderers/table.tsx +1 -547
  141. package/src/specs/MarkdownSession.nitro.ts +10 -0
  142. package/src/theme.ts +29 -1
  143. package/src/use-markdown-stream.ts +10 -0
  144. package/src/utils/code-highlight.ts +102 -0
@@ -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[] };
@@ -1,547 +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 { getTextContent, type MarkdownNode } from "../headless";
22
- import { useMarkdownContext, type NodeRendererProps } from "../MarkdownContext";
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: "RESET_WIDTHS" | "SET_MONOTONIC_WIDTHS";
70
- payload: number[];
71
- };
72
-
73
- const MIN_COLUMN_WIDTH = 60;
74
- const COLUMN_MEASUREMENT_PADDING = 8;
75
- const APPROX_CHAR_WIDTH = 7;
76
- const MAX_ESTIMATED_CHARS = 120;
77
- const ESTIMATED_WIDTH_STEP = 24;
78
- const MEASUREMENT_STABILIZE_MS = 140;
79
- const IS_ACT_TEST_ENVIRONMENT =
80
- Reflect.get(globalThis, "IS_REACT_ACT_ENVIRONMENT") === true;
81
- const SHOULD_DEBOUNCE_MEASUREMENT = !IS_ACT_TEST_ENVIRONMENT;
82
-
83
- const columnWidthsReducer = (state: number[], action: ColumnWidthsAction) => {
84
- if (action.type === "RESET_WIDTHS") {
85
- if (
86
- state.length === action.payload.length &&
87
- state.every((width, index) => width === action.payload[index])
88
- ) {
89
- return state;
90
- }
91
- return action.payload;
92
- }
93
-
94
- if (action.type === "SET_MONOTONIC_WIDTHS") {
95
- if (state.length !== action.payload.length) {
96
- return action.payload;
97
- }
98
-
99
- const merged = action.payload.map((width, index) =>
100
- Math.max(width, state[index] ?? width),
101
- );
102
-
103
- if (state.every((width, index) => width === merged[index])) {
104
- return state;
105
- }
106
- return merged;
107
- }
108
-
109
- return state;
110
- };
111
-
112
- const estimateColumnWidths = (
113
- headers: MarkdownNode[],
114
- rows: MarkdownNode[][],
115
- columnCount: number,
116
- ) => {
117
- const widths = new Array<number>(columnCount).fill(MIN_COLUMN_WIDTH);
118
-
119
- for (let col = 0; col < columnCount; col++) {
120
- const headerChars = Math.min(
121
- getTextContent(headers[col] ?? { type: "text", content: "" }).trim()
122
- .length,
123
- MAX_ESTIMATED_CHARS,
124
- );
125
- let maxChars = headerChars;
126
-
127
- for (let row = 0; row < rows.length; row++) {
128
- const cell = rows[row][col];
129
- if (!cell) continue;
130
- const cellChars = Math.min(
131
- getTextContent(cell).trim().length,
132
- MAX_ESTIMATED_CHARS,
133
- );
134
- if (cellChars > maxChars) {
135
- maxChars = cellChars;
136
- }
137
- }
138
-
139
- const estimatedWidth =
140
- maxChars * APPROX_CHAR_WIDTH + COLUMN_MEASUREMENT_PADDING;
141
- const steppedEstimatedWidth =
142
- Math.ceil(estimatedWidth / ESTIMATED_WIDTH_STEP) * ESTIMATED_WIDTH_STEP;
143
- widths[col] = Math.max(MIN_COLUMN_WIDTH, steppedEstimatedWidth);
144
- }
145
-
146
- return widths;
147
- };
148
-
149
- export const TableRenderer: FC<TableRendererProps> = ({
150
- node,
151
- Renderer,
152
- style,
153
- }) => {
154
- const { theme } = useMarkdownContext();
155
- const { headers, rows, alignments } = useMemo(
156
- () => extractTableData(node),
157
- [node],
158
- );
159
-
160
- const columnCount = headers.length;
161
- const styles = useMemo(() => createTableStyles(theme), [theme]);
162
- const estimatedColumnWidths = useMemo(
163
- () => estimateColumnWidths(headers, rows, columnCount),
164
- [headers, rows, columnCount],
165
- );
166
-
167
- const [columnWidths, dispatch] = useReducer(
168
- columnWidthsReducer,
169
- estimatedColumnWidths,
170
- );
171
- const measuredWidths = useRef<Map<string, number>>(new Map());
172
- const measuredCells = useRef<Set<string>>(new Set());
173
- const widthsCalculated = useRef(false);
174
- const columnWidthsRef = useRef(columnWidths);
175
- const lastCellKeySignatureRef = useRef("");
176
- const measurementTimerRef = useRef<ReturnType<typeof setTimeout> | null>(
177
- null,
178
- );
179
- const [needsMeasurement, setNeedsMeasurement] = useReducer(
180
- (_previous: boolean, nextValue: boolean) => nextValue,
181
- false,
182
- );
183
-
184
- const expectedCellKeys = useMemo(() => {
185
- const keys: string[] = [];
186
-
187
- headers.forEach((_, colIndex) => {
188
- keys.push(`header-${colIndex}`);
189
- });
190
-
191
- rows.forEach((row, rowIndex) => {
192
- row.forEach((_, colIndex) => {
193
- keys.push(`cell-${rowIndex}-${colIndex}`);
194
- });
195
- });
196
-
197
- return keys;
198
- }, [headers, rows]);
199
-
200
- const expectedCellKeySignature = useMemo(
201
- () => expectedCellKeys.join("|"),
202
- [expectedCellKeys],
203
- );
204
-
205
- useEffect(() => {
206
- columnWidthsRef.current = columnWidths;
207
- }, [columnWidths]);
208
-
209
- useEffect(() => {
210
- const structureChanged =
211
- lastCellKeySignatureRef.current !== expectedCellKeySignature;
212
- lastCellKeySignatureRef.current = expectedCellKeySignature;
213
-
214
- if (measurementTimerRef.current) {
215
- clearTimeout(measurementTimerRef.current);
216
- measurementTimerRef.current = null;
217
- }
218
-
219
- if (structureChanged) {
220
- measuredWidths.current.clear();
221
- measuredCells.current.clear();
222
- widthsCalculated.current = false;
223
- setNeedsMeasurement(false);
224
- dispatch({ type: "RESET_WIDTHS", payload: estimatedColumnWidths });
225
- } else {
226
- dispatch({
227
- type: "SET_MONOTONIC_WIDTHS",
228
- payload: estimatedColumnWidths,
229
- });
230
- if (widthsCalculated.current) {
231
- return;
232
- }
233
- }
234
-
235
- if (!SHOULD_DEBOUNCE_MEASUREMENT) {
236
- setNeedsMeasurement(true);
237
- return;
238
- }
239
-
240
- measurementTimerRef.current = setTimeout(() => {
241
- measurementTimerRef.current = null;
242
- setNeedsMeasurement(true);
243
- }, MEASUREMENT_STABILIZE_MS);
244
-
245
- return () => {
246
- if (measurementTimerRef.current) {
247
- clearTimeout(measurementTimerRef.current);
248
- measurementTimerRef.current = null;
249
- }
250
- };
251
- }, [estimatedColumnWidths, expectedCellKeySignature]);
252
-
253
- const onCellLayout = useCallback(
254
- (cellKey: string, width: number) => {
255
- if (width <= 0 || widthsCalculated.current || !needsMeasurement) return;
256
-
257
- measuredWidths.current.set(cellKey, width);
258
- if (!measuredCells.current.has(cellKey)) {
259
- measuredCells.current.add(cellKey);
260
- }
261
-
262
- if (measuredCells.current.size < expectedCellKeys.length) return;
263
-
264
- const allCellsMeasured = expectedCellKeys.every((key) =>
265
- measuredCells.current.has(key),
266
- );
267
- if (!allCellsMeasured) return;
268
-
269
- const maxWidths: number[] = [...columnWidthsRef.current];
270
-
271
- for (let col = 0; col < columnCount; col++) {
272
- const headerWidth = measuredWidths.current.get(`header-${col}`);
273
- if (headerWidth && headerWidth > 0) {
274
- maxWidths[col] = Math.max(maxWidths[col], headerWidth);
275
- }
276
-
277
- for (let row = 0; row < rows.length; row++) {
278
- if (col >= rows[row].length) continue;
279
- const cellWidth = measuredWidths.current.get(`cell-${row}-${col}`);
280
- if (cellWidth && cellWidth > 0) {
281
- maxWidths[col] = Math.max(maxWidths[col], cellWidth);
282
- }
283
- }
284
-
285
- maxWidths[col] = Math.max(
286
- maxWidths[col] + COLUMN_MEASUREMENT_PADDING,
287
- MIN_COLUMN_WIDTH,
288
- );
289
- }
290
-
291
- widthsCalculated.current = true;
292
- setNeedsMeasurement(false);
293
- dispatch({ type: "RESET_WIDTHS", payload: maxWidths });
294
- },
295
- [columnCount, expectedCellKeys, needsMeasurement, rows],
296
- );
297
-
298
- const getAlignment = (
299
- index: number,
300
- ): "flex-start" | "center" | "flex-end" => {
301
- const align = alignments[index];
302
- if (align === "center") return "center";
303
- if (align === "right") return "flex-end";
304
- return "flex-start";
305
- };
306
-
307
- if (columnCount === 0) return null;
308
-
309
- const hasWidths = columnWidths.length === columnCount;
310
- const resolvedWidths = hasWidths ? columnWidths : estimatedColumnWidths;
311
-
312
- return (
313
- <View style={[styles.container, style]}>
314
- {needsMeasurement ? (
315
- <View style={styles.measurementContainer}>
316
- <View style={styles.measurementRow}>
317
- {headers.map((cell, colIndex) => (
318
- <View
319
- key={`measure-header-${colIndex}`}
320
- style={styles.measurementCell}
321
- onLayout={(e: LayoutChangeEvent) => {
322
- onCellLayout(
323
- `header-${colIndex}`,
324
- e.nativeEvent.layout.width,
325
- );
326
- }}
327
- >
328
- <CellContent node={cell} Renderer={Renderer} styles={styles} />
329
- </View>
330
- ))}
331
- </View>
332
- {rows.map((row, rowIndex) => (
333
- <View key={`measure-row-${rowIndex}`} style={styles.measurementRow}>
334
- {row.map((cell, colIndex) => (
335
- <View
336
- key={`measure-cell-${rowIndex}-${colIndex}`}
337
- style={styles.measurementCell}
338
- onLayout={(e: LayoutChangeEvent) => {
339
- onCellLayout(
340
- `cell-${rowIndex}-${colIndex}`,
341
- e.nativeEvent.layout.width,
342
- );
343
- }}
344
- >
345
- <CellContent
346
- node={cell}
347
- Renderer={Renderer}
348
- styles={styles}
349
- />
350
- </View>
351
- ))}
352
- </View>
353
- ))}
354
- </View>
355
- ) : null}
356
-
357
- <ScrollView
358
- horizontal
359
- showsHorizontalScrollIndicator
360
- style={styles.tableScroll}
361
- bounces={false}
362
- >
363
- <View
364
- style={[
365
- styles.table,
366
- {
367
- backgroundColor:
368
- style?.backgroundColor ?? theme.colors.surface ?? "#111827",
369
- },
370
- ]}
371
- >
372
- <View style={styles.headerRow}>
373
- {headers.map((cell, colIndex) => (
374
- <View
375
- key={`header-${colIndex}`}
376
- style={[
377
- styles.headerCell,
378
- {
379
- width: resolvedWidths[colIndex] ?? MIN_COLUMN_WIDTH,
380
- alignItems: getAlignment(colIndex),
381
- },
382
- colIndex === columnCount - 1 && styles.lastCell,
383
- ]}
384
- >
385
- <CellContent
386
- node={cell}
387
- Renderer={Renderer}
388
- styles={styles}
389
- textStyle={styles.headerText}
390
- />
391
- </View>
392
- ))}
393
- </View>
394
-
395
- {rows.map((row, rowIndex) => (
396
- <View
397
- key={`row-${rowIndex}`}
398
- style={[
399
- styles.bodyRow,
400
- rowIndex === rows.length - 1 && styles.lastRow,
401
- rowIndex % 2 === 0 ? styles.evenRow : styles.oddRow,
402
- ]}
403
- >
404
- {row.map((cell, colIndex) => (
405
- <View
406
- key={`cell-${rowIndex}-${colIndex}`}
407
- style={[
408
- styles.bodyCell,
409
- {
410
- width: resolvedWidths[colIndex] ?? MIN_COLUMN_WIDTH,
411
- alignItems: getAlignment(colIndex),
412
- },
413
- colIndex === columnCount - 1 && styles.lastCell,
414
- ]}
415
- >
416
- <CellContent
417
- node={cell}
418
- Renderer={Renderer}
419
- styles={styles}
420
- textStyle={styles.cellText}
421
- />
422
- </View>
423
- ))}
424
- </View>
425
- ))}
426
- </View>
427
- </ScrollView>
428
- </View>
429
- );
430
- };
431
-
432
- const CellContent: FC<{
433
- node: MarkdownNode;
434
- Renderer: ComponentType<NodeRendererProps>;
435
- styles: ReturnType<typeof createTableStyles>;
436
- textStyle?: StyleProp<TextStyle>;
437
- }> = ({ node, Renderer, styles, textStyle }) => {
438
- if (!node.children || node.children.length === 0) {
439
- return <Text style={textStyle}>{node.content ?? ""}</Text>;
440
- }
441
-
442
- return (
443
- <View style={styles.cellContentWrapper}>
444
- {node.children.map((child, idx) => (
445
- <Renderer
446
- key={idx}
447
- node={child}
448
- depth={0}
449
- inListItem={false}
450
- parentIsText={false}
451
- />
452
- ))}
453
- </View>
454
- );
455
- };
456
-
457
- const createTableStyles = (theme: MarkdownTheme) => {
458
- const colors = theme?.colors || {};
459
- const borderRadius = theme?.borderRadius || { m: 8 };
460
-
461
- return StyleSheet.create({
462
- container: {
463
- marginVertical: theme.spacing.s,
464
- },
465
- measurementContainer: {
466
- position: "absolute",
467
- opacity: 0,
468
- pointerEvents: "none",
469
- left: -9999,
470
- },
471
- measurementRow: {
472
- flexDirection: "row",
473
- },
474
- measurementCell: {
475
- paddingVertical: 10,
476
- paddingHorizontal: 12,
477
- },
478
- tableScroll: {
479
- flexGrow: 0,
480
- },
481
- table: {
482
- borderRadius: borderRadius.m,
483
- overflow: "hidden",
484
- borderWidth: 1,
485
- borderColor: colors.tableBorder || "#374151",
486
- },
487
- headerRow: {
488
- flexDirection: "row",
489
- backgroundColor: colors.tableHeader || "#1f2937",
490
- borderBottomWidth: 1,
491
- borderBottomColor: colors.tableBorder || "#374151",
492
- },
493
- bodyRow: {
494
- flexDirection: "row",
495
- borderBottomWidth: 1,
496
- borderBottomColor: colors.tableBorder || "#374151",
497
- },
498
- evenRow: {
499
- backgroundColor: colors.tableRowEven || "transparent",
500
- },
501
- oddRow: {
502
- backgroundColor: colors.tableRowOdd || "rgba(255,255,255,0.02)",
503
- },
504
- lastRow: {
505
- borderBottomWidth: 0,
506
- },
507
- headerCell: {
508
- flexShrink: 0,
509
- paddingVertical: 10,
510
- paddingHorizontal: 12,
511
- minWidth: 60,
512
- borderRightWidth: 1,
513
- borderRightColor: colors.tableBorder || "#374151",
514
- },
515
- bodyCell: {
516
- flexShrink: 0,
517
- paddingVertical: 10,
518
- paddingHorizontal: 12,
519
- minWidth: 60,
520
- borderRightWidth: 1,
521
- borderRightColor: colors.tableBorder || "#374151",
522
- justifyContent: "center",
523
- },
524
- lastCell: {
525
- borderRightWidth: 0,
526
- },
527
- headerText: {
528
- color: colors.tableHeaderText || "#9ca3af",
529
- fontSize: 12,
530
- fontWeight: "600",
531
- fontFamily: theme.fontFamilies?.regular,
532
- ...(Platform.OS === "android" && { includeFontPadding: false }),
533
- },
534
- cellText: {
535
- color: colors.text || "#e5e7eb",
536
- fontSize: 14,
537
- lineHeight: 20,
538
- fontFamily: theme.fontFamilies?.regular,
539
- ...(Platform.OS === "android" && { includeFontPadding: false }),
540
- },
541
- cellContentWrapper: {
542
- flexDirection: "row",
543
- flexWrap: "wrap",
544
- alignItems: "center",
545
- },
546
- });
547
- };
1
+ export { TableRenderer } from "./table/index";
@@ -15,4 +15,14 @@ export interface MarkdownSession extends HybridObject<{
15
15
  highlightPosition: number;
16
16
 
17
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;
18
28
  }