@openeditor/react-native-prose-editor 0.0.6 → 0.0.8

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 (68) hide show
  1. package/android/build.gradle +35 -0
  2. package/android/src/main/java/com/apollohg/editor/CaretGeometry.kt +2 -2
  3. package/android/src/main/java/com/apollohg/editor/EditorEditText.kt +138 -597
  4. package/android/src/main/java/com/apollohg/editor/EditorInputConnection.kt +13 -3
  5. package/android/src/main/java/com/apollohg/editor/InputSnapshotSupport.kt +6 -0
  6. package/android/src/main/java/com/apollohg/editor/NativeBlockEditorSurface.kt +1086 -0
  7. package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +1 -1
  8. package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +26 -50
  9. package/android/src/main/java/com/apollohg/editor/NativeImageLoader.kt +89 -0
  10. package/android/src/main/java/com/apollohg/editor/RemoteSelectionOverlayView.kt +31 -66
  11. package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +98 -33
  12. package/dist/EditorToolbar.d.ts +1 -1
  13. package/dist/EditorToolbar.js +19 -0
  14. package/dist/NativeEditorBridge.d.ts +62 -35
  15. package/dist/NativeEditorBridge.js +122 -79
  16. package/dist/NativeProseViewer.d.ts +13 -34
  17. package/dist/NativeProseViewer.js +40 -465
  18. package/dist/NativeRichTextEditor.d.ts +10 -1
  19. package/dist/NativeRichTextEditor.js +72 -34
  20. package/dist/index.d.ts +2 -2
  21. package/dist/index.js +2 -1
  22. package/dist/schemas.d.ts +11 -0
  23. package/dist/schemas.js +149 -0
  24. package/dist/useNativeEditor.d.ts +3 -3
  25. package/dist/useNativeEditor.js +3 -3
  26. package/ios/EditorCore.xcframework/ios-arm64/libeditor_core.a +0 -0
  27. package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a +0 -0
  28. package/ios/Generated_editor_core.swift +97 -2
  29. package/ios/NativeBlockEditorSurface.swift +1393 -0
  30. package/ios/NativeEditorExpoView.swift +3 -3
  31. package/ios/NativeEditorModule.swift +30 -45
  32. package/ios/NativeInputSupport.swift +396 -0
  33. package/ios/PositionBridge.swift +4 -4
  34. package/ios/RichTextEditorView.swift +333 -786
  35. package/ios/editor_coreFFI/editor_coreFFI.h +147 -70
  36. package/package.json +1 -1
  37. package/rust/android/arm64-v8a/libeditor_core.so +0 -0
  38. package/rust/android/armeabi-v7a/libeditor_core.so +0 -0
  39. package/rust/android/x86_64/libeditor_core.so +0 -0
  40. package/rust/bindings/kotlin/uniffi/editor_core/editor_core.kt +2087 -1399
  41. package/android/src/androidTest/AndroidManifest.xml +0 -8
  42. package/android/src/androidTest/java/com/apollohg/editor/MeasureHeightInstrumentedTest.kt +0 -53
  43. package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceCollaborationInitialSyncTest.kt +0 -241
  44. package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceImeRegressionTest.kt +0 -338
  45. package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceOutsideTapTest.kt +0 -789
  46. package/android/src/androidTest/java/com/apollohg/editor/NativeDevicePerformanceTest.kt +0 -350
  47. package/android/src/androidTest/java/com/apollohg/editor/NativeEditorOutsideTapActivity.kt +0 -5
  48. package/android/src/main/java/com/apollohg/editor/NativeProseViewerExpoView.kt +0 -315
  49. package/android/src/main/java/com/apollohg/editor/RenderBridge.kt +0 -2172
  50. package/android/src/sharedTest/java/com/apollohg/editor/NativePerformanceSupport.kt +0 -482
  51. package/android/src/test/java/com/apollohg/editor/CaretGeometryTest.kt +0 -137
  52. package/android/src/test/java/com/apollohg/editor/EditorEditTextHardwareKeyTest.kt +0 -321
  53. package/android/src/test/java/com/apollohg/editor/EditorInputConnectionTest.kt +0 -4202
  54. package/android/src/test/java/com/apollohg/editor/NativeEditorExpoViewTest.kt +0 -2581
  55. package/android/src/test/java/com/apollohg/editor/NativeEditorModuleTest.kt +0 -27
  56. package/android/src/test/java/com/apollohg/editor/NativePerformanceTest.kt +0 -197
  57. package/android/src/test/java/com/apollohg/editor/NativeProseViewerExpoViewTest.kt +0 -84
  58. package/android/src/test/java/com/apollohg/editor/NativeToolbarTest.kt +0 -544
  59. package/android/src/test/java/com/apollohg/editor/PositionBridgeTest.kt +0 -461
  60. package/android/src/test/java/com/apollohg/editor/RenderBridgeTest.kt +0 -2054
  61. package/android/src/test/java/com/apollohg/editor/RichTextEditorViewTest.kt +0 -1367
  62. package/ios/EditorLayoutManager.swift +0 -774
  63. package/ios/NativeProseViewerExpoView.swift +0 -276
  64. package/ios/RenderBridge.swift +0 -1708
  65. package/ios/Tests/NativePerformanceTests.swift +0 -687
  66. package/ios/Tests/PositionBridgeTests.swift +0 -706
  67. package/ios/Tests/RenderBridgeTests.swift +0 -2236
  68. package/ios/Tests/RichTextEditorViewTests.swift +0 -6548
@@ -3,478 +3,53 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.NativeProseViewer = NativeProseViewer;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("react");
6
- const expo_modules_core_1 = require("expo-modules-core");
7
6
  const react_native_1 = require("react-native");
8
- const addons_1 = require("./addons");
9
- const EditorTheme_1 = require("./EditorTheme");
10
- const heightCache_1 = require("./heightCache");
7
+ const NativeRichTextEditor_1 = require("./NativeRichTextEditor");
11
8
  const schemas_1 = require("./schemas");
12
- const NativeProseViewerView = (0, expo_modules_core_1.requireNativeViewManager)('NativeEditor', 'NativeProseViewer');
13
- let nativeProseViewerModule = null;
14
- function getNativeProseViewerModule() {
15
- if (!nativeProseViewerModule) {
16
- nativeProseViewerModule =
17
- (0, expo_modules_core_1.requireNativeModule)('NativeEditor');
18
- }
19
- return nativeProseViewerModule;
20
- }
21
- const serializedJsonCache = new WeakMap();
22
- const EMPTY_TEXT_BLOCK_PLACEHOLDER = '\u200B';
23
- function stringifyCachedJson(value) {
24
- if (value != null && typeof value === 'object') {
25
- const cached = serializedJsonCache.get(value);
26
- if (cached != null) {
27
- return cached;
28
- }
29
- const serialized = JSON.stringify(value);
30
- serializedJsonCache.set(value, serialized);
31
- return serialized;
32
- }
33
- return JSON.stringify(value);
34
- }
35
- function looksLikeRenderElementsJson(json) {
36
- for (let index = 0; index < json.length; index += 1) {
37
- const char = json[index];
38
- if (char === ' ' || char === '\n' || char === '\r' || char === '\t') {
39
- continue;
40
- }
41
- return char === '[';
42
- }
43
- return false;
44
- }
45
- function unicodeScalarLength(text) {
46
- let length = 0;
47
- for (const _char of text) {
48
- length += 1;
49
- }
50
- return length;
51
- }
52
- function normalizeMentionAttrs(node) {
53
- if (node == null || typeof node !== 'object') {
54
- return {};
55
- }
56
- const attrs = node.attrs;
57
- if (attrs == null || typeof attrs !== 'object' || Array.isArray(attrs)) {
58
- return {};
59
- }
60
- return attrs;
61
- }
62
- function baseMentionLabelFromAttrs(attrs) {
63
- const label = attrs.label;
64
- return typeof label === 'string' && label.length > 0 ? label : 'mention';
65
- }
66
- function resolveConfiguredMentionPrefix(prefix, mention) {
67
- const rawPrefix = typeof prefix === 'function' ? prefix(mention) : prefix;
68
- return typeof rawPrefix === 'string' && rawPrefix.length > 0 ? rawPrefix : undefined;
69
- }
70
- function mentionTriggerFromAttrs(attrs) {
71
- const trigger = attrs.mentionSuggestionChar;
72
- return typeof trigger === 'string' && trigger.length > 0 ? trigger : undefined;
73
- }
74
- function applyMentionPrefix(label, prefix) {
75
- if (!prefix || label.startsWith(prefix)) {
76
- return label;
77
- }
78
- return `${prefix}${label}`;
79
- }
80
- function resolveMentionRenderedLabel(mentionContext, prefix, trigger) {
81
- if (prefix !== undefined) {
82
- return applyMentionPrefix(mentionContext.label, resolveConfiguredMentionPrefix(prefix, mentionContext));
83
- }
84
- return applyMentionPrefix(mentionContext.label, trigger ?? mentionTriggerFromAttrs(mentionContext.attrs));
85
- }
86
- function collectMentionPayloadsByDocPos(document, mentionsAddon) {
87
- const mentions = new Map();
88
- const effectiveMentionPrefix = mentionsAddon?.prefix;
89
- const effectiveResolveMentionTheme = mentionsAddon?.resolveTheme;
90
- const defaultMentionTheme = mentionsAddon?.theme;
91
- const trigger = mentionsAddon?.trigger?.trim() || undefined;
92
- const visit = (node, pos, isRoot = false) => {
93
- if (node == null || typeof node !== 'object') {
94
- return pos;
95
- }
96
- const nodeRecord = node;
97
- const nodeType = typeof nodeRecord.type === 'string' ? nodeRecord.type : '';
98
- const content = Array.isArray(nodeRecord.content) ? nodeRecord.content : [];
99
- if (nodeType === 'text') {
100
- const text = typeof nodeRecord.text === 'string' ? nodeRecord.text : '';
101
- return pos + unicodeScalarLength(text);
102
- }
103
- if (nodeType === 'mention') {
104
- const attrs = normalizeMentionAttrs(nodeRecord);
105
- const label = baseMentionLabelFromAttrs(attrs);
106
- const mentionContext = { docPos: pos, label, attrs };
107
- const renderedLabel = resolveMentionRenderedLabel(mentionContext, effectiveMentionPrefix, trigger);
108
- const resolvedMentionTheme = effectiveResolveMentionTheme?.(mentionContext) ?? undefined;
109
- const mentionTheme = defaultMentionTheme || resolvedMentionTheme
110
- ? {
111
- ...(defaultMentionTheme ?? {}),
112
- ...(resolvedMentionTheme ?? {}),
113
- }
114
- : undefined;
115
- mentions.set(pos, {
116
- ...mentionContext,
117
- renderedLabel,
118
- mentionTheme,
119
- });
120
- }
121
- if (isRoot && nodeType === 'doc') {
122
- let nextPos = pos;
123
- for (const child of content) {
124
- nextPos = visit(child, nextPos);
125
- }
126
- return nextPos;
127
- }
128
- if (content.length === 0) {
129
- return pos + 1;
130
- }
131
- let nextPos = pos + 1;
132
- for (const child of content) {
133
- nextPos = visit(child, nextPos);
134
- }
135
- return nextPos + 1;
136
- };
137
- visit(document, 0, true);
138
- return mentions;
139
- }
140
- function applyResolvedMentionRendering(renderJson, mentionPayloadsByDocPos) {
141
- if (mentionPayloadsByDocPos.size === 0) {
142
- return renderJson;
143
- }
144
- let parsedElements;
9
+ function parseDocument(content) {
10
+ if (typeof content !== 'string')
11
+ return content;
145
12
  try {
146
- parsedElements = JSON.parse(renderJson);
13
+ const parsed = JSON.parse(content);
14
+ return parsed != null && typeof parsed === 'object'
15
+ ? parsed
16
+ : { type: 'doc', content: [] };
147
17
  }
148
18
  catch {
149
- return renderJson;
150
- }
151
- if (!Array.isArray(parsedElements)) {
152
- return renderJson;
19
+ return { type: 'doc', content: [] };
153
20
  }
154
- let didChange = false;
155
- const nextElements = parsedElements.map((element) => {
156
- if (element == null || typeof element !== 'object' || Array.isArray(element)) {
157
- return element;
158
- }
159
- const renderElement = element;
160
- if (renderElement.type !== 'opaqueInlineAtom' ||
161
- renderElement.nodeType !== 'mention' ||
162
- typeof renderElement.docPos !== 'number') {
163
- return element;
164
- }
165
- const mention = mentionPayloadsByDocPos.get(renderElement.docPos);
166
- if (!mention) {
167
- return element;
168
- }
169
- let nextElement = renderElement;
170
- if (renderElement.label !== mention.renderedLabel) {
171
- nextElement = { ...nextElement, label: mention.renderedLabel };
172
- didChange = true;
173
- }
174
- if (mention.mentionTheme && Object.keys(mention.mentionTheme).length > 0) {
175
- nextElement =
176
- nextElement === renderElement ? { ...nextElement } : nextElement;
177
- nextElement.mentionTheme = mention.mentionTheme;
178
- didChange = true;
179
- }
180
- return nextElement;
181
- });
182
- return didChange ? JSON.stringify(nextElements) : renderJson;
183
- }
184
- function isTopLevelSingleElementBlock(element) {
185
- return element.type === 'voidBlock' || element.type === 'opaqueBlockAtom';
186
- }
187
- function isEmptyParagraphPlaceholderText(text) {
188
- if (text.length === 0) {
189
- return false;
190
- }
191
- return Array.from(text).every((char) => char === EMPTY_TEXT_BLOCK_PLACEHOLDER);
192
- }
193
- function isCollapsibleEmptyParagraphText(text) {
194
- return Array.from(text).every((char) => char === EMPTY_TEXT_BLOCK_PLACEHOLDER);
195
21
  }
196
- function renderElementsJsonContainsOnlyEmptyParagraphs(renderJson) {
197
- let parsedElements;
198
- try {
199
- parsedElements = JSON.parse(renderJson);
200
- }
201
- catch {
202
- return false;
203
- }
204
- if (!Array.isArray(parsedElements)) {
205
- return false;
206
- }
207
- if (parsedElements.length === 0) {
22
+ function isEmptyDocument(document) {
23
+ const root = document;
24
+ if (!Array.isArray(root.content) || root.content.length === 0)
208
25
  return true;
209
- }
210
- let hasParagraph = false;
211
- let paragraphIsOpen = false;
212
- for (const element of parsedElements) {
213
- if (element == null || typeof element !== 'object' || Array.isArray(element)) {
214
- return false;
215
- }
216
- const renderElement = element;
217
- switch (renderElement.type) {
218
- case 'blockStart':
219
- if (paragraphIsOpen ||
220
- renderElement.nodeType !== 'paragraph' ||
221
- renderElement.depth !== 0) {
222
- return false;
223
- }
224
- paragraphIsOpen = true;
225
- hasParagraph = true;
226
- break;
227
- case 'textRun':
228
- if (!paragraphIsOpen ||
229
- typeof renderElement.text !== 'string' ||
230
- !isCollapsibleEmptyParagraphText(renderElement.text)) {
231
- return false;
232
- }
233
- break;
234
- case 'blockEnd':
235
- if (!paragraphIsOpen) {
236
- return false;
237
- }
238
- paragraphIsOpen = false;
239
- break;
240
- default:
241
- return false;
242
- }
243
- }
244
- return hasParagraph && !paragraphIsOpen;
245
- }
246
- function isTrailingEmptyParagraphRange(elements, start, endExclusive) {
247
- const startElement = elements[start];
248
- const endElement = elements[endExclusive - 1];
249
- if (startElement?.type !== 'blockStart' ||
250
- startElement.nodeType !== 'paragraph' ||
251
- startElement.depth !== 0 ||
252
- endElement?.type !== 'blockEnd') {
253
- return false;
254
- }
255
- const innerElements = elements.slice(start + 1, endExclusive - 1);
256
- return (innerElements.length > 0 &&
257
- innerElements.every((element) => element.type === 'textRun' &&
258
- typeof element.text === 'string' &&
259
- isEmptyParagraphPlaceholderText(element.text)));
260
- }
261
- function collapseTrailingEmptyParagraphRenderElements(renderJson) {
262
- let parsedElements;
263
- try {
264
- parsedElements = JSON.parse(renderJson);
265
- }
266
- catch {
267
- return renderJson;
268
- }
269
- if (!Array.isArray(parsedElements)) {
270
- return renderJson;
271
- }
272
- const elements = parsedElements;
273
- const topLevelRanges = [];
274
- for (let index = 0; index < elements.length; index += 1) {
275
- const element = elements[index];
276
- if (!element || typeof element !== 'object' || Array.isArray(element)) {
277
- continue;
278
- }
279
- if (element.type === 'blockStart' && element.depth === 0) {
280
- let nestingDepth = 1;
281
- let cursor = index + 1;
282
- while (cursor < elements.length && nestingDepth > 0) {
283
- const current = elements[cursor];
284
- if (current?.type === 'blockStart') {
285
- nestingDepth += 1;
286
- }
287
- else if (current?.type === 'blockEnd') {
288
- nestingDepth -= 1;
289
- }
290
- cursor += 1;
291
- }
292
- if (nestingDepth !== 0) {
293
- return renderJson;
294
- }
295
- topLevelRanges.push({ start: index, endExclusive: cursor });
296
- index = cursor - 1;
297
- continue;
298
- }
299
- if (isTopLevelSingleElementBlock(element)) {
300
- topLevelRanges.push({ start: index, endExclusive: index + 1 });
301
- }
302
- }
303
- if (topLevelRanges.length <= 1) {
304
- return renderJson;
305
- }
306
- let trimStart = null;
307
- for (let rangeIndex = topLevelRanges.length - 1; rangeIndex >= 1; rangeIndex -= 1) {
308
- const range = topLevelRanges[rangeIndex];
309
- if (!isTrailingEmptyParagraphRange(elements, range.start, range.endExclusive)) {
310
- break;
311
- }
312
- trimStart = range.start;
313
- }
314
- if (trimStart == null) {
315
- return renderJson;
316
- }
317
- return JSON.stringify(elements.slice(0, trimStart));
318
- }
319
- function serializeDocumentInput(document, schema) {
320
- if (typeof document === 'string') {
321
- try {
322
- const parsed = JSON.parse(document);
323
- const normalizedDocument = (0, schemas_1.normalizeDocumentJson)(parsed, schema);
324
- return {
325
- normalizedDocument,
326
- serializedContentJson: stringifyCachedJson(normalizedDocument),
327
- };
328
- }
329
- catch {
330
- return {
331
- normalizedDocument: null,
332
- serializedContentJson: document,
333
- };
334
- }
335
- }
336
- const normalizedDocument = (0, schemas_1.normalizeDocumentJson)(document, schema);
337
- return {
338
- normalizedDocument,
339
- serializedContentJson: stringifyCachedJson(normalizedDocument),
340
- };
341
- }
342
- function extractRenderError(json) {
343
- try {
344
- const parsed = JSON.parse(json);
345
- if (parsed == null || typeof parsed !== 'object' || Array.isArray(parsed)) {
346
- return null;
347
- }
348
- const error = parsed.error;
349
- return typeof error === 'string' ? error : null;
350
- }
351
- catch {
352
- return null;
353
- }
26
+ return root.content.every((node) => {
27
+ if (node == null || typeof node !== 'object')
28
+ return true;
29
+ const record = node;
30
+ return record.type === 'paragraph' &&
31
+ (!Array.isArray(record.content) || record.content.length === 0);
32
+ });
354
33
  }
355
- function NativeProseViewer({ ...props }) {
356
- const { contentRevision, contentJSONRevision, schema, theme, style, allowBase64Images = false, collapseTrailingEmptyParagraphs = true, enableLinkTaps = true, addons, onPressLink, contentId, containerWidth, } = props;
357
- const mentionPressHandler = addons?.mentions?.onPress;
358
- const contentJSON = 'contentJSON' in props ? props.contentJSON : undefined;
359
- const contentHTML = 'contentHTML' in props ? props.contentHTML : undefined;
360
- const resolvedContentRevision = contentRevision ?? contentJSONRevision;
361
- const documentSchema = (0, react_1.useMemo)(() => (0, addons_1.withMentionsSchema)(schema ?? schemas_1.tiptapSchema), [schema]);
362
- const { normalizedDocument, serializedContentJson } = (0, react_1.useMemo)(() => {
363
- if (contentJSON === undefined) {
364
- return {
365
- normalizedDocument: null,
366
- serializedContentJson: null,
367
- };
368
- }
369
- return serializeDocumentInput(contentJSON, documentSchema);
370
- }, [contentJSON, resolvedContentRevision, documentSchema]);
371
- const themeJson = (0, react_1.useMemo)(() => (0, EditorTheme_1.serializeEditorTheme)(theme), [theme]);
372
- const mentionPayloadsByDocPos = (0, react_1.useMemo)(() => normalizedDocument == null
373
- ? new Map()
374
- : collectMentionPayloadsByDocPos(normalizedDocument, addons?.mentions), [addons?.mentions, normalizedDocument]);
375
- const renderJson = (0, react_1.useMemo)(() => {
376
- const configJson = JSON.stringify({
377
- schema: documentSchema,
378
- ...(allowBase64Images ? { allowBase64Images } : {}),
379
- });
380
- const nextRenderJson = serializedContentJson != null
381
- ? getNativeProseViewerModule().renderDocumentJson(configJson, serializedContentJson)
382
- : getNativeProseViewerModule().renderDocumentHtml(configJson, contentHTML ?? '');
383
- const renderError = extractRenderError(nextRenderJson);
384
- if (renderError != null) {
385
- console.error(`NativeProseViewer: ${renderError}`);
386
- return '[]';
387
- }
388
- if (looksLikeRenderElementsJson(nextRenderJson)) {
389
- const collapsedRenderJson = collapseTrailingEmptyParagraphs
390
- ? collapseTrailingEmptyParagraphRenderElements(nextRenderJson)
391
- : nextRenderJson;
392
- return applyResolvedMentionRendering(collapsedRenderJson, mentionPayloadsByDocPos);
393
- }
394
- console.error('NativeProseViewer: native renderDocumentJson returned an invalid payload.');
395
- return '[]';
396
- }, [
397
- allowBase64Images,
398
- collapseTrailingEmptyParagraphs,
399
- contentHTML,
400
- documentSchema,
401
- mentionPayloadsByDocPos,
402
- serializedContentJson,
403
- ]);
404
- const renderJsonIsCollapsedEmpty = (0, react_1.useMemo)(() => collapseTrailingEmptyParagraphs &&
405
- renderElementsJsonContainsOnlyEmptyParagraphs(renderJson), [collapseTrailingEmptyParagraphs, renderJson]);
406
- const [contentHeight, setContentHeight] = (0, react_1.useState)(null);
407
- (0, react_1.useEffect)(() => {
408
- setContentHeight(null);
409
- }, [contentId]);
410
- const renderJsonHash = (0, react_1.useMemo)(() => (0, heightCache_1.computeRenderJsonHash)(renderJson), [renderJson]);
411
- const layoutContextKey = (0, react_1.useMemo)(() => containerWidth != null ? (0, heightCache_1.computeLayoutContextKey)(themeJson, containerWidth) : null, [themeJson, containerWidth]);
412
- const preMeasuredHeight = (0, react_1.useMemo)(() => {
413
- if (!contentId || layoutContextKey == null || containerWidth == null) {
414
- return null;
415
- }
416
- const cached = (0, heightCache_1.getHeightCache)(contentId, layoutContextKey, renderJsonHash);
417
- if (cached != null)
418
- return cached;
419
- const measured = getNativeProseViewerModule().measureContentHeight(renderJson, themeJson, containerWidth);
420
- if (measured > 0) {
421
- (0, heightCache_1.setHeightCache)(contentId, layoutContextKey, renderJsonHash, measured);
422
- }
423
- return measured > 0 ? measured : null;
424
- }, [contentId, containerWidth, renderJson, themeJson, layoutContextKey, renderJsonHash]);
425
- const handleContentHeightChange = (0, react_1.useCallback)((event) => {
426
- const density = react_native_1.Platform.OS === 'android' ? react_native_1.PixelRatio.get() : 1;
427
- const nextHeight = Math.ceil(event.nativeEvent.contentHeight / density);
428
- if (nextHeight < 0)
429
- return;
430
- if (nextHeight === 0 && !renderJsonIsCollapsedEmpty)
431
- return;
432
- if (nextHeight === 0) {
433
- setContentHeight((currentHeight) => currentHeight === 0 ? currentHeight : 0);
434
- return;
435
- }
436
- setContentHeight((currentHeight) => currentHeight === nextHeight ? currentHeight : nextHeight);
437
- if (contentId && layoutContextKey != null) {
438
- (0, heightCache_1.setHeightCache)(contentId, layoutContextKey, renderJsonHash, nextHeight);
439
- }
440
- }, [renderJsonIsCollapsedEmpty, contentId, layoutContextKey, renderJsonHash]);
441
- const handlePressMention = (0, react_1.useCallback)((event) => {
442
- if (!mentionPressHandler)
443
- return;
444
- const { docPos, label } = event.nativeEvent;
445
- const resolvedMention = mentionPayloadsByDocPos.get(docPos);
446
- mentionPressHandler({
447
- docPos,
448
- label: resolvedMention?.renderedLabel ?? label,
449
- attrs: resolvedMention?.attrs ?? {},
450
- });
451
- }, [mentionPayloadsByDocPos, mentionPressHandler]);
452
- const handlePressLink = (0, react_1.useCallback)((event) => {
453
- if (!onPressLink)
454
- return;
455
- onPressLink({
456
- href: event.nativeEvent.href,
457
- text: event.nativeEvent.text,
458
- });
459
- }, [onPressLink]);
460
- const nativeStyle = (0, react_1.useMemo)(() => {
461
- let measuredStyle = null;
462
- if (renderJsonIsCollapsedEmpty) {
463
- measuredStyle = { height: 0, minHeight: 0 };
464
- }
465
- else if (contentHeight != null && contentHeight > 0) {
466
- measuredStyle = { minHeight: contentHeight };
467
- }
468
- else if (preMeasuredHeight != null && preMeasuredHeight > 0) {
469
- measuredStyle = { minHeight: preMeasuredHeight };
470
- }
471
- return [
472
- { minHeight: renderJsonIsCollapsedEmpty ? 0 : 1 },
473
- style,
474
- measuredStyle,
475
- ];
476
- }, [contentHeight, preMeasuredHeight, renderJsonIsCollapsedEmpty, style]);
477
- return ((0, jsx_runtime_1.jsx)(NativeProseViewerView, { style: nativeStyle, renderJson: renderJson, themeJson: themeJson, collapsesWhenEmpty: collapseTrailingEmptyParagraphs, enableLinkTaps: enableLinkTaps, interceptLinkTaps: typeof onPressLink === 'function', onContentHeightChange: handleContentHeightChange, onPressLink: typeof onPressLink === 'function' ? handlePressLink : undefined, onPressMention: typeof mentionPressHandler === 'function'
478
- ? handlePressMention
479
- : undefined }));
34
+ /**
35
+ * Read-only presentation of the canonical block-editor surface.
36
+ *
37
+ * The viewer deliberately shares the editor's document parser, render tree,
38
+ * layout surface, theme implementation, and native views. Keeping it as a
39
+ * configured editor prevents the read-only product from becoming a second
40
+ * rendering engine with subtly different schema behavior.
41
+ */
42
+ function NativeProseViewer(props) {
43
+ const { schema = schemas_1.tiptapSchema, theme, style, allowBase64Images = false, } = props;
44
+ const documentSchema = schema;
45
+ const document = (0, react_1.useMemo)(() => 'contentJSON' in props && props.contentJSON != null
46
+ ? (0, schemas_1.normalizeDocumentJson)(parseDocument(props.contentJSON), documentSchema)
47
+ : undefined, [documentSchema, props.contentJSON, props.revision]);
48
+ if ('collapseWhenEmpty' in props &&
49
+ props.collapseWhenEmpty &&
50
+ document &&
51
+ isEmptyDocument(document)) {
52
+ return (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [style, { height: 0, minHeight: 0 }] });
53
+ }
54
+ return ((0, jsx_runtime_1.jsx)(NativeRichTextEditor_1.NativeRichTextEditor, { value: 'contentHTML' in props ? props.contentHTML : undefined, valueJSON: document, valueJSONRevision: props.revision, schema: documentSchema, editable: false, showToolbar: false, heightBehavior: "autoGrow", allowBase64Images: allowBase64Images, allowImageResizing: false, theme: theme, containerStyle: style }));
480
55
  }
@@ -4,7 +4,7 @@ import { type ActiveState, type DocumentJSON, type HistoryState, type Selection
4
4
  import { type EditorToolbarHeadingLevel, type EditorToolbarItem } from './EditorToolbar';
5
5
  import { type EditorTheme } from './EditorTheme';
6
6
  import { type EditorAddons } from './addons';
7
- import { type ImageNodeAttributes, type SchemaDefinition } from './schemas';
7
+ import { type ColumnsFragmentOptions, type ImageNodeAttributes, type SchemaDefinition, type TableFragmentOptions } from './schemas';
8
8
  export type NativeRichTextEditorHeightBehavior = 'fixed' | 'autoGrow';
9
9
  export type NativeRichTextEditorToolbarPlacement = 'keyboard' | 'inline';
10
10
  export type NativeRichTextEditorValueJSONUpdateMode = 'replace' | 'reset';
@@ -137,6 +137,15 @@ export interface NativeRichTextEditorRef {
137
137
  insertNode(nodeType: string): void;
138
138
  /** Insert a block image node with the given source and optional metadata. */
139
139
  insertImage(src: string, attrs?: Omit<ImageNodeAttributes, 'src'>): void;
140
+ /** Insert a structured columns layout at the current selection. */
141
+ insertColumns(options?: ColumnsFragmentOptions): void;
142
+ /** Insert a structured table at the current selection. */
143
+ insertTable(options?: TableFragmentOptions): void;
144
+ insertTableRow(after?: boolean): void;
145
+ deleteTableRow(): void;
146
+ insertTableColumn(after?: boolean): void;
147
+ deleteTableColumn(): void;
148
+ moveTableCell(forward?: boolean): void;
140
149
  /** Insert text at the current cursor position. */
141
150
  insertText(text: string): void;
142
151
  /** Insert HTML content at the current selection. */