@openeditor/react-native-prose-editor 0.0.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 (91) hide show
  1. package/LICENSE +160 -0
  2. package/README.md +180 -0
  3. package/android/build.gradle +69 -0
  4. package/android/consumer-rules.pro +18 -0
  5. package/android/src/androidTest/AndroidManifest.xml +8 -0
  6. package/android/src/androidTest/java/com/apollohg/editor/MeasureHeightInstrumentedTest.kt +53 -0
  7. package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceCollaborationInitialSyncTest.kt +241 -0
  8. package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceImeRegressionTest.kt +338 -0
  9. package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceOutsideTapTest.kt +789 -0
  10. package/android/src/androidTest/java/com/apollohg/editor/NativeDevicePerformanceTest.kt +350 -0
  11. package/android/src/androidTest/java/com/apollohg/editor/NativeEditorOutsideTapActivity.kt +5 -0
  12. package/android/src/main/assets/editor-icons/MaterialIcons.json +2236 -0
  13. package/android/src/main/assets/editor-icons/MaterialIcons.ttf +0 -0
  14. package/android/src/main/java/com/apollohg/editor/CaretGeometry.kt +50 -0
  15. package/android/src/main/java/com/apollohg/editor/EditorAddons.kt +135 -0
  16. package/android/src/main/java/com/apollohg/editor/EditorEditText.kt +4527 -0
  17. package/android/src/main/java/com/apollohg/editor/EditorHeightBehavior.kt +14 -0
  18. package/android/src/main/java/com/apollohg/editor/EditorInputConnection.kt +999 -0
  19. package/android/src/main/java/com/apollohg/editor/EditorTheme.kt +474 -0
  20. package/android/src/main/java/com/apollohg/editor/ImageResizeOverlayView.kt +199 -0
  21. package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +3137 -0
  22. package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +561 -0
  23. package/android/src/main/java/com/apollohg/editor/NativeProseViewerExpoView.kt +315 -0
  24. package/android/src/main/java/com/apollohg/editor/NativeToolbar.kt +1413 -0
  25. package/android/src/main/java/com/apollohg/editor/PositionBridge.kt +133 -0
  26. package/android/src/main/java/com/apollohg/editor/RemoteSelectionOverlayView.kt +315 -0
  27. package/android/src/main/java/com/apollohg/editor/RenderBridge.kt +2172 -0
  28. package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +399 -0
  29. package/android/src/sharedTest/java/com/apollohg/editor/NativePerformanceSupport.kt +482 -0
  30. package/android/src/test/java/com/apollohg/editor/CaretGeometryTest.kt +137 -0
  31. package/android/src/test/java/com/apollohg/editor/EditorEditTextHardwareKeyTest.kt +321 -0
  32. package/android/src/test/java/com/apollohg/editor/EditorInputConnectionTest.kt +4202 -0
  33. package/android/src/test/java/com/apollohg/editor/NativeEditorExpoViewTest.kt +2581 -0
  34. package/android/src/test/java/com/apollohg/editor/NativeEditorModuleTest.kt +27 -0
  35. package/android/src/test/java/com/apollohg/editor/NativePerformanceTest.kt +197 -0
  36. package/android/src/test/java/com/apollohg/editor/NativeProseViewerExpoViewTest.kt +84 -0
  37. package/android/src/test/java/com/apollohg/editor/NativeToolbarTest.kt +544 -0
  38. package/android/src/test/java/com/apollohg/editor/PositionBridgeTest.kt +461 -0
  39. package/android/src/test/java/com/apollohg/editor/RenderBridgeTest.kt +2054 -0
  40. package/android/src/test/java/com/apollohg/editor/RichTextEditorViewTest.kt +1367 -0
  41. package/app.plugin.js +62 -0
  42. package/dist/EditorTheme.d.ts +113 -0
  43. package/dist/EditorTheme.js +29 -0
  44. package/dist/EditorToolbar.d.ts +191 -0
  45. package/dist/EditorToolbar.js +1095 -0
  46. package/dist/NativeEditorBridge.d.ts +325 -0
  47. package/dist/NativeEditorBridge.js +933 -0
  48. package/dist/NativeProseViewer.d.ts +53 -0
  49. package/dist/NativeProseViewer.js +480 -0
  50. package/dist/NativeRichTextEditor.d.ts +183 -0
  51. package/dist/NativeRichTextEditor.js +2574 -0
  52. package/dist/YjsCollaboration.d.ts +90 -0
  53. package/dist/YjsCollaboration.js +743 -0
  54. package/dist/addons.d.ts +100 -0
  55. package/dist/addons.js +82 -0
  56. package/dist/heightCache.d.ts +6 -0
  57. package/dist/heightCache.js +45 -0
  58. package/dist/index.d.ts +10 -0
  59. package/dist/index.js +30 -0
  60. package/dist/schemas.d.ts +37 -0
  61. package/dist/schemas.js +263 -0
  62. package/dist/useNativeEditor.d.ts +42 -0
  63. package/dist/useNativeEditor.js +124 -0
  64. package/expo-module.config.json +9 -0
  65. package/ios/EditorAddons.swift +327 -0
  66. package/ios/EditorCore.xcframework/Info.plist +44 -0
  67. package/ios/EditorCore.xcframework/ios-arm64/libeditor_core.a +0 -0
  68. package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a +0 -0
  69. package/ios/EditorLayoutManager.swift +774 -0
  70. package/ios/EditorTheme.swift +515 -0
  71. package/ios/Generated_editor_core.swift +1513 -0
  72. package/ios/NativeEditorExpoView.swift +3593 -0
  73. package/ios/NativeEditorModule.swift +624 -0
  74. package/ios/NativeProseViewerExpoView.swift +276 -0
  75. package/ios/PositionBridge.swift +558 -0
  76. package/ios/ReactNativeProseEditor.podspec +49 -0
  77. package/ios/RenderBridge.swift +1708 -0
  78. package/ios/RichTextEditorView.swift +6150 -0
  79. package/ios/Tests/NativePerformanceTests.swift +687 -0
  80. package/ios/Tests/PositionBridgeTests.swift +706 -0
  81. package/ios/Tests/RenderBridgeTests.swift +2236 -0
  82. package/ios/Tests/RichTextEditorViewTests.swift +6548 -0
  83. package/ios/editor_coreFFI/editor_coreFFI.h +1289 -0
  84. package/ios/editor_coreFFI/module.modulemap +7 -0
  85. package/ios/editor_coreFFI.h +904 -0
  86. package/ios/editor_coreFFI.modulemap +7 -0
  87. package/package.json +74 -0
  88. package/rust/android/arm64-v8a/libeditor_core.so +0 -0
  89. package/rust/android/armeabi-v7a/libeditor_core.so +0 -0
  90. package/rust/android/x86_64/libeditor_core.so +0 -0
  91. package/rust/bindings/kotlin/uniffi/editor_core/editor_core.kt +2563 -0
@@ -0,0 +1,2574 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NativeRichTextEditor = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const react_native_1 = require("react-native");
7
+ const expo_modules_core_1 = require("expo-modules-core");
8
+ const NativeEditorBridge_1 = require("./NativeEditorBridge");
9
+ const EditorToolbar_1 = require("./EditorToolbar");
10
+ const EditorTheme_1 = require("./EditorTheme");
11
+ const addons_1 = require("./addons");
12
+ const schemas_1 = require("./schemas");
13
+ const NativeEditorView = (0, expo_modules_core_1.requireNativeViewManager)('NativeEditor');
14
+ const DEV_NATIVE_VIEW_KEY = __DEV__
15
+ ? `native-editor-dev:${Math.random().toString(36).slice(2)}`
16
+ : 'native-editor';
17
+ const LINK_TOOLBAR_ACTION_KEY = '__native-editor-link__';
18
+ const IMAGE_TOOLBAR_ACTION_KEY = '__native-editor-image__';
19
+ const DEFAULT_MENTION_TRIGGER = '@';
20
+ const MAX_INLINE_MENTION_SUGGESTIONS = 8;
21
+ const INLINE_TOOLBAR_BORDER_COLOR = '#E5E5EA';
22
+ function mapToolbarChildForNative(item, activeState, editable, onRequestLink, onRequestImage) {
23
+ if (item.type === 'link') {
24
+ return {
25
+ type: 'action',
26
+ key: LINK_TOOLBAR_ACTION_KEY,
27
+ label: item.label,
28
+ icon: item.icon,
29
+ placement: item.placement,
30
+ isActive: activeState.marks.link === true,
31
+ isDisabled: !editable || !onRequestLink || !activeState.allowedMarks.includes('link'),
32
+ };
33
+ }
34
+ if (item.type === 'image') {
35
+ return {
36
+ type: 'action',
37
+ key: IMAGE_TOOLBAR_ACTION_KEY,
38
+ label: item.label,
39
+ icon: item.icon,
40
+ placement: item.placement,
41
+ isActive: false,
42
+ isDisabled: !editable ||
43
+ !onRequestImage ||
44
+ !activeState.insertableNodes.includes(schemas_1.IMAGE_NODE_NAME),
45
+ };
46
+ }
47
+ return item;
48
+ }
49
+ function mapToolbarItemsForNative(items, activeState, editable, onRequestLink, onRequestImage) {
50
+ return items.map((item) => {
51
+ if (item.type === 'group') {
52
+ return {
53
+ ...item,
54
+ items: item.items.map((child) => mapToolbarChildForNative(child, activeState, editable, onRequestLink, onRequestImage)),
55
+ };
56
+ }
57
+ if (item.type === 'separator') {
58
+ return item;
59
+ }
60
+ return mapToolbarChildForNative(item, activeState, editable, onRequestLink, onRequestImage);
61
+ });
62
+ }
63
+ function isImageDataUrl(value) {
64
+ return /^data:image\//i.test(value.trim());
65
+ }
66
+ function isRetryableNativeCommandBlock(reason) {
67
+ return reason === 'composition' || (react_native_1.Platform.OS === 'android' && reason === 'pendingUpdate');
68
+ }
69
+ function restoreSelectionInBridge(bridge, selection) {
70
+ if (selection.type === 'text') {
71
+ const { anchor, head } = selection;
72
+ if (anchor == null || head == null) {
73
+ return false;
74
+ }
75
+ bridge.setSelection(anchor, head);
76
+ return true;
77
+ }
78
+ if (selection.type === 'node') {
79
+ const { pos } = selection;
80
+ if (pos == null) {
81
+ return false;
82
+ }
83
+ bridge.setSelection(pos, pos);
84
+ return true;
85
+ }
86
+ return false;
87
+ }
88
+ function isPromiseLike(value) {
89
+ return (value != null &&
90
+ typeof value === 'object' &&
91
+ 'then' in value &&
92
+ typeof value.then === 'function');
93
+ }
94
+ function isRecord(value) {
95
+ return value != null && typeof value === 'object' && !Array.isArray(value);
96
+ }
97
+ function resolveMentionTrigger(addons) {
98
+ return addons?.mentions?.trigger?.trim() || DEFAULT_MENTION_TRIGGER;
99
+ }
100
+ function resolveMentionSuggestionLabel(suggestion, trigger) {
101
+ return suggestion.label?.trim() || `${trigger}${suggestion.title}`;
102
+ }
103
+ function filterMentionSuggestions(suggestions, query, trigger) {
104
+ const normalizedQuery = query.trim().toLowerCase();
105
+ const filtered = normalizedQuery.length === 0
106
+ ? suggestions
107
+ : suggestions.filter((suggestion) => {
108
+ const label = resolveMentionSuggestionLabel(suggestion, trigger);
109
+ return (suggestion.title.toLowerCase().includes(normalizedQuery) ||
110
+ label.toLowerCase().includes(normalizedQuery) ||
111
+ suggestion.subtitle?.toLowerCase().includes(normalizedQuery) === true);
112
+ });
113
+ return filtered.slice(0, MAX_INLINE_MENTION_SUGGESTIONS);
114
+ }
115
+ function resolveMentionSuggestionAttrs(suggestion, trigger) {
116
+ const attrs = { ...(suggestion.attrs ?? {}) };
117
+ if (!('label' in attrs)) {
118
+ attrs.label = resolveMentionSuggestionLabel(suggestion, trigger);
119
+ }
120
+ if (!('mentionSuggestionChar' in attrs)) {
121
+ attrs.mentionSuggestionChar = trigger;
122
+ }
123
+ return attrs;
124
+ }
125
+ function mergeMentionSuggestionTheme(baseTheme, resolvedTheme) {
126
+ if (baseTheme == null && resolvedTheme == null) {
127
+ return undefined;
128
+ }
129
+ const merged = {
130
+ ...(baseTheme ?? {}),
131
+ ...(resolvedTheme ?? {}),
132
+ };
133
+ if (resolvedTheme?.textColor != null && resolvedTheme.optionTextColor == null) {
134
+ merged.optionTextColor = resolvedTheme.textColor;
135
+ }
136
+ return merged;
137
+ }
138
+ const AUTO_LINK_URL_REGEX = /(?:https?:\/\/|www\.)\S+/giu;
139
+ const AUTO_LINK_INLINE_PLACEHOLDER = '\uFFFC';
140
+ const AUTO_LINK_LEADING_BOUNDARY_CHARS = new Set(['(', '[', '{', '<', '"', "'"]);
141
+ const AUTO_LINK_TRAILING_DELIMITER_CHARS = new Set([
142
+ '.',
143
+ ',',
144
+ '!',
145
+ '?',
146
+ ';',
147
+ ':',
148
+ ')',
149
+ ']',
150
+ '}',
151
+ '>',
152
+ '"',
153
+ "'",
154
+ ]);
155
+ const AUTO_LINK_ALWAYS_TRIM_CHARS = new Set(['.', ',', '!', '?', ';', ':']);
156
+ const AUTO_LINK_MATCHED_CLOSERS = {
157
+ ')': '(',
158
+ ']': '[',
159
+ '}': '{',
160
+ };
161
+ function unicodeScalars(text) {
162
+ return Array.from(text);
163
+ }
164
+ function unicodeScalarCount(text) {
165
+ return unicodeScalars(text).length;
166
+ }
167
+ function hasDocumentLinkMark(marks) {
168
+ if (!Array.isArray(marks)) {
169
+ return false;
170
+ }
171
+ return marks.some((mark) => isRecord(mark) && typeof mark.type === 'string' && mark.type === 'link');
172
+ }
173
+ function isInlineDocumentNode(node) {
174
+ if (!isRecord(node)) {
175
+ return false;
176
+ }
177
+ if (node.type === 'text') {
178
+ return true;
179
+ }
180
+ return !Array.isArray(node.content);
181
+ }
182
+ function isInlineTextBlockNode(node) {
183
+ const content = Array.isArray(node.content) ? node.content : [];
184
+ return content.length > 0 && content.every((child) => isInlineDocumentNode(child));
185
+ }
186
+ function countOccurrences(text, target) {
187
+ let count = 0;
188
+ for (const char of text) {
189
+ if (char === target) {
190
+ count += 1;
191
+ }
192
+ }
193
+ return count;
194
+ }
195
+ function trimAutoLinkTrailingPunctuation(value) {
196
+ let result = value;
197
+ while (result.length > 0) {
198
+ const chars = unicodeScalars(result);
199
+ const lastChar = chars[chars.length - 1];
200
+ if (!lastChar) {
201
+ break;
202
+ }
203
+ if (AUTO_LINK_ALWAYS_TRIM_CHARS.has(lastChar)) {
204
+ chars.pop();
205
+ result = chars.join('');
206
+ continue;
207
+ }
208
+ const matchingOpener = AUTO_LINK_MATCHED_CLOSERS[lastChar];
209
+ if (matchingOpener &&
210
+ countOccurrences(result, lastChar) > countOccurrences(result, matchingOpener)) {
211
+ chars.pop();
212
+ result = chars.join('');
213
+ continue;
214
+ }
215
+ if ((lastChar === '"' || lastChar === "'") &&
216
+ countOccurrences(result, lastChar) % 2 !== 0) {
217
+ chars.pop();
218
+ result = chars.join('');
219
+ continue;
220
+ }
221
+ break;
222
+ }
223
+ return result;
224
+ }
225
+ function normalizeAutoDetectedHref(value) {
226
+ const trimmed = trimAutoLinkTrailingPunctuation(value);
227
+ if (!trimmed) {
228
+ return null;
229
+ }
230
+ const normalized = /^www\./iu.test(trimmed) ? `https://${trimmed}` : trimmed;
231
+ try {
232
+ new URL(normalized);
233
+ return normalized;
234
+ }
235
+ catch {
236
+ return null;
237
+ }
238
+ }
239
+ function isAutoLinkBoundaryChar(char) {
240
+ if (!char) {
241
+ return true;
242
+ }
243
+ return (/\s/u.test(char) ||
244
+ char === AUTO_LINK_INLINE_PLACEHOLDER ||
245
+ AUTO_LINK_LEADING_BOUNDARY_CHARS.has(char));
246
+ }
247
+ function isAutoLinkTrailingDelimiterChar(char) {
248
+ if (!char) {
249
+ return true;
250
+ }
251
+ return (/\s/u.test(char) ||
252
+ char === AUTO_LINK_INLINE_PLACEHOLDER ||
253
+ AUTO_LINK_TRAILING_DELIMITER_CHARS.has(char));
254
+ }
255
+ function codeUnitBoundariesForScalars(chars) {
256
+ const boundaries = [0];
257
+ let offset = 0;
258
+ for (const char of chars) {
259
+ offset += char.length;
260
+ boundaries.push(offset);
261
+ }
262
+ return boundaries;
263
+ }
264
+ function codeUnitOffsetToScalarIndex(boundaries, offset) {
265
+ let scalarIndex = 0;
266
+ while (scalarIndex + 1 < boundaries.length && boundaries[scalarIndex + 1] <= offset) {
267
+ scalarIndex += 1;
268
+ }
269
+ return scalarIndex;
270
+ }
271
+ function buildAutoLinkInlineBlock(node, pos) {
272
+ const chars = [];
273
+ const docPositions = [];
274
+ const linked = [];
275
+ const content = Array.isArray(node.content) ? node.content : [];
276
+ let nextPos = pos + 1;
277
+ for (const child of content) {
278
+ if (!isRecord(child)) {
279
+ continue;
280
+ }
281
+ if (child.type === 'text') {
282
+ const childText = typeof child.text === 'string' ? child.text : '';
283
+ const childChars = unicodeScalars(childText);
284
+ const hasLink = hasDocumentLinkMark(child.marks);
285
+ for (const char of childChars) {
286
+ chars.push(char);
287
+ docPositions.push(nextPos);
288
+ linked.push(hasLink);
289
+ nextPos += 1;
290
+ }
291
+ continue;
292
+ }
293
+ chars.push(child.type === 'hardBreak' ? '\n' : AUTO_LINK_INLINE_PLACEHOLDER);
294
+ docPositions.push(nextPos);
295
+ linked.push(false);
296
+ nextPos += 1;
297
+ }
298
+ return {
299
+ chars,
300
+ docPositions,
301
+ linked,
302
+ contentStart: pos + 1,
303
+ contentEnd: nextPos,
304
+ nextPos: nextPos + 1,
305
+ };
306
+ }
307
+ function findAutoLinkCandidateInInlineBlock(block, cursorDocPos) {
308
+ if (cursorDocPos < block.contentStart || cursorDocPos > block.contentEnd) {
309
+ return null;
310
+ }
311
+ let localIndex = 0;
312
+ while (localIndex < block.docPositions.length &&
313
+ block.docPositions[localIndex] < cursorDocPos) {
314
+ localIndex += 1;
315
+ }
316
+ if (localIndex === 0) {
317
+ return null;
318
+ }
319
+ if (cursorDocPos < block.contentEnd &&
320
+ !isAutoLinkTrailingDelimiterChar(block.chars[localIndex - 1])) {
321
+ return null;
322
+ }
323
+ const prefixChars = block.chars.slice(0, localIndex);
324
+ const prefixText = prefixChars.join('');
325
+ if (!prefixText) {
326
+ return null;
327
+ }
328
+ const boundaries = codeUnitBoundariesForScalars(prefixChars);
329
+ AUTO_LINK_URL_REGEX.lastIndex = 0;
330
+ let lastMatch = null;
331
+ for (const match of prefixText.matchAll(AUTO_LINK_URL_REGEX)) {
332
+ lastMatch = match;
333
+ }
334
+ if (!lastMatch || typeof lastMatch.index !== 'number') {
335
+ return null;
336
+ }
337
+ const rawStartScalar = codeUnitOffsetToScalarIndex(boundaries, lastMatch.index);
338
+ const normalizedHref = normalizeAutoDetectedHref(lastMatch[0]);
339
+ const trimmedText = trimAutoLinkTrailingPunctuation(lastMatch[0]);
340
+ if (!normalizedHref || !trimmedText) {
341
+ return null;
342
+ }
343
+ const candidateEndScalar = rawStartScalar + unicodeScalarCount(trimmedText);
344
+ if (candidateEndScalar > prefixChars.length) {
345
+ return null;
346
+ }
347
+ if (!isAutoLinkBoundaryChar(prefixChars[rawStartScalar - 1])) {
348
+ return null;
349
+ }
350
+ for (let index = candidateEndScalar; index < localIndex; index += 1) {
351
+ if (!isAutoLinkTrailingDelimiterChar(prefixChars[index])) {
352
+ return null;
353
+ }
354
+ }
355
+ for (let index = rawStartScalar; index < candidateEndScalar; index += 1) {
356
+ if (block.linked[index]) {
357
+ return null;
358
+ }
359
+ }
360
+ const docFrom = block.docPositions[rawStartScalar];
361
+ const docTo = candidateEndScalar < block.docPositions.length
362
+ ? block.docPositions[candidateEndScalar]
363
+ : block.contentEnd;
364
+ if (!(docTo > docFrom)) {
365
+ return null;
366
+ }
367
+ return {
368
+ docFrom,
369
+ docTo,
370
+ href: normalizedHref,
371
+ };
372
+ }
373
+ function findAutoLinkCandidateInDocument(document, cursorDocPos) {
374
+ const visit = (node, pos, isRoot = false) => {
375
+ if (!isRecord(node)) {
376
+ return { candidate: null, nextPos: pos };
377
+ }
378
+ const nodeType = typeof node.type === 'string' ? node.type : '';
379
+ const content = Array.isArray(node.content) ? node.content : [];
380
+ if (nodeType === 'text') {
381
+ const text = typeof node.text === 'string' ? node.text : '';
382
+ return { candidate: null, nextPos: pos + unicodeScalarCount(text) };
383
+ }
384
+ if (isRoot && nodeType === 'doc') {
385
+ let nextPos = pos;
386
+ for (const child of content) {
387
+ const result = visit(child, nextPos);
388
+ if (result.candidate) {
389
+ return result;
390
+ }
391
+ nextPos = result.nextPos;
392
+ }
393
+ return { candidate: null, nextPos };
394
+ }
395
+ if (isInlineTextBlockNode(node)) {
396
+ const block = buildAutoLinkInlineBlock(node, pos);
397
+ return {
398
+ candidate: findAutoLinkCandidateInInlineBlock(block, cursorDocPos),
399
+ nextPos: block.nextPos,
400
+ };
401
+ }
402
+ if (content.length === 0) {
403
+ return { candidate: null, nextPos: pos + 1 };
404
+ }
405
+ let nextPos = pos + 1;
406
+ for (const child of content) {
407
+ const result = visit(child, nextPos);
408
+ if (result.candidate) {
409
+ return result;
410
+ }
411
+ nextPos = result.nextPos;
412
+ }
413
+ return { candidate: null, nextPos: nextPos + 1 };
414
+ };
415
+ return visit(document, 0, true).candidate;
416
+ }
417
+ function didContentChange(previousDocumentVersion, update) {
418
+ if (!update) {
419
+ return false;
420
+ }
421
+ return (previousDocumentVersion == null ||
422
+ typeof update.documentVersion !== 'number' ||
423
+ update.documentVersion !== previousDocumentVersion);
424
+ }
425
+ function documentVersionFromUpdateJson(json) {
426
+ try {
427
+ const parsed = JSON.parse(json);
428
+ return typeof parsed.documentVersion === 'number' ? parsed.documentVersion : null;
429
+ }
430
+ catch {
431
+ return null;
432
+ }
433
+ }
434
+ function isCurrentNativeEditorEvent(event, bridge) {
435
+ if (react_native_1.Platform.OS === 'android') {
436
+ return typeof event.editorId === 'number' && bridge?.editorId === event.editorId;
437
+ }
438
+ if (typeof event.editorId !== 'number')
439
+ return true;
440
+ return event.editorId === 0 || bridge?.editorId === event.editorId;
441
+ }
442
+ function computeRenderedTextLength(elements) {
443
+ let len = 0;
444
+ let blockCount = 0;
445
+ for (const el of elements) {
446
+ if (el.type === 'blockStart' && el.listContext) {
447
+ len += el.listContext.kind === 'task'
448
+ ? unicodeScalarCount(el.listContext.checked ? '☑ ' : '☐ ')
449
+ : el.listContext.ordered
450
+ ? unicodeScalarCount(`${el.listContext.index}. `)
451
+ : unicodeScalarCount('• ');
452
+ }
453
+ else if (el.type === 'textRun' && el.text) {
454
+ len += unicodeScalarCount(el.text);
455
+ }
456
+ else if (el.type === 'voidInline' ||
457
+ el.type === 'voidBlock' ||
458
+ el.type === 'opaqueInlineAtom' ||
459
+ el.type === 'opaqueBlockAtom') {
460
+ if (el.type === 'opaqueInlineAtom' || el.type === 'opaqueBlockAtom') {
461
+ const visibleText = el.nodeType === 'mention' ? (el.label ?? '?') : `[${el.label ?? '?'}]`;
462
+ len += unicodeScalarCount(visibleText);
463
+ }
464
+ else {
465
+ // U+FFFC placeholder / hard break
466
+ len += 1;
467
+ }
468
+ }
469
+ else if (el.type === 'blockEnd') {
470
+ blockCount++;
471
+ }
472
+ }
473
+ // Block breaks add 1 scalar each, except the last block
474
+ if (blockCount > 1)
475
+ len += blockCount - 1;
476
+ return len;
477
+ }
478
+ function serializeRemoteSelections(remoteSelections) {
479
+ if (!remoteSelections || remoteSelections.length === 0) {
480
+ return undefined;
481
+ }
482
+ return stringifyCachedJson(remoteSelections);
483
+ }
484
+ function areToolbarFramesEqual(left, right) {
485
+ return (left?.x === right?.x &&
486
+ left?.y === right?.y &&
487
+ left?.width === right?.width &&
488
+ left?.height === right?.height);
489
+ }
490
+ function serializeToolbarFrames(frames) {
491
+ if (!frames || frames.length === 0) {
492
+ return undefined;
493
+ }
494
+ return JSON.stringify(frames.length === 1 ? frames[0] : { frames });
495
+ }
496
+ function parseCaretRectJson(raw) {
497
+ if (!raw) {
498
+ return null;
499
+ }
500
+ try {
501
+ const parsed = JSON.parse(raw);
502
+ const x = typeof parsed.x === 'number' ? parsed.x : null;
503
+ const y = typeof parsed.y === 'number' ? parsed.y : null;
504
+ const width = typeof parsed.width === 'number' ? parsed.width : null;
505
+ const height = typeof parsed.height === 'number' ? parsed.height : null;
506
+ const editorWidth = typeof parsed.editorWidth === 'number' ? parsed.editorWidth : null;
507
+ const editorHeight = typeof parsed.editorHeight === 'number' ? parsed.editorHeight : null;
508
+ if (x == null ||
509
+ y == null ||
510
+ width == null ||
511
+ height == null ||
512
+ editorWidth == null ||
513
+ editorHeight == null) {
514
+ return null;
515
+ }
516
+ return { x, y, width, height, editorWidth, editorHeight };
517
+ }
518
+ catch {
519
+ return null;
520
+ }
521
+ }
522
+ const serializedJsonCache = new WeakMap();
523
+ function stringifyCachedJson(value) {
524
+ if (value != null && typeof value === 'object') {
525
+ const cached = serializedJsonCache.get(value);
526
+ if (cached != null) {
527
+ return cached;
528
+ }
529
+ const serialized = JSON.stringify(value);
530
+ serializedJsonCache.set(value, serialized);
531
+ return serialized;
532
+ }
533
+ return JSON.stringify(value);
534
+ }
535
+ function useSerializedValue(value, serialize, revision) {
536
+ const cacheRef = (0, react_1.useRef)(null);
537
+ const hasRevision = revision !== undefined;
538
+ const cached = cacheRef.current;
539
+ if (cached) {
540
+ if (hasRevision && cached.hasRevision && Object.is(cached.revision, revision)) {
541
+ return cached.serialized;
542
+ }
543
+ if (Object.is(cached.value, value) && cached.hasRevision === hasRevision) {
544
+ return cached.serialized;
545
+ }
546
+ }
547
+ const serialized = value == null ? undefined : serialize(value);
548
+ cacheRef.current = {
549
+ value,
550
+ revision,
551
+ hasRevision,
552
+ serialized,
553
+ };
554
+ return serialized;
555
+ }
556
+ function doesLiveMentionQueryConflictWithNativeSelectRequest(request, currentQuery, requestDocumentVersion, currentDocumentVersion) {
557
+ if (currentQuery == null)
558
+ return false;
559
+ const currentQueryDocumentVersion = typeof currentQuery.documentVersion === 'number' ? currentQuery.documentVersion : null;
560
+ const isSameDocument = currentQueryDocumentVersion != null
561
+ ? currentQueryDocumentVersion === requestDocumentVersion
562
+ : requestDocumentVersion == null || requestDocumentVersion === currentDocumentVersion;
563
+ if (!isSameDocument)
564
+ return false;
565
+ return (currentQuery.trigger !== request.trigger ||
566
+ currentQuery.range.anchor !== request.range.anchor ||
567
+ currentQuery.range.head !== request.range.head);
568
+ }
569
+ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEditor({ initialContent, initialJSON, value, valueJSON, valueJSONRevision, valueJSONUpdateMode = 'replace', preserveSelectionOnValueJSONReset = false, selectionOnValueJSONReset, schema, placeholder, editable = true, maxLength, autoFocus = false, autoCapitalize, autoCorrect, keyboardType, keyboardAppearance, heightBehavior = 'autoGrow', showToolbar = true, toolbarPlacement = 'keyboard', toolbarItems = EditorToolbar_1.DEFAULT_EDITOR_TOOLBAR_ITEMS, onToolbarAction, onRequestLink, onRequestImage, autoDetectLinks = false, onContentChange, onContentChangeJSON, onSelectionChange, onActiveStateChange, onHistoryStateChange, onFocus, onBlur, style, containerStyle, theme, addons, remoteSelections, allowBase64Images = false, allowImageResizing = true, }, ref) {
570
+ const bridgeRef = (0, react_1.useRef)(null);
571
+ const nativeViewRef = (0, react_1.useRef)(null);
572
+ const [isReady, setIsReady] = (0, react_1.useState)(false);
573
+ const [editorInstanceId, setEditorInstanceId] = (0, react_1.useState)(0);
574
+ const editorInstanceIdRef = (0, react_1.useRef)(0);
575
+ editorInstanceIdRef.current = editorInstanceId;
576
+ const [isFocused, setIsFocused] = (0, react_1.useState)(false);
577
+ const isFocusedRef = (0, react_1.useRef)(false);
578
+ const [inlineToolbarFrame, setInlineToolbarFrame] = (0, react_1.useState)(null);
579
+ const registeredToolbarFrames = (0, EditorToolbar_1.useEditorToolbarFrames)(editorInstanceId);
580
+ const [pendingNativeUpdate, setPendingNativeUpdate] = (0, react_1.useState)({
581
+ json: undefined,
582
+ editorId: undefined,
583
+ revision: 0,
584
+ });
585
+ const [pendingNativeResetUpdate, setPendingNativeResetUpdate] = (0, react_1.useState)({
586
+ json: undefined,
587
+ editorId: undefined,
588
+ revision: 0,
589
+ });
590
+ const pendingNativeUpdateInFlightRef = (0, react_1.useRef)(null);
591
+ const pendingNativeResetUpdateInFlightRef = (0, react_1.useRef)(null);
592
+ const nativeUpdateRevisionRef = (0, react_1.useRef)(0);
593
+ const nextNativeUpdateRevision = (0, react_1.useCallback)(() => {
594
+ const revision = nativeUpdateRevisionRef.current + 1;
595
+ nativeUpdateRevisionRef.current = revision;
596
+ return revision;
597
+ }, []);
598
+ const [blockedNativeCommandRetry, setBlockedNativeCommandRetry] = (0, react_1.useState)(0);
599
+ const [detachedControlledSyncRetry, setDetachedControlledSyncRetry] = (0, react_1.useState)(0);
600
+ const [controlledNativeUpdateRetry, setControlledNativeUpdateRetry] = (0, react_1.useState)(0);
601
+ const pendingDetachedControlledSyncRef = (0, react_1.useRef)(false);
602
+ const pendingControlledSyncAfterNativeUpdateRef = (0, react_1.useRef)(false);
603
+ const pendingBlockedNativeCommandRetryRef = (0, react_1.useRef)(false);
604
+ const pendingNativeCommandRetryRef = (0, react_1.useRef)(null);
605
+ const pendingBridgeRecreationContentRef = (0, react_1.useRef)(null);
606
+ const blockedNativeCommandRetryTimerRef = (0, react_1.useRef)(null);
607
+ const [autoGrowHeight, setAutoGrowHeight] = (0, react_1.useState)(null);
608
+ // Toolbar state from EditorUpdate events
609
+ const [activeState, setActiveState] = (0, react_1.useState)({
610
+ marks: {},
611
+ markAttrs: {},
612
+ nodes: {},
613
+ commands: {},
614
+ allowedMarks: [],
615
+ insertableNodes: [],
616
+ });
617
+ const activeStateRef = (0, react_1.useRef)(activeState);
618
+ activeStateRef.current = activeState;
619
+ const [historyState, setHistoryState] = (0, react_1.useState)({
620
+ canUndo: false,
621
+ canRedo: false,
622
+ });
623
+ const [mentionQueryEvent, setMentionQueryEvent] = (0, react_1.useState)(null);
624
+ // Selection and rendered text length refs (non-rendering state)
625
+ const selectionRef = (0, react_1.useRef)({ type: 'text', anchor: 0, head: 0 });
626
+ const renderedTextLengthRef = (0, react_1.useRef)(0);
627
+ const documentVersionRef = (0, react_1.useRef)(null);
628
+ const controlledHtmlSyncRef = (0, react_1.useRef)({
629
+ value: undefined,
630
+ documentVersion: null,
631
+ });
632
+ const controlledJsonSyncRef = (0, react_1.useRef)({
633
+ value: undefined,
634
+ documentVersion: null,
635
+ });
636
+ const toolbarRef = (0, react_1.useRef)(null);
637
+ const mentionQueryEventRef = (0, react_1.useRef)(null);
638
+ const mentionQueryEditorIdRef = (0, react_1.useRef)(null);
639
+ mentionQueryEventRef.current = mentionQueryEvent;
640
+ const setMentionQueryEventState = (0, react_1.useCallback)((nextEvent, editorId) => {
641
+ mentionQueryEventRef.current = nextEvent;
642
+ mentionQueryEditorIdRef.current =
643
+ nextEvent == null
644
+ ? null
645
+ : typeof editorId === 'number'
646
+ ? editorId
647
+ : (bridgeRef.current?.editorId ?? null);
648
+ setMentionQueryEvent(nextEvent);
649
+ }, []);
650
+ const toolbarItemsSerializationCacheRef = (0, react_1.useRef)(null);
651
+ // Stable callback refs to avoid re-renders
652
+ const onContentChangeRef = (0, react_1.useRef)(onContentChange);
653
+ onContentChangeRef.current = onContentChange;
654
+ const onContentChangeJSONRef = (0, react_1.useRef)(onContentChangeJSON);
655
+ onContentChangeJSONRef.current = onContentChangeJSON;
656
+ const onSelectionChangeRef = (0, react_1.useRef)(onSelectionChange);
657
+ onSelectionChangeRef.current = onSelectionChange;
658
+ const onActiveStateChangeRef = (0, react_1.useRef)(onActiveStateChange);
659
+ onActiveStateChangeRef.current = onActiveStateChange;
660
+ const onHistoryStateChangeRef = (0, react_1.useRef)(onHistoryStateChange);
661
+ onHistoryStateChangeRef.current = onHistoryStateChange;
662
+ const onFocusRef = (0, react_1.useRef)(onFocus);
663
+ onFocusRef.current = onFocus;
664
+ const onBlurRef = (0, react_1.useRef)(onBlur);
665
+ onBlurRef.current = onBlur;
666
+ const addonsRef = (0, react_1.useRef)(addons);
667
+ addonsRef.current = addons;
668
+ const currentLinkHref = typeof activeState.markAttrs?.link?.href === 'string'
669
+ ? activeState.markAttrs.link.href
670
+ : undefined;
671
+ const mentionSuggestionsByKeyRef = (0, react_1.useRef)(new Map());
672
+ mentionSuggestionsByKeyRef.current = new Map((addons?.mentions?.suggestions ?? []).map((suggestion) => [suggestion.key, suggestion]));
673
+ const bridgeSchema = addons?.mentions != null ? (0, addons_1.withMentionsSchema)(schema ?? schemas_1.tiptapSchema) : schema;
674
+ const documentSchema = bridgeSchema ?? schemas_1.tiptapSchema;
675
+ const serializedSchemaJson = useSerializedValue(bridgeSchema, (nextSchema) => stringifyCachedJson(nextSchema));
676
+ const serializedInitialJson = useSerializedValue(initialJSON, (doc) => stringifyCachedJson((0, schemas_1.normalizeDocumentJson)(doc, documentSchema)));
677
+ const serializedValueJson = useSerializedValue(valueJSON, (doc) => stringifyCachedJson((0, schemas_1.normalizeDocumentJson)(doc, documentSchema)), valueJSONRevision);
678
+ const themeJson = useSerializedValue(theme, EditorTheme_1.serializeEditorTheme);
679
+ const addonsJson = useSerializedValue(addons, addons_1.serializeEditorAddons);
680
+ const remoteSelectionsJson = useSerializedValue(remoteSelections, (selections) => serializeRemoteSelections(selections));
681
+ const clearBlockedNativeCommandRetryTimer = (0, react_1.useCallback)(() => {
682
+ const timer = blockedNativeCommandRetryTimerRef.current;
683
+ if (timer == null)
684
+ return;
685
+ clearTimeout(timer);
686
+ blockedNativeCommandRetryTimerRef.current = null;
687
+ }, []);
688
+ const flushBlockedNativeCommandRetry = (0, react_1.useCallback)(() => {
689
+ const hadPendingRetry = pendingBlockedNativeCommandRetryRef.current ||
690
+ blockedNativeCommandRetryTimerRef.current != null;
691
+ if (!hadPendingRetry)
692
+ return;
693
+ pendingBlockedNativeCommandRetryRef.current = false;
694
+ clearBlockedNativeCommandRetryTimer();
695
+ setBlockedNativeCommandRetry((revision) => revision + 1);
696
+ }, [clearBlockedNativeCommandRetryTimer]);
697
+ const scheduleBlockedNativeCommandRetry = (0, react_1.useCallback)(() => {
698
+ pendingBlockedNativeCommandRetryRef.current = true;
699
+ if (blockedNativeCommandRetryTimerRef.current != null)
700
+ return;
701
+ blockedNativeCommandRetryTimerRef.current = setTimeout(() => {
702
+ blockedNativeCommandRetryTimerRef.current = null;
703
+ if (!pendingBlockedNativeCommandRetryRef.current)
704
+ return;
705
+ pendingBlockedNativeCommandRetryRef.current = false;
706
+ setBlockedNativeCommandRetry((revision) => revision + 1);
707
+ }, 50);
708
+ }, []);
709
+ const enqueueBlockedNativeCommandRetry = (0, react_1.useCallback)((retry) => {
710
+ pendingNativeCommandRetryRef.current = retry;
711
+ scheduleBlockedNativeCommandRetry();
712
+ }, [scheduleBlockedNativeCommandRetry]);
713
+ const clearStaleMentionQueryForDocumentVersion = (0, react_1.useCallback)((documentVersion) => {
714
+ const currentMentionQuery = mentionQueryEventRef.current;
715
+ if (currentMentionQuery != null &&
716
+ (typeof currentMentionQuery.documentVersion !== 'number' ||
717
+ currentMentionQuery.documentVersion < documentVersion)) {
718
+ setMentionQueryEventState(null);
719
+ }
720
+ }, [setMentionQueryEventState]);
721
+ const syncStateFromUpdate = (0, react_1.useCallback)((update) => {
722
+ if (!update)
723
+ return;
724
+ activeStateRef.current = update.activeState;
725
+ setActiveState(update.activeState);
726
+ setHistoryState(update.historyState);
727
+ selectionRef.current = update.selection;
728
+ renderedTextLengthRef.current = computeRenderedTextLength(update.renderElements);
729
+ if (typeof update.documentVersion === 'number') {
730
+ const previousDocumentVersion = documentVersionRef.current;
731
+ documentVersionRef.current = update.documentVersion;
732
+ if (previousDocumentVersion == null ||
733
+ update.documentVersion > previousDocumentVersion) {
734
+ clearStaleMentionQueryForDocumentVersion(update.documentVersion);
735
+ }
736
+ }
737
+ flushBlockedNativeCommandRetry();
738
+ }, [clearStaleMentionQueryForDocumentVersion, flushBlockedNativeCommandRetry]);
739
+ const syncSelectionStateFromUpdate = (0, react_1.useCallback)((update) => {
740
+ if (!update)
741
+ return;
742
+ activeStateRef.current = update.activeState;
743
+ setActiveState(update.activeState);
744
+ setHistoryState(update.historyState);
745
+ selectionRef.current = update.selection;
746
+ if (typeof update.documentVersion === 'number') {
747
+ const previousDocumentVersion = documentVersionRef.current;
748
+ documentVersionRef.current = update.documentVersion;
749
+ if (previousDocumentVersion == null ||
750
+ update.documentVersion > previousDocumentVersion) {
751
+ clearStaleMentionQueryForDocumentVersion(update.documentVersion);
752
+ }
753
+ }
754
+ flushBlockedNativeCommandRetry();
755
+ }, [clearStaleMentionQueryForDocumentVersion, flushBlockedNativeCommandRetry]);
756
+ const emitContentCallbacksForUpdate = (0, react_1.useCallback)((update, previousDocumentVersion) => {
757
+ if (!update || !bridgeRef.current || bridgeRef.current.isDestroyed)
758
+ return;
759
+ const wantsHtml = typeof onContentChangeRef.current === 'function';
760
+ const wantsJson = typeof onContentChangeJSONRef.current === 'function';
761
+ if (!wantsHtml && !wantsJson)
762
+ return;
763
+ if (previousDocumentVersion != null &&
764
+ typeof update.documentVersion === 'number' &&
765
+ update.documentVersion === previousDocumentVersion) {
766
+ return;
767
+ }
768
+ if (wantsHtml && wantsJson) {
769
+ const snapshot = bridgeRef.current.getContentSnapshot();
770
+ onContentChangeRef.current?.(snapshot.html);
771
+ onContentChangeJSONRef.current?.(snapshot.json);
772
+ return;
773
+ }
774
+ if (wantsHtml) {
775
+ onContentChangeRef.current?.(bridgeRef.current.getHtml());
776
+ }
777
+ if (wantsJson) {
778
+ onContentChangeJSONRef.current?.(bridgeRef.current.getJson());
779
+ }
780
+ }, []);
781
+ const clearPendingNativeUpdateForCurrentEditor = (0, react_1.useCallback)(() => {
782
+ if (react_native_1.Platform.OS !== 'android')
783
+ return;
784
+ const editorId = bridgeRef.current?.editorId;
785
+ if (typeof editorId !== 'number')
786
+ return;
787
+ const inFlight = pendingNativeUpdateInFlightRef.current;
788
+ if (inFlight != null && inFlight.editorId === editorId) {
789
+ pendingNativeUpdateInFlightRef.current = null;
790
+ }
791
+ const next = {
792
+ json: undefined,
793
+ editorId,
794
+ revision: nextNativeUpdateRevision(),
795
+ };
796
+ setPendingNativeUpdate(next);
797
+ }, [nextNativeUpdateRevision]);
798
+ const queuePendingNativeResetUpdate = (0, react_1.useCallback)((updateJson) => {
799
+ if (react_native_1.Platform.OS !== 'android')
800
+ return;
801
+ const editorId = bridgeRef.current?.editorId;
802
+ const revision = nextNativeUpdateRevision();
803
+ const next = {
804
+ json: updateJson,
805
+ editorId,
806
+ revision,
807
+ };
808
+ pendingNativeResetUpdateInFlightRef.current = { editorId, revision };
809
+ setPendingNativeResetUpdate(next);
810
+ }, [nextNativeUpdateRevision]);
811
+ const consumeBlockedCommandInfoForRetry = (0, react_1.useCallback)((bridge) => {
812
+ const blockedInfo = bridge.consumeLastCommandBlockedInfo();
813
+ if (!blockedInfo.blocked)
814
+ return blockedInfo;
815
+ if (blockedInfo.reason === 'detached') {
816
+ pendingDetachedControlledSyncRef.current = true;
817
+ }
818
+ else if (blockedInfo.reason === 'destroyed') {
819
+ pendingDetachedControlledSyncRef.current = false;
820
+ pendingBlockedNativeCommandRetryRef.current = false;
821
+ pendingNativeCommandRetryRef.current = null;
822
+ clearBlockedNativeCommandRetryTimer();
823
+ clearPendingNativeUpdateForCurrentEditor();
824
+ }
825
+ else {
826
+ scheduleBlockedNativeCommandRetry();
827
+ }
828
+ return blockedInfo;
829
+ }, [
830
+ clearBlockedNativeCommandRetryTimer,
831
+ clearPendingNativeUpdateForCurrentEditor,
832
+ scheduleBlockedNativeCommandRetry,
833
+ ]);
834
+ const consumeBlockedCommandForRetry = (0, react_1.useCallback)((bridge) => consumeBlockedCommandInfoForRetry(bridge).blocked, [consumeBlockedCommandInfoForRetry]);
835
+ (0, react_1.useEffect)(() => {
836
+ const retry = pendingNativeCommandRetryRef.current;
837
+ if (retry == null)
838
+ return;
839
+ pendingNativeCommandRetryRef.current = null;
840
+ retry();
841
+ }, [blockedNativeCommandRetry]);
842
+ const hasPendingNativeUpdateInFlightForCurrentEditor = (0, react_1.useCallback)(() => {
843
+ if (react_native_1.Platform.OS !== 'android')
844
+ return false;
845
+ const editorId = bridgeRef.current?.editorId;
846
+ if (typeof editorId !== 'number')
847
+ return false;
848
+ const inFlight = pendingNativeUpdateInFlightRef.current;
849
+ return inFlight != null && inFlight.editorId === editorId;
850
+ }, []);
851
+ const applyUpdateToNativeView = (0, react_1.useCallback)((update, previousDocumentVersion, skipNativeApplyIfContentUnchanged = false) => {
852
+ const contentChanged = didContentChange(previousDocumentVersion, update);
853
+ const handleApplyResult = (result) => {
854
+ if (result === false) {
855
+ setBlockedNativeCommandRetry((revision) => revision + 1);
856
+ return false;
857
+ }
858
+ return true;
859
+ };
860
+ if (!skipNativeApplyIfContentUnchanged || contentChanged) {
861
+ const updateJson = JSON.stringify(update);
862
+ if (react_native_1.Platform.OS === 'android') {
863
+ const editorId = bridgeRef.current?.editorId;
864
+ const revision = nextNativeUpdateRevision();
865
+ const next = {
866
+ json: updateJson,
867
+ editorId,
868
+ revision,
869
+ };
870
+ pendingNativeUpdateInFlightRef.current = { editorId, revision };
871
+ setPendingNativeUpdate(next);
872
+ }
873
+ else {
874
+ try {
875
+ const applyResult = nativeViewRef.current?.applyEditorUpdate(updateJson);
876
+ if (isPromiseLike(applyResult)) {
877
+ void applyResult
878
+ .then((result) => {
879
+ handleApplyResult(result);
880
+ })
881
+ .catch(() => {
882
+ // The native view may already be torn down during navigation.
883
+ });
884
+ }
885
+ else {
886
+ return handleApplyResult(applyResult);
887
+ }
888
+ }
889
+ catch {
890
+ // The native view may already be torn down during navigation.
891
+ }
892
+ }
893
+ }
894
+ return true;
895
+ }, [nextNativeUpdateRevision]);
896
+ const applyResetUpdateToNativeView = (0, react_1.useCallback)((update, previousDocumentVersion) => {
897
+ const updateJson = JSON.stringify(update);
898
+ if (react_native_1.Platform.OS === 'android') {
899
+ clearPendingNativeUpdateForCurrentEditor();
900
+ queuePendingNativeResetUpdate(updateJson);
901
+ const applyResetUpdate = nativeViewRef.current?.applyEditorResetUpdate;
902
+ if (applyResetUpdate) {
903
+ try {
904
+ const applyResult = applyResetUpdate(updateJson);
905
+ if (isPromiseLike(applyResult)) {
906
+ void applyResult.catch(() => {
907
+ // The native view may already be torn down during navigation.
908
+ });
909
+ }
910
+ return;
911
+ }
912
+ catch {
913
+ // Fall through to the regular prop-based apply path.
914
+ }
915
+ }
916
+ return;
917
+ }
918
+ applyUpdateToNativeView(update, previousDocumentVersion);
919
+ }, [
920
+ applyUpdateToNativeView,
921
+ clearPendingNativeUpdateForCurrentEditor,
922
+ queuePendingNativeResetUpdate,
923
+ ]);
924
+ const maybeApplyAutoDetectedLink = (0, react_1.useCallback)((update, previousDocumentVersion) => {
925
+ const applyAutoLink = (candidateUpdate, allowPreflightRetry) => {
926
+ if (!autoDetectLinks ||
927
+ !candidateUpdate ||
928
+ !didContentChange(previousDocumentVersion, candidateUpdate) ||
929
+ !bridgeRef.current ||
930
+ bridgeRef.current.isDestroyed ||
931
+ !candidateUpdate.activeState.allowedMarks.includes('link') ||
932
+ candidateUpdate.selection.type !== 'text' ||
933
+ candidateUpdate.selection.anchor == null ||
934
+ candidateUpdate.selection.head == null ||
935
+ candidateUpdate.selection.anchor !== candidateUpdate.selection.head) {
936
+ return candidateUpdate;
937
+ }
938
+ const cursorDocPos = candidateUpdate.selection.head;
939
+ const candidate = findAutoLinkCandidateInDocument(bridgeRef.current.getJson(), cursorDocPos);
940
+ if (!candidate) {
941
+ return candidateUpdate;
942
+ }
943
+ const scalarFrom = bridgeRef.current.docToScalar(candidate.docFrom);
944
+ const scalarTo = bridgeRef.current.docToScalar(candidate.docTo);
945
+ if (!(scalarTo > scalarFrom)) {
946
+ return candidateUpdate;
947
+ }
948
+ const autoLinkUpdate = bridgeRef.current.setMarkAtSelectionScalar(scalarFrom, scalarTo, 'link', { href: candidate.href });
949
+ if (!autoLinkUpdate) {
950
+ const preflightUpdate = bridgeRef.current.consumeLastCommandPreflightUpdate();
951
+ if (preflightUpdate) {
952
+ return allowPreflightRetry
953
+ ? applyAutoLink(preflightUpdate, false)
954
+ : preflightUpdate;
955
+ }
956
+ consumeBlockedCommandForRetry(bridgeRef.current);
957
+ return candidateUpdate;
958
+ }
959
+ bridgeRef.current.setSelection(candidateUpdate.selection.anchor, candidateUpdate.selection.head);
960
+ const selectionState = bridgeRef.current.getSelectionState();
961
+ if (selectionState) {
962
+ autoLinkUpdate.selection = selectionState.selection;
963
+ autoLinkUpdate.activeState = selectionState.activeState;
964
+ autoLinkUpdate.historyState = selectionState.historyState;
965
+ if (typeof selectionState.documentVersion === 'number') {
966
+ autoLinkUpdate.documentVersion = selectionState.documentVersion;
967
+ }
968
+ }
969
+ else {
970
+ autoLinkUpdate.selection = candidateUpdate.selection;
971
+ }
972
+ return autoLinkUpdate;
973
+ };
974
+ return applyAutoLink(update, true);
975
+ }, [autoDetectLinks, consumeBlockedCommandForRetry]);
976
+ const syncNativeUpdateFromBridge = (0, react_1.useCallback)((nativeUpdate, previousDocumentVersion, options) => {
977
+ const update = maybeApplyAutoDetectedLink(nativeUpdate, previousDocumentVersion);
978
+ if (!update)
979
+ return null;
980
+ if (update !== nativeUpdate) {
981
+ applyUpdateToNativeView(update, previousDocumentVersion, options?.skipNativeApplyIfContentUnchanged);
982
+ }
983
+ syncStateFromUpdate(update);
984
+ onActiveStateChangeRef.current?.(update.activeState);
985
+ onHistoryStateChangeRef.current?.(update.historyState);
986
+ onSelectionChangeRef.current?.(update.selection);
987
+ if (!options?.suppressContentCallbacks) {
988
+ emitContentCallbacksForUpdate(update, previousDocumentVersion);
989
+ }
990
+ return update;
991
+ }, [
992
+ applyUpdateToNativeView,
993
+ emitContentCallbacksForUpdate,
994
+ maybeApplyAutoDetectedLink,
995
+ syncStateFromUpdate,
996
+ ]);
997
+ // Warn if both value and valueJSON are set
998
+ if (__DEV__ && value != null && valueJSON != null) {
999
+ console.warn('NativeRichTextEditor: value and valueJSON are mutually exclusive. ' +
1000
+ 'Only value will be used.');
1001
+ }
1002
+ const runAndApply = (0, react_1.useCallback)((mutate, options) => {
1003
+ if (hasPendingNativeUpdateInFlightForCurrentEditor()) {
1004
+ if (options?.retryBlockedCommand != null) {
1005
+ enqueueBlockedNativeCommandRetry(options.retryBlockedCommand);
1006
+ options.onBlockedCommandRetryQueued?.();
1007
+ }
1008
+ return null;
1009
+ }
1010
+ const previousDocumentVersion = documentVersionRef.current;
1011
+ const preservedSelection = options?.preserveLiveTextSelection === true ? selectionRef.current : null;
1012
+ let update = mutate();
1013
+ if (!update) {
1014
+ const bridge = bridgeRef.current;
1015
+ if (bridge != null && !bridge.isDestroyed) {
1016
+ const preflightUpdate = bridge.consumeLastCommandPreflightUpdate();
1017
+ if (preflightUpdate) {
1018
+ syncNativeUpdateFromBridge(preflightUpdate, previousDocumentVersion);
1019
+ }
1020
+ const blockedInfo = consumeBlockedCommandInfoForRetry(bridge);
1021
+ if (options?.retryBlockedCommand != null &&
1022
+ isRetryableNativeCommandBlock(blockedInfo.reason)) {
1023
+ enqueueBlockedNativeCommandRetry(options.retryBlockedCommand);
1024
+ options.onBlockedCommandRetryQueued?.();
1025
+ }
1026
+ }
1027
+ return null;
1028
+ }
1029
+ if (!options?.skipAutoDetectLinks) {
1030
+ update = maybeApplyAutoDetectedLink(update, previousDocumentVersion);
1031
+ if (!update) {
1032
+ return null;
1033
+ }
1034
+ }
1035
+ if (preservedSelection?.type === 'text' &&
1036
+ typeof preservedSelection.anchor === 'number' &&
1037
+ typeof preservedSelection.head === 'number' &&
1038
+ !didContentChange(previousDocumentVersion, update) &&
1039
+ bridgeRef.current != null &&
1040
+ !bridgeRef.current.isDestroyed) {
1041
+ bridgeRef.current.setSelection(preservedSelection.anchor, preservedSelection.head);
1042
+ update.selection = {
1043
+ type: 'text',
1044
+ anchor: preservedSelection.anchor,
1045
+ head: preservedSelection.head,
1046
+ };
1047
+ }
1048
+ applyUpdateToNativeView(update, previousDocumentVersion, options?.skipNativeApplyIfContentUnchanged);
1049
+ syncStateFromUpdate(update);
1050
+ onActiveStateChangeRef.current?.(update.activeState);
1051
+ onHistoryStateChangeRef.current?.(update.historyState);
1052
+ onSelectionChangeRef.current?.(update.selection);
1053
+ if (!options?.suppressContentCallbacks) {
1054
+ emitContentCallbacksForUpdate(update, previousDocumentVersion);
1055
+ }
1056
+ return update;
1057
+ }, [
1058
+ applyUpdateToNativeView,
1059
+ consumeBlockedCommandInfoForRetry,
1060
+ enqueueBlockedNativeCommandRetry,
1061
+ emitContentCallbacksForUpdate,
1062
+ hasPendingNativeUpdateInFlightForCurrentEditor,
1063
+ maybeApplyAutoDetectedLink,
1064
+ syncNativeUpdateFromBridge,
1065
+ syncStateFromUpdate,
1066
+ ]);
1067
+ const prepareBridgeForExternalContentRead = (0, react_1.useCallback)((options) => {
1068
+ const bridge = bridgeRef.current;
1069
+ if (!bridge || bridge.isDestroyed)
1070
+ return false;
1071
+ const previousDocumentVersion = documentVersionRef.current;
1072
+ const ready = bridge.prepareForNativeCommand();
1073
+ const preflightUpdate = bridge.consumeLastCommandPreflightUpdate();
1074
+ if (preflightUpdate) {
1075
+ syncNativeUpdateFromBridge(preflightUpdate, previousDocumentVersion);
1076
+ }
1077
+ if (!ready) {
1078
+ consumeBlockedCommandForRetry(bridge);
1079
+ return false;
1080
+ }
1081
+ return (options?.skipWhenContentChanged !== true ||
1082
+ !didContentChange(previousDocumentVersion, preflightUpdate));
1083
+ }, [consumeBlockedCommandForRetry, syncNativeUpdateFromBridge]);
1084
+ const resetContentJsonString = (0, react_1.useCallback)((jsonString, options) => {
1085
+ const bridge = bridgeRef.current;
1086
+ if (!bridge || bridge.isDestroyed)
1087
+ return null;
1088
+ const previousDocumentVersion = documentVersionRef.current;
1089
+ const preservedSelection = options?.preserveLiveTextSelection === true
1090
+ ? (options.selection ?? selectionRef.current)
1091
+ : null;
1092
+ bridge.setJsonString(jsonString);
1093
+ const update = bridge.getCurrentState();
1094
+ if (!update)
1095
+ return null;
1096
+ if (preservedSelection != null &&
1097
+ restoreSelectionInBridge(bridge, preservedSelection)) {
1098
+ const selectionState = bridge.getSelectionState();
1099
+ if (selectionState) {
1100
+ update.selection = selectionState.selection;
1101
+ update.activeState = selectionState.activeState;
1102
+ update.historyState = selectionState.historyState;
1103
+ if (typeof selectionState.documentVersion === 'number') {
1104
+ update.documentVersion = selectionState.documentVersion;
1105
+ }
1106
+ }
1107
+ }
1108
+ applyResetUpdateToNativeView(update, previousDocumentVersion);
1109
+ syncStateFromUpdate(update);
1110
+ onActiveStateChangeRef.current?.(update.activeState);
1111
+ onHistoryStateChangeRef.current?.(update.historyState);
1112
+ onSelectionChangeRef.current?.(update.selection);
1113
+ if (!options?.suppressContentCallbacks) {
1114
+ emitContentCallbacksForUpdate(update, previousDocumentVersion);
1115
+ }
1116
+ return update;
1117
+ }, [applyResetUpdateToNativeView, emitContentCallbacksForUpdate, syncStateFromUpdate]);
1118
+ const syncPreflightUpdateFromNativeEvent = (0, react_1.useCallback)((updateJson) => {
1119
+ if (typeof updateJson !== 'string' || updateJson.length === 0) {
1120
+ return true;
1121
+ }
1122
+ const bridge = bridgeRef.current;
1123
+ if (!bridge || bridge.isDestroyed)
1124
+ return false;
1125
+ const previousDocumentVersion = documentVersionRef.current;
1126
+ try {
1127
+ const parsed = JSON.parse(updateJson);
1128
+ if (react_native_1.Platform.OS === 'android' &&
1129
+ typeof previousDocumentVersion === 'number' &&
1130
+ typeof parsed.documentVersion !== 'number') {
1131
+ return false;
1132
+ }
1133
+ const update = bridge.parseUpdateJson(updateJson);
1134
+ if (!update)
1135
+ return false;
1136
+ syncNativeUpdateFromBridge(update, previousDocumentVersion);
1137
+ return true;
1138
+ }
1139
+ catch {
1140
+ return false;
1141
+ }
1142
+ }, [syncNativeUpdateFromBridge]);
1143
+ (0, react_1.useEffect)(() => {
1144
+ const preservedUncontrolledContent = value == null && serializedValueJson == null
1145
+ ? pendingBridgeRecreationContentRef.current
1146
+ : null;
1147
+ pendingBridgeRecreationContentRef.current = null;
1148
+ const bridgeConfig = maxLength != null || serializedSchemaJson || allowBase64Images
1149
+ ? {
1150
+ maxLength,
1151
+ schemaJson: serializedSchemaJson,
1152
+ allowBase64Images,
1153
+ }
1154
+ : undefined;
1155
+ const bridge = NativeEditorBridge_1.NativeEditorBridge.create(bridgeConfig);
1156
+ bridgeRef.current = bridge;
1157
+ setEditorInstanceId(bridge.editorId);
1158
+ // Four-way content initialization: value > valueJSON > initialJSON > initialContent
1159
+ if (value != null) {
1160
+ bridge.setHtml(value);
1161
+ }
1162
+ else if (serializedValueJson != null) {
1163
+ bridge.setJsonString(serializedValueJson);
1164
+ }
1165
+ else if (preservedUncontrolledContent != null) {
1166
+ bridge.setJsonString(preservedUncontrolledContent.jsonString);
1167
+ }
1168
+ else if (serializedInitialJson != null) {
1169
+ bridge.setJsonString(serializedInitialJson);
1170
+ }
1171
+ else if (initialContent) {
1172
+ bridge.setHtml(initialContent);
1173
+ }
1174
+ if (preservedUncontrolledContent != null) {
1175
+ const preservedSelection = preservedUncontrolledContent.selection;
1176
+ if (preservedSelection.type === 'text') {
1177
+ const anchor = preservedSelection.anchor ?? 0;
1178
+ const head = preservedSelection.head ?? anchor;
1179
+ bridge.setSelection(anchor, head);
1180
+ }
1181
+ else if (preservedSelection.type === 'node' &&
1182
+ typeof preservedSelection.pos === 'number') {
1183
+ bridge.setSelection(preservedSelection.pos, preservedSelection.pos);
1184
+ }
1185
+ }
1186
+ syncStateFromUpdate(bridge.getCurrentState());
1187
+ setIsReady(true);
1188
+ return () => {
1189
+ if (bridgeRef.current === bridge &&
1190
+ value == null &&
1191
+ serializedValueJson == null &&
1192
+ !bridge.isDestroyed) {
1193
+ try {
1194
+ pendingBridgeRecreationContentRef.current = {
1195
+ jsonString: bridge.getJsonString(),
1196
+ selection: selectionRef.current,
1197
+ };
1198
+ }
1199
+ catch {
1200
+ pendingBridgeRecreationContentRef.current = null;
1201
+ }
1202
+ }
1203
+ bridge.destroy();
1204
+ if (bridgeRef.current === bridge) {
1205
+ bridgeRef.current = null;
1206
+ }
1207
+ pendingNativeUpdateInFlightRef.current = null;
1208
+ pendingNativeResetUpdateInFlightRef.current = null;
1209
+ pendingDetachedControlledSyncRef.current = false;
1210
+ pendingControlledSyncAfterNativeUpdateRef.current = false;
1211
+ pendingBlockedNativeCommandRetryRef.current = false;
1212
+ pendingNativeCommandRetryRef.current = null;
1213
+ documentVersionRef.current = null;
1214
+ mentionQueryEventRef.current = null;
1215
+ mentionQueryEditorIdRef.current = null;
1216
+ clearBlockedNativeCommandRetryTimer();
1217
+ setMentionQueryEvent(null);
1218
+ const clearedNativeUpdate = {
1219
+ json: undefined,
1220
+ editorId: undefined,
1221
+ revision: nextNativeUpdateRevision(),
1222
+ };
1223
+ setPendingNativeUpdate(clearedNativeUpdate);
1224
+ const clearedNativeResetUpdate = {
1225
+ json: undefined,
1226
+ editorId: undefined,
1227
+ revision: nextNativeUpdateRevision(),
1228
+ };
1229
+ setPendingNativeResetUpdate(clearedNativeResetUpdate);
1230
+ setEditorInstanceId(0);
1231
+ setIsReady(false);
1232
+ };
1233
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1234
+ }, [
1235
+ maxLength,
1236
+ syncStateFromUpdate,
1237
+ allowBase64Images,
1238
+ serializedSchemaJson,
1239
+ clearBlockedNativeCommandRetryTimer,
1240
+ nextNativeUpdateRevision,
1241
+ ]);
1242
+ (0, react_1.useEffect)(() => {
1243
+ if (value == null)
1244
+ return;
1245
+ if (!bridgeRef.current || bridgeRef.current.isDestroyed)
1246
+ return;
1247
+ const previousSync = controlledHtmlSyncRef.current;
1248
+ const didControlledValueChange = previousSync.value !== value;
1249
+ const didDocumentAdvanceForSameValue = !didControlledValueChange &&
1250
+ previousSync.documentVersion !== documentVersionRef.current;
1251
+ if (!prepareBridgeForExternalContentRead({
1252
+ skipWhenContentChanged: !didControlledValueChange,
1253
+ })) {
1254
+ controlledHtmlSyncRef.current = {
1255
+ value,
1256
+ documentVersion: documentVersionRef.current,
1257
+ };
1258
+ return;
1259
+ }
1260
+ if (didDocumentAdvanceForSameValue) {
1261
+ controlledHtmlSyncRef.current = {
1262
+ value,
1263
+ documentVersion: documentVersionRef.current,
1264
+ };
1265
+ return;
1266
+ }
1267
+ const currentHtml = bridgeRef.current.getHtml();
1268
+ if (currentHtml === value) {
1269
+ clearPendingNativeUpdateForCurrentEditor();
1270
+ controlledHtmlSyncRef.current = {
1271
+ value,
1272
+ documentVersion: documentVersionRef.current,
1273
+ };
1274
+ return;
1275
+ }
1276
+ if (hasPendingNativeUpdateInFlightForCurrentEditor()) {
1277
+ pendingControlledSyncAfterNativeUpdateRef.current = true;
1278
+ return;
1279
+ }
1280
+ const update = runAndApply(() => bridgeRef.current.replaceHtml(value), {
1281
+ suppressContentCallbacks: true,
1282
+ preserveLiveTextSelection: true,
1283
+ skipAutoDetectLinks: true,
1284
+ });
1285
+ if (!update && hasPendingNativeUpdateInFlightForCurrentEditor()) {
1286
+ pendingControlledSyncAfterNativeUpdateRef.current = true;
1287
+ return;
1288
+ }
1289
+ controlledHtmlSyncRef.current = {
1290
+ value,
1291
+ documentVersion: documentVersionRef.current,
1292
+ };
1293
+ }, [
1294
+ value,
1295
+ runAndApply,
1296
+ blockedNativeCommandRetry,
1297
+ controlledNativeUpdateRetry,
1298
+ detachedControlledSyncRetry,
1299
+ prepareBridgeForExternalContentRead,
1300
+ clearPendingNativeUpdateForCurrentEditor,
1301
+ hasPendingNativeUpdateInFlightForCurrentEditor,
1302
+ ]);
1303
+ (0, react_1.useEffect)(() => {
1304
+ if (serializedValueJson == null || value != null)
1305
+ return;
1306
+ if (!bridgeRef.current || bridgeRef.current.isDestroyed)
1307
+ return;
1308
+ const previousSync = controlledJsonSyncRef.current;
1309
+ const didControlledValueChange = previousSync.value !== serializedValueJson;
1310
+ const didDocumentAdvanceForSameValue = !didControlledValueChange &&
1311
+ previousSync.documentVersion !== documentVersionRef.current;
1312
+ if (valueJSONUpdateMode === 'reset') {
1313
+ const currentJson = bridgeRef.current.getJsonString();
1314
+ if (currentJson === serializedValueJson) {
1315
+ clearPendingNativeUpdateForCurrentEditor();
1316
+ controlledJsonSyncRef.current = {
1317
+ value: serializedValueJson,
1318
+ documentVersion: documentVersionRef.current,
1319
+ };
1320
+ return;
1321
+ }
1322
+ resetContentJsonString(serializedValueJson, {
1323
+ suppressContentCallbacks: true,
1324
+ preserveLiveTextSelection: preserveSelectionOnValueJSONReset,
1325
+ selection: selectionOnValueJSONReset,
1326
+ });
1327
+ controlledJsonSyncRef.current = {
1328
+ value: serializedValueJson,
1329
+ documentVersion: documentVersionRef.current,
1330
+ };
1331
+ return;
1332
+ }
1333
+ if (!prepareBridgeForExternalContentRead({
1334
+ skipWhenContentChanged: !didControlledValueChange,
1335
+ })) {
1336
+ controlledJsonSyncRef.current = {
1337
+ value: serializedValueJson,
1338
+ documentVersion: documentVersionRef.current,
1339
+ };
1340
+ return;
1341
+ }
1342
+ if (didDocumentAdvanceForSameValue) {
1343
+ controlledJsonSyncRef.current = {
1344
+ value: serializedValueJson,
1345
+ documentVersion: documentVersionRef.current,
1346
+ };
1347
+ return;
1348
+ }
1349
+ const currentJson = bridgeRef.current.getJsonString();
1350
+ if (currentJson === serializedValueJson) {
1351
+ clearPendingNativeUpdateForCurrentEditor();
1352
+ controlledJsonSyncRef.current = {
1353
+ value: serializedValueJson,
1354
+ documentVersion: documentVersionRef.current,
1355
+ };
1356
+ return;
1357
+ }
1358
+ if (hasPendingNativeUpdateInFlightForCurrentEditor()) {
1359
+ pendingControlledSyncAfterNativeUpdateRef.current = true;
1360
+ return;
1361
+ }
1362
+ const update = runAndApply(() => bridgeRef.current.replaceJsonString(serializedValueJson), {
1363
+ suppressContentCallbacks: true,
1364
+ preserveLiveTextSelection: true,
1365
+ skipAutoDetectLinks: true,
1366
+ });
1367
+ if (!update && hasPendingNativeUpdateInFlightForCurrentEditor()) {
1368
+ pendingControlledSyncAfterNativeUpdateRef.current = true;
1369
+ return;
1370
+ }
1371
+ controlledJsonSyncRef.current = {
1372
+ value: serializedValueJson,
1373
+ documentVersion: documentVersionRef.current,
1374
+ };
1375
+ }, [
1376
+ serializedValueJson,
1377
+ value,
1378
+ valueJSONUpdateMode,
1379
+ preserveSelectionOnValueJSONReset,
1380
+ selectionOnValueJSONReset,
1381
+ runAndApply,
1382
+ blockedNativeCommandRetry,
1383
+ controlledNativeUpdateRetry,
1384
+ detachedControlledSyncRetry,
1385
+ prepareBridgeForExternalContentRead,
1386
+ clearPendingNativeUpdateForCurrentEditor,
1387
+ hasPendingNativeUpdateInFlightForCurrentEditor,
1388
+ resetContentJsonString,
1389
+ ]);
1390
+ const updateToolbarFrame = (0, react_1.useCallback)(() => {
1391
+ const toolbar = toolbarRef.current;
1392
+ if (!toolbar) {
1393
+ setInlineToolbarFrame(null);
1394
+ return;
1395
+ }
1396
+ toolbar.measureInWindow((x, y, width, height) => {
1397
+ if (width <= 0 || height <= 0) {
1398
+ setInlineToolbarFrame(null);
1399
+ return;
1400
+ }
1401
+ const nextFrame = { x, y, width, height };
1402
+ setInlineToolbarFrame((prev) => areToolbarFramesEqual(prev, nextFrame) ? prev : nextFrame);
1403
+ });
1404
+ }, []);
1405
+ (0, react_1.useEffect)(() => {
1406
+ if (!(showToolbar && toolbarPlacement === 'inline' && editable)) {
1407
+ setInlineToolbarFrame(null);
1408
+ return;
1409
+ }
1410
+ const frame = requestAnimationFrame(() => {
1411
+ updateToolbarFrame();
1412
+ });
1413
+ return () => cancelAnimationFrame(frame);
1414
+ }, [editable, showToolbar, toolbarPlacement, updateToolbarFrame]);
1415
+ (0, react_1.useEffect)(() => {
1416
+ if (heightBehavior !== 'autoGrow') {
1417
+ setAutoGrowHeight(null);
1418
+ }
1419
+ }, [heightBehavior]);
1420
+ const handleUpdate = (0, react_1.useCallback)((event) => {
1421
+ const bridge = bridgeRef.current;
1422
+ if (!bridge || bridge.isDestroyed)
1423
+ return;
1424
+ if (!isCurrentNativeEditorEvent(event.nativeEvent, bridge))
1425
+ return;
1426
+ try {
1427
+ const previousDocumentVersion = documentVersionRef.current;
1428
+ if (react_native_1.Platform.OS === 'android' && typeof previousDocumentVersion === 'number') {
1429
+ const parsed = JSON.parse(event.nativeEvent.updateJson);
1430
+ if (typeof parsed.documentVersion !== 'number') {
1431
+ return;
1432
+ }
1433
+ }
1434
+ const nativeUpdate = bridge.parseUpdateJson(event.nativeEvent.updateJson, { rejectSameDocumentVersion: true });
1435
+ if (!nativeUpdate)
1436
+ return;
1437
+ syncNativeUpdateFromBridge(nativeUpdate, previousDocumentVersion);
1438
+ }
1439
+ catch {
1440
+ // Invalid JSON from native — skip
1441
+ }
1442
+ }, [syncNativeUpdateFromBridge]);
1443
+ const handleSelectionChange = (0, react_1.useCallback)((event) => {
1444
+ const bridge = bridgeRef.current;
1445
+ if (!bridge || bridge.isDestroyed)
1446
+ return;
1447
+ if (!isCurrentNativeEditorEvent(event.nativeEvent, bridge))
1448
+ return;
1449
+ const { anchor, head, stateJson, documentVersion } = event.nativeEvent;
1450
+ const currentDocumentVersion = documentVersionRef.current;
1451
+ if (typeof documentVersion === 'number' &&
1452
+ typeof currentDocumentVersion === 'number' &&
1453
+ documentVersion < currentDocumentVersion) {
1454
+ return;
1455
+ }
1456
+ let currentState = null;
1457
+ if (typeof stateJson === 'string' && stateJson.length > 0) {
1458
+ if (react_native_1.Platform.OS === 'android' &&
1459
+ typeof currentDocumentVersion === 'number') {
1460
+ const stateDocumentVersion = documentVersionFromUpdateJson(stateJson);
1461
+ if (typeof stateDocumentVersion !== 'number' ||
1462
+ stateDocumentVersion < currentDocumentVersion) {
1463
+ return;
1464
+ }
1465
+ }
1466
+ try {
1467
+ currentState = bridge.parseUpdateJson(stateJson);
1468
+ if (!currentState)
1469
+ return;
1470
+ }
1471
+ catch {
1472
+ currentState = bridge.getSelectionState();
1473
+ }
1474
+ }
1475
+ else {
1476
+ currentState = bridge.getSelectionState();
1477
+ }
1478
+ if (currentState != null &&
1479
+ typeof currentState.documentVersion === 'number' &&
1480
+ typeof currentDocumentVersion === 'number' &&
1481
+ currentState.documentVersion < currentDocumentVersion) {
1482
+ return;
1483
+ }
1484
+ if (currentState != null &&
1485
+ react_native_1.Platform.OS === 'android' &&
1486
+ typeof currentDocumentVersion === 'number' &&
1487
+ typeof currentState.documentVersion !== 'number') {
1488
+ return;
1489
+ }
1490
+ if (currentState == null &&
1491
+ react_native_1.Platform.OS === 'android' &&
1492
+ typeof currentDocumentVersion === 'number') {
1493
+ return;
1494
+ }
1495
+ let selection;
1496
+ const selectionStart = Math.min(anchor, head);
1497
+ const selectionEnd = Math.max(anchor, head);
1498
+ if (selectionStart === 0 &&
1499
+ selectionEnd >= renderedTextLengthRef.current &&
1500
+ renderedTextLengthRef.current > 0) {
1501
+ selection = { type: 'all' };
1502
+ }
1503
+ else {
1504
+ selection = { type: 'text', anchor, head };
1505
+ }
1506
+ const nextSelection = selection.type === 'all' ? selection : (currentState?.selection ?? selection);
1507
+ if (currentState == null) {
1508
+ bridge.updateSelectionFromNative(anchor, head);
1509
+ }
1510
+ syncSelectionStateFromUpdate(currentState);
1511
+ selectionRef.current = nextSelection;
1512
+ if (currentState) {
1513
+ onActiveStateChangeRef.current?.(currentState.activeState);
1514
+ onHistoryStateChangeRef.current?.(currentState.historyState);
1515
+ }
1516
+ onSelectionChangeRef.current?.(nextSelection);
1517
+ }, [syncSelectionStateFromUpdate]);
1518
+ const handleFocusChange = (0, react_1.useCallback)((event) => {
1519
+ if (!isCurrentNativeEditorEvent(event.nativeEvent, bridgeRef.current))
1520
+ return;
1521
+ const { isFocused: focused } = event.nativeEvent;
1522
+ const wasFocused = isFocusedRef.current;
1523
+ isFocusedRef.current = focused;
1524
+ (0, EditorToolbar_1.setActiveEditorToolbarFrameOwnerForEditor)(editorInstanceIdRef.current, focused);
1525
+ setIsFocused(focused);
1526
+ if (!focused) {
1527
+ setMentionQueryEventState(null);
1528
+ }
1529
+ if (focused) {
1530
+ if (!wasFocused) {
1531
+ onFocusRef.current?.();
1532
+ }
1533
+ }
1534
+ else if (wasFocused) {
1535
+ onBlurRef.current?.();
1536
+ }
1537
+ }, [setMentionQueryEventState]);
1538
+ (0, react_1.useEffect)(() => () => {
1539
+ (0, EditorToolbar_1.setActiveEditorToolbarFrameOwnerForEditor)(editorInstanceId, false);
1540
+ }, [editorInstanceId]);
1541
+ (0, react_1.useEffect)(() => {
1542
+ if (addons?.mentions != null) {
1543
+ return;
1544
+ }
1545
+ setMentionQueryEventState(null);
1546
+ }, [addons?.mentions, setMentionQueryEventState]);
1547
+ const handleContentHeightChange = (0, react_1.useCallback)((event) => {
1548
+ if (!isCurrentNativeEditorEvent(event.nativeEvent, bridgeRef.current))
1549
+ return;
1550
+ if (heightBehavior !== 'autoGrow')
1551
+ return;
1552
+ const density = react_native_1.Platform.OS === 'android' ? react_native_1.PixelRatio.get() : 1;
1553
+ const nextHeight = Math.ceil(event.nativeEvent.contentHeight / density);
1554
+ if (!(nextHeight > 0))
1555
+ return;
1556
+ setAutoGrowHeight((prev) => (prev === nextHeight ? prev : nextHeight));
1557
+ }, [autoGrowHeight, heightBehavior]);
1558
+ const handleEditorReady = (0, react_1.useCallback)((event) => {
1559
+ if (!isCurrentNativeEditorEvent(event.nativeEvent, bridgeRef.current))
1560
+ return;
1561
+ const editorId = bridgeRef.current?.editorId;
1562
+ const acknowledgedRevision = event.nativeEvent.editorUpdateRevision;
1563
+ const inFlight = pendingNativeUpdateInFlightRef.current;
1564
+ const resetInFlight = pendingNativeResetUpdateInFlightRef.current;
1565
+ let didClearInFlight = false;
1566
+ let didMatchInFlight = false;
1567
+ if (inFlight != null) {
1568
+ const matchesRevision = typeof acknowledgedRevision === 'number' &&
1569
+ inFlight.editorId === editorId &&
1570
+ inFlight.revision === acknowledgedRevision;
1571
+ if (matchesRevision) {
1572
+ didMatchInFlight = true;
1573
+ pendingNativeUpdateInFlightRef.current = null;
1574
+ didClearInFlight = true;
1575
+ setPendingNativeUpdate((current) => {
1576
+ if (current.editorId !== editorId ||
1577
+ current.revision !== acknowledgedRevision ||
1578
+ current.json == null) {
1579
+ return current;
1580
+ }
1581
+ const next = {
1582
+ json: undefined,
1583
+ editorId,
1584
+ revision: current.revision,
1585
+ };
1586
+ return next;
1587
+ });
1588
+ }
1589
+ }
1590
+ if (resetInFlight != null) {
1591
+ const matchesRevision = typeof acknowledgedRevision === 'number' &&
1592
+ resetInFlight.editorId === editorId &&
1593
+ resetInFlight.revision === acknowledgedRevision;
1594
+ if (matchesRevision) {
1595
+ didMatchInFlight = true;
1596
+ pendingNativeResetUpdateInFlightRef.current = null;
1597
+ didClearInFlight = true;
1598
+ setPendingNativeResetUpdate((current) => {
1599
+ if (current.editorId !== editorId ||
1600
+ current.revision !== acknowledgedRevision ||
1601
+ current.json == null) {
1602
+ return current;
1603
+ }
1604
+ const next = {
1605
+ json: undefined,
1606
+ editorId,
1607
+ revision: current.revision,
1608
+ };
1609
+ return next;
1610
+ });
1611
+ }
1612
+ }
1613
+ if ((inFlight != null || resetInFlight != null) && !didMatchInFlight) {
1614
+ return;
1615
+ }
1616
+ flushBlockedNativeCommandRetry();
1617
+ if (didClearInFlight && pendingControlledSyncAfterNativeUpdateRef.current) {
1618
+ pendingControlledSyncAfterNativeUpdateRef.current = false;
1619
+ setControlledNativeUpdateRetry((revision) => revision + 1);
1620
+ }
1621
+ if (!pendingDetachedControlledSyncRef.current)
1622
+ return;
1623
+ pendingDetachedControlledSyncRef.current = false;
1624
+ setDetachedControlledSyncRetry((revision) => revision + 1);
1625
+ }, [flushBlockedNativeCommandRetry]);
1626
+ const restoreSelection = (0, react_1.useCallback)((selection) => {
1627
+ if (selection.type === 'text') {
1628
+ const { anchor, head } = selection;
1629
+ if (anchor == null || head == null) {
1630
+ return;
1631
+ }
1632
+ bridgeRef.current?.setSelection(anchor, head);
1633
+ return;
1634
+ }
1635
+ if (selection.type === 'node') {
1636
+ const { pos } = selection;
1637
+ if (pos == null) {
1638
+ return;
1639
+ }
1640
+ bridgeRef.current?.setSelection(pos, pos);
1641
+ }
1642
+ }, []);
1643
+ const isAsyncRequestCurrent = (0, react_1.useCallback)((requestEditorId, requestDocumentVersion) => {
1644
+ const bridge = bridgeRef.current;
1645
+ return (bridge != null &&
1646
+ !bridge.isDestroyed &&
1647
+ bridge.editorId === requestEditorId &&
1648
+ documentVersionRef.current === requestDocumentVersion);
1649
+ }, []);
1650
+ const scalarRangeFromSelection = (0, react_1.useCallback)((selection) => {
1651
+ const bridge = bridgeRef.current;
1652
+ if (!bridge || bridge.isDestroyed)
1653
+ return null;
1654
+ if (selection.type === 'text') {
1655
+ const anchor = selection.anchor ?? 0;
1656
+ const head = selection.head ?? anchor;
1657
+ return {
1658
+ anchor: bridge.docToScalar(anchor),
1659
+ head: bridge.docToScalar(head),
1660
+ };
1661
+ }
1662
+ if (selection.type === 'node' && typeof selection.pos === 'number') {
1663
+ const scalar = bridge.docToScalar(selection.pos);
1664
+ return { anchor: scalar, head: scalar };
1665
+ }
1666
+ return null;
1667
+ }, []);
1668
+ const isCommandRetryScopeCurrent = (0, react_1.useCallback)((scope) => {
1669
+ const bridge = bridgeRef.current;
1670
+ if (!bridge || bridge.isDestroyed || bridge.editorId !== scope.editorId) {
1671
+ return false;
1672
+ }
1673
+ const currentDocumentVersion = documentVersionRef.current;
1674
+ const hasMentionScope = scope.mentionQuery != null ||
1675
+ scope.mentionRange != null ||
1676
+ scope.nativeMentionSelectRequest != null;
1677
+ if (currentDocumentVersion !== scope.documentVersion) {
1678
+ if (!hasMentionScope) {
1679
+ return false;
1680
+ }
1681
+ if (typeof currentDocumentVersion === 'number' &&
1682
+ typeof scope.documentVersion === 'number' &&
1683
+ currentDocumentVersion > scope.documentVersion) {
1684
+ return false;
1685
+ }
1686
+ }
1687
+ if (hasMentionScope &&
1688
+ typeof currentDocumentVersion === 'number' &&
1689
+ scope.documentVersion == null) {
1690
+ return false;
1691
+ }
1692
+ if (typeof scope.scalarAnchor === 'number' &&
1693
+ typeof scope.scalarHead === 'number') {
1694
+ const currentRange = scalarRangeFromSelection(selectionRef.current);
1695
+ if (currentRange?.anchor !== scope.scalarAnchor ||
1696
+ currentRange?.head !== scope.scalarHead) {
1697
+ return false;
1698
+ }
1699
+ }
1700
+ if (scope.mentionQuery) {
1701
+ const mentionQueryEditorId = mentionQueryEditorIdRef.current;
1702
+ if (mentionQueryEditorId != null &&
1703
+ mentionQueryEditorId !== scope.editorId) {
1704
+ return false;
1705
+ }
1706
+ const currentQuery = mentionQueryEventRef.current;
1707
+ if (currentQuery == null ||
1708
+ currentQuery.trigger !== scope.mentionQuery.trigger ||
1709
+ currentQuery.query !== scope.mentionQuery.query ||
1710
+ currentQuery.range.anchor !== scope.mentionQuery.range.anchor ||
1711
+ currentQuery.range.head !== scope.mentionQuery.range.head ||
1712
+ currentQuery.documentVersion !== scope.mentionQuery.documentVersion) {
1713
+ return false;
1714
+ }
1715
+ }
1716
+ if (scope.mentionRange) {
1717
+ const mentionQueryEditorId = mentionQueryEditorIdRef.current;
1718
+ if (mentionQueryEditorId != null &&
1719
+ mentionQueryEditorId !== scope.editorId) {
1720
+ return false;
1721
+ }
1722
+ const currentQuery = mentionQueryEventRef.current;
1723
+ if (currentQuery == null ||
1724
+ currentQuery.range.anchor !== scope.mentionRange.anchor ||
1725
+ currentQuery.range.head !== scope.mentionRange.head ||
1726
+ (currentQuery.documentVersion ?? null) !== scope.documentVersion) {
1727
+ return false;
1728
+ }
1729
+ }
1730
+ if (scope.nativeMentionSelectRequest) {
1731
+ const mentionQueryEditorId = mentionQueryEditorIdRef.current;
1732
+ if (mentionQueryEditorId != null &&
1733
+ mentionQueryEditorId !== scope.editorId) {
1734
+ return false;
1735
+ }
1736
+ if (doesLiveMentionQueryConflictWithNativeSelectRequest(scope.nativeMentionSelectRequest, mentionQueryEventRef.current, scope.documentVersion, currentDocumentVersion)) {
1737
+ return false;
1738
+ }
1739
+ }
1740
+ return true;
1741
+ }, [scalarRangeFromSelection]);
1742
+ const runAndApplyWithCommandRetry = (0, react_1.useCallback)((scope, mutate, options, onApplied) => {
1743
+ let didQueueRetry = false;
1744
+ let run = null;
1745
+ const runIfScopeCurrent = () => {
1746
+ if (!isCommandRetryScopeCurrent(scope))
1747
+ return null;
1748
+ return run?.() ?? null;
1749
+ };
1750
+ const retry = () => {
1751
+ const update = runIfScopeCurrent();
1752
+ if (update) {
1753
+ onApplied?.(update);
1754
+ }
1755
+ return update != null;
1756
+ };
1757
+ run = () => runAndApply(mutate, {
1758
+ ...options,
1759
+ retryBlockedCommand: retry,
1760
+ onBlockedCommandRetryQueued: () => {
1761
+ didQueueRetry = true;
1762
+ options?.onBlockedCommandRetryQueued?.();
1763
+ },
1764
+ });
1765
+ const update = runIfScopeCurrent();
1766
+ if (update) {
1767
+ onApplied?.(update);
1768
+ }
1769
+ return { update, queued: didQueueRetry };
1770
+ }, [isCommandRetryScopeCurrent, runAndApply]);
1771
+ const runPersistentContentCommand = (0, react_1.useCallback)((mutate, options) => {
1772
+ const requestedEditorId = bridgeRef.current?.editorId;
1773
+ let run = null;
1774
+ const retry = () => {
1775
+ const bridge = bridgeRef.current;
1776
+ if (bridge == null ||
1777
+ bridge.isDestroyed ||
1778
+ bridge.editorId !== requestedEditorId) {
1779
+ return false;
1780
+ }
1781
+ return run?.() != null;
1782
+ };
1783
+ run = () => runAndApply(mutate, {
1784
+ ...options,
1785
+ retryBlockedCommand: retry,
1786
+ });
1787
+ return run();
1788
+ }, [runAndApply]);
1789
+ const insertImage = (0, react_1.useCallback)((src, attrs, selection) => {
1790
+ const trimmedSrc = src.trim();
1791
+ if (!trimmedSrc)
1792
+ return;
1793
+ if (!allowBase64Images && isImageDataUrl(trimmedSrc)) {
1794
+ return;
1795
+ }
1796
+ runAndApply(() => {
1797
+ if (selection) {
1798
+ restoreSelection(selection);
1799
+ }
1800
+ return (bridgeRef.current?.insertContentJson((0, schemas_1.buildImageFragmentJson)({
1801
+ src: trimmedSrc,
1802
+ ...(attrs ?? {}),
1803
+ })) ?? null);
1804
+ });
1805
+ }, [allowBase64Images, restoreSelection, runAndApply]);
1806
+ const openLinkRequest = (0, react_1.useCallback)(() => {
1807
+ const requestSelection = selectionRef.current;
1808
+ const requestDocumentVersion = documentVersionRef.current;
1809
+ const requestEditorId = bridgeRef.current?.editorId ?? null;
1810
+ const linkState = activeStateRef.current;
1811
+ const requestLinkHref = typeof linkState.markAttrs?.link?.href === 'string'
1812
+ ? linkState.markAttrs.link.href
1813
+ : undefined;
1814
+ onRequestLink?.({
1815
+ href: requestLinkHref,
1816
+ isActive: linkState.marks.link === true,
1817
+ selection: requestSelection,
1818
+ setLink: (href) => {
1819
+ const trimmedHref = href.trim();
1820
+ if (!trimmedHref)
1821
+ return;
1822
+ if (!isAsyncRequestCurrent(requestEditorId, requestDocumentVersion))
1823
+ return;
1824
+ const bridge = bridgeRef.current;
1825
+ if (!bridge || bridge.isDestroyed)
1826
+ return;
1827
+ const scalarSelection = scalarRangeFromSelection(requestSelection);
1828
+ if (!scalarSelection)
1829
+ return;
1830
+ runAndApplyWithCommandRetry({
1831
+ editorId: bridge.editorId,
1832
+ documentVersion: requestDocumentVersion,
1833
+ scalarAnchor: scalarSelection.anchor,
1834
+ scalarHead: scalarSelection.head,
1835
+ }, () => bridgeRef.current?.setMarkAtSelectionScalar(scalarSelection.anchor, scalarSelection.head, 'link', { href: trimmedHref }) ?? null, { skipNativeApplyIfContentUnchanged: true });
1836
+ },
1837
+ unsetLink: () => {
1838
+ if (!isAsyncRequestCurrent(requestEditorId, requestDocumentVersion))
1839
+ return;
1840
+ const bridge = bridgeRef.current;
1841
+ if (!bridge || bridge.isDestroyed)
1842
+ return;
1843
+ const scalarSelection = scalarRangeFromSelection(requestSelection);
1844
+ if (!scalarSelection)
1845
+ return;
1846
+ runAndApplyWithCommandRetry({
1847
+ editorId: bridge.editorId,
1848
+ documentVersion: requestDocumentVersion,
1849
+ scalarAnchor: scalarSelection.anchor,
1850
+ scalarHead: scalarSelection.head,
1851
+ }, () => bridgeRef.current?.unsetMarkAtSelectionScalar(scalarSelection.anchor, scalarSelection.head, 'link') ?? null, { skipNativeApplyIfContentUnchanged: true });
1852
+ },
1853
+ });
1854
+ }, [
1855
+ isAsyncRequestCurrent,
1856
+ onRequestLink,
1857
+ runAndApplyWithCommandRetry,
1858
+ scalarRangeFromSelection,
1859
+ ]);
1860
+ const openImageRequest = (0, react_1.useCallback)(() => {
1861
+ const requestSelection = selectionRef.current;
1862
+ const requestDocumentVersion = documentVersionRef.current;
1863
+ const requestEditorId = bridgeRef.current?.editorId ?? null;
1864
+ onRequestImage?.({
1865
+ selection: requestSelection,
1866
+ allowBase64: allowBase64Images,
1867
+ insertImage: (src, attrs) => {
1868
+ const trimmedSrc = src.trim();
1869
+ if (!trimmedSrc)
1870
+ return;
1871
+ if (!allowBase64Images && isImageDataUrl(trimmedSrc))
1872
+ return;
1873
+ if (!isAsyncRequestCurrent(requestEditorId, requestDocumentVersion))
1874
+ return;
1875
+ const bridge = bridgeRef.current;
1876
+ if (!bridge || bridge.isDestroyed)
1877
+ return;
1878
+ const scalarSelection = scalarRangeFromSelection(requestSelection);
1879
+ if (!scalarSelection)
1880
+ return;
1881
+ runAndApplyWithCommandRetry({
1882
+ editorId: bridge.editorId,
1883
+ documentVersion: requestDocumentVersion,
1884
+ scalarAnchor: scalarSelection.anchor,
1885
+ scalarHead: scalarSelection.head,
1886
+ }, () => bridgeRef.current?.insertContentJsonAtSelectionScalar(scalarSelection.anchor, scalarSelection.head, (0, schemas_1.buildImageFragmentJson)({
1887
+ src: trimmedSrc,
1888
+ ...(attrs ?? {}),
1889
+ })) ?? null);
1890
+ },
1891
+ });
1892
+ }, [
1893
+ allowBase64Images,
1894
+ isAsyncRequestCurrent,
1895
+ onRequestImage,
1896
+ runAndApplyWithCommandRetry,
1897
+ scalarRangeFromSelection,
1898
+ ]);
1899
+ const handleToolbarAction = (0, react_1.useCallback)((event) => {
1900
+ if (!isCurrentNativeEditorEvent(event.nativeEvent, bridgeRef.current))
1901
+ return;
1902
+ const currentDocumentVersion = documentVersionRef.current;
1903
+ const eventDocumentVersion = event.nativeEvent.documentVersion;
1904
+ if (typeof eventDocumentVersion === 'number' &&
1905
+ typeof currentDocumentVersion === 'number' &&
1906
+ eventDocumentVersion < currentDocumentVersion) {
1907
+ return;
1908
+ }
1909
+ if (react_native_1.Platform.OS === 'android' &&
1910
+ typeof currentDocumentVersion === 'number' &&
1911
+ typeof eventDocumentVersion !== 'number' &&
1912
+ typeof event.nativeEvent.updateJson !== 'string') {
1913
+ return;
1914
+ }
1915
+ if (!syncPreflightUpdateFromNativeEvent(event.nativeEvent.updateJson)) {
1916
+ return;
1917
+ }
1918
+ if (event.nativeEvent.key === LINK_TOOLBAR_ACTION_KEY) {
1919
+ openLinkRequest();
1920
+ return;
1921
+ }
1922
+ if (event.nativeEvent.key === IMAGE_TOOLBAR_ACTION_KEY) {
1923
+ openImageRequest();
1924
+ return;
1925
+ }
1926
+ onToolbarAction?.(event.nativeEvent.key);
1927
+ }, [
1928
+ onToolbarAction,
1929
+ openImageRequest,
1930
+ openLinkRequest,
1931
+ syncPreflightUpdateFromNativeEvent,
1932
+ ]);
1933
+ const resolveMentionSelectionAttrs = (0, react_1.useCallback)((selectionEvent) => {
1934
+ let resolvedAttrs;
1935
+ try {
1936
+ resolvedAttrs =
1937
+ addonsRef.current?.mentions?.resolveSelectionAttrs?.(selectionEvent);
1938
+ }
1939
+ catch (error) {
1940
+ if (__DEV__) {
1941
+ console.error('NativeRichTextEditor: mentions.resolveSelectionAttrs threw', error);
1942
+ }
1943
+ }
1944
+ return isRecord(resolvedAttrs)
1945
+ ? { ...selectionEvent.attrs, ...resolvedAttrs }
1946
+ : selectionEvent.attrs;
1947
+ }, []);
1948
+ const resolveMentionInsertionAttrs = (0, react_1.useCallback)((selectionEvent) => {
1949
+ const attrs = resolveMentionSelectionAttrs(selectionEvent);
1950
+ let resolvedTheme;
1951
+ try {
1952
+ resolvedTheme = addonsRef.current?.mentions?.resolveTheme?.({
1953
+ ...selectionEvent,
1954
+ attrs,
1955
+ });
1956
+ }
1957
+ catch (error) {
1958
+ if (__DEV__) {
1959
+ console.error('NativeRichTextEditor: mentions.resolveTheme threw', error);
1960
+ }
1961
+ }
1962
+ return isRecord(resolvedTheme) ? { ...attrs, mentionTheme: resolvedTheme } : attrs;
1963
+ }, [resolveMentionSelectionAttrs]);
1964
+ const handleInlineMentionSuggestionPress = (0, react_1.useCallback)((suggestion) => {
1965
+ const mentionQuery = mentionQueryEventRef.current;
1966
+ const mentions = addonsRef.current?.mentions;
1967
+ if (!mentionQuery ||
1968
+ !mentions ||
1969
+ !bridgeRef.current ||
1970
+ bridgeRef.current.isDestroyed) {
1971
+ return;
1972
+ }
1973
+ const currentDocumentVersion = documentVersionRef.current;
1974
+ if (typeof mentionQuery.documentVersion === 'number' &&
1975
+ typeof currentDocumentVersion === 'number' &&
1976
+ mentionQuery.documentVersion < currentDocumentVersion) {
1977
+ setMentionQueryEventState(null);
1978
+ return;
1979
+ }
1980
+ let attrs = null;
1981
+ const requestDocumentVersion = typeof mentionQuery.documentVersion === 'number'
1982
+ ? mentionQuery.documentVersion
1983
+ : currentDocumentVersion;
1984
+ const retryScope = {
1985
+ editorId: bridgeRef.current.editorId,
1986
+ documentVersion: requestDocumentVersion,
1987
+ mentionQuery,
1988
+ };
1989
+ let queuedRetry = false;
1990
+ let retry = null;
1991
+ const finishSelection = (selectedAttrs) => {
1992
+ setMentionQueryEventState(null);
1993
+ mentions.onSelect?.({
1994
+ trigger: mentionQuery.trigger,
1995
+ suggestion,
1996
+ attrs: selectedAttrs,
1997
+ ...(typeof mentionQuery.documentVersion === 'number'
1998
+ ? { documentVersion: mentionQuery.documentVersion }
1999
+ : {}),
2000
+ });
2001
+ };
2002
+ const attemptInsertion = () => {
2003
+ if (!isCommandRetryScopeCurrent(retryScope))
2004
+ return false;
2005
+ attrs = null;
2006
+ const update = runAndApply(() => bridgeRef.current?.insertContentJsonAtSelectionScalarLazy(mentionQuery.range.anchor, mentionQuery.range.head, () => {
2007
+ attrs = resolveMentionInsertionAttrs({
2008
+ trigger: mentionQuery.trigger,
2009
+ suggestion,
2010
+ attrs: resolveMentionSuggestionAttrs(suggestion, mentionQuery.trigger),
2011
+ range: mentionQuery.range,
2012
+ ...(typeof mentionQuery.documentVersion === 'number'
2013
+ ? { documentVersion: mentionQuery.documentVersion }
2014
+ : {}),
2015
+ });
2016
+ return (0, addons_1.buildMentionFragmentJson)(attrs);
2017
+ }) ?? null, {
2018
+ retryBlockedCommand: () => retry?.() === true,
2019
+ onBlockedCommandRetryQueued: () => {
2020
+ queuedRetry = true;
2021
+ },
2022
+ });
2023
+ if (update && attrs) {
2024
+ finishSelection(attrs);
2025
+ return true;
2026
+ }
2027
+ return false;
2028
+ };
2029
+ retry = attemptInsertion;
2030
+ if (attemptInsertion()) {
2031
+ return;
2032
+ }
2033
+ if (queuedRetry) {
2034
+ return;
2035
+ }
2036
+ const latestMentionQuery = mentionQueryEventRef.current;
2037
+ if (latestMentionQuery == null ||
2038
+ (latestMentionQuery.trigger === mentionQuery.trigger &&
2039
+ latestMentionQuery.query === mentionQuery.query &&
2040
+ latestMentionQuery.range.anchor === mentionQuery.range.anchor &&
2041
+ latestMentionQuery.range.head === mentionQuery.range.head &&
2042
+ latestMentionQuery.documentVersion === mentionQuery.documentVersion)) {
2043
+ setMentionQueryEventState(null);
2044
+ }
2045
+ }, [
2046
+ isCommandRetryScopeCurrent,
2047
+ resolveMentionInsertionAttrs,
2048
+ runAndApply,
2049
+ setMentionQueryEventState,
2050
+ ]);
2051
+ const handleAddonEvent = (0, react_1.useCallback)((event) => {
2052
+ const bridge = bridgeRef.current;
2053
+ if (!isCurrentNativeEditorEvent(event.nativeEvent, bridge))
2054
+ return;
2055
+ let parsed = null;
2056
+ try {
2057
+ parsed = JSON.parse(event.nativeEvent.eventJson);
2058
+ }
2059
+ catch {
2060
+ return;
2061
+ }
2062
+ if (!parsed)
2063
+ return;
2064
+ let parsedDocumentVersion = typeof parsed.documentVersion === 'number' ? parsed.documentVersion : undefined;
2065
+ if (typeof parsedDocumentVersion !== 'number' &&
2066
+ parsed.type === 'mentionsSelectRequest' &&
2067
+ typeof parsed.updateJson === 'string') {
2068
+ try {
2069
+ const parsedUpdate = JSON.parse(parsed.updateJson);
2070
+ if (typeof parsedUpdate.documentVersion === 'number') {
2071
+ parsedDocumentVersion = parsedUpdate.documentVersion;
2072
+ }
2073
+ }
2074
+ catch {
2075
+ return;
2076
+ }
2077
+ }
2078
+ const currentDocumentVersion = documentVersionRef.current;
2079
+ const isStaleAddonEvent = typeof parsedDocumentVersion === 'number' &&
2080
+ typeof currentDocumentVersion === 'number' &&
2081
+ parsedDocumentVersion < currentDocumentVersion;
2082
+ const isVersionlessAndroidAddonEvent = react_native_1.Platform.OS === 'android' &&
2083
+ typeof currentDocumentVersion === 'number' &&
2084
+ typeof parsedDocumentVersion !== 'number';
2085
+ if (parsed.type === 'mentionsQueryChange') {
2086
+ if (isStaleAddonEvent || isVersionlessAndroidAddonEvent)
2087
+ return;
2088
+ const nextEvent = {
2089
+ query: parsed.query,
2090
+ trigger: parsed.trigger,
2091
+ range: parsed.range,
2092
+ isActive: parsed.isActive,
2093
+ documentVersion: parsedDocumentVersion,
2094
+ };
2095
+ setMentionQueryEventState(parsed.isActive ? nextEvent : null, event.nativeEvent.editorId ?? bridgeRef.current?.editorId ?? null);
2096
+ addonsRef.current?.mentions?.onQueryChange?.({
2097
+ query: nextEvent.query,
2098
+ trigger: nextEvent.trigger,
2099
+ range: nextEvent.range,
2100
+ isActive: nextEvent.isActive,
2101
+ ...(typeof nextEvent.documentVersion === 'number'
2102
+ ? { documentVersion: nextEvent.documentVersion }
2103
+ : {}),
2104
+ });
2105
+ return;
2106
+ }
2107
+ if (parsed.type === 'mentionsSelectRequest') {
2108
+ if (isStaleAddonEvent || isVersionlessAndroidAddonEvent)
2109
+ return;
2110
+ const requestDocumentVersion = typeof parsedDocumentVersion === 'number'
2111
+ ? parsedDocumentVersion
2112
+ : currentDocumentVersion;
2113
+ const nativeMentionSelectRequest = {
2114
+ trigger: parsed.trigger,
2115
+ suggestionKey: parsed.suggestionKey,
2116
+ range: parsed.range,
2117
+ };
2118
+ if (doesLiveMentionQueryConflictWithNativeSelectRequest(nativeMentionSelectRequest, mentionQueryEventRef.current, requestDocumentVersion, currentDocumentVersion)) {
2119
+ return;
2120
+ }
2121
+ if (!syncPreflightUpdateFromNativeEvent(parsed.updateJson))
2122
+ return;
2123
+ const suggestion = mentionSuggestionsByKeyRef.current.get(parsed.suggestionKey);
2124
+ if (!suggestion || !bridgeRef.current || bridgeRef.current.isDestroyed)
2125
+ return;
2126
+ let finalAttrs = null;
2127
+ runAndApplyWithCommandRetry({
2128
+ editorId: bridgeRef.current.editorId,
2129
+ documentVersion: requestDocumentVersion,
2130
+ nativeMentionSelectRequest,
2131
+ }, () => {
2132
+ return (bridgeRef.current?.insertContentJsonAtSelectionScalarLazy(parsed.range.anchor, parsed.range.head, () => {
2133
+ const selectionEvent = {
2134
+ trigger: parsed.trigger,
2135
+ suggestion,
2136
+ attrs: parsed.attrs,
2137
+ range: parsed.range,
2138
+ ...(typeof parsedDocumentVersion === 'number'
2139
+ ? { documentVersion: parsedDocumentVersion }
2140
+ : {}),
2141
+ };
2142
+ const attrs = resolveMentionInsertionAttrs(selectionEvent);
2143
+ finalAttrs = attrs;
2144
+ return (0, addons_1.buildMentionFragmentJson)(attrs);
2145
+ }) ?? null);
2146
+ }, undefined, () => {
2147
+ if (!finalAttrs)
2148
+ return;
2149
+ addonsRef.current?.mentions?.onSelect?.({
2150
+ trigger: parsed.trigger,
2151
+ suggestion,
2152
+ attrs: finalAttrs,
2153
+ ...(typeof parsedDocumentVersion === 'number'
2154
+ ? { documentVersion: parsedDocumentVersion }
2155
+ : {}),
2156
+ });
2157
+ });
2158
+ return;
2159
+ }
2160
+ if (parsed.type === 'mentionsSelect') {
2161
+ if (isStaleAddonEvent || isVersionlessAndroidAddonEvent)
2162
+ return;
2163
+ const suggestion = mentionSuggestionsByKeyRef.current.get(parsed.suggestionKey);
2164
+ if (!suggestion)
2165
+ return;
2166
+ addonsRef.current?.mentions?.onSelect?.({
2167
+ trigger: parsed.trigger,
2168
+ suggestion,
2169
+ attrs: parsed.attrs,
2170
+ ...(typeof parsedDocumentVersion === 'number'
2171
+ ? { documentVersion: parsedDocumentVersion }
2172
+ : {}),
2173
+ });
2174
+ }
2175
+ }, [
2176
+ resolveMentionInsertionAttrs,
2177
+ runAndApplyWithCommandRetry,
2178
+ setMentionQueryEventState,
2179
+ syncPreflightUpdateFromNativeEvent,
2180
+ ]);
2181
+ (0, react_1.useImperativeHandle)(ref, () => ({
2182
+ focus() {
2183
+ nativeViewRef.current?.focus?.();
2184
+ },
2185
+ blur() {
2186
+ nativeViewRef.current?.blur?.();
2187
+ },
2188
+ toggleMark(markType) {
2189
+ runAndApply(() => bridgeRef.current?.toggleMark(markType) ?? null, {
2190
+ skipNativeApplyIfContentUnchanged: true,
2191
+ });
2192
+ },
2193
+ setLink(href) {
2194
+ const trimmedHref = href.trim();
2195
+ if (!trimmedHref)
2196
+ return;
2197
+ runAndApply(() => bridgeRef.current?.setMark('link', { href: trimmedHref }) ?? null, { skipNativeApplyIfContentUnchanged: true });
2198
+ },
2199
+ unsetLink() {
2200
+ runAndApply(() => bridgeRef.current?.unsetMark('link') ?? null, {
2201
+ skipNativeApplyIfContentUnchanged: true,
2202
+ });
2203
+ },
2204
+ toggleBlockquote() {
2205
+ runAndApply(() => bridgeRef.current?.toggleBlockquote() ?? null);
2206
+ },
2207
+ toggleHeading(level) {
2208
+ runAndApply(() => bridgeRef.current?.toggleHeading(level) ?? null);
2209
+ },
2210
+ toggleList(listType) {
2211
+ runAndApply(() => bridgeRef.current?.toggleList(listType) ?? null);
2212
+ },
2213
+ indentListItem() {
2214
+ runAndApply(() => bridgeRef.current?.indentListItem() ?? null);
2215
+ },
2216
+ outdentListItem() {
2217
+ runAndApply(() => bridgeRef.current?.outdentListItem() ?? null);
2218
+ },
2219
+ insertNode(nodeType) {
2220
+ runAndApply(() => bridgeRef.current?.insertNode(nodeType) ?? null);
2221
+ },
2222
+ insertImage(src, attrs) {
2223
+ insertImage(src, attrs);
2224
+ },
2225
+ insertText(text) {
2226
+ runAndApply(() => bridgeRef.current?.replaceSelectionText(text) ?? null);
2227
+ },
2228
+ insertContentHtml(html) {
2229
+ runAndApply(() => bridgeRef.current?.insertContentHtml(html) ?? null);
2230
+ },
2231
+ insertContentJson(doc) {
2232
+ runAndApply(() => bridgeRef.current?.insertContentJson(doc) ?? null);
2233
+ },
2234
+ setContent(html) {
2235
+ runPersistentContentCommand(() => bridgeRef.current?.replaceHtml(html) ?? null);
2236
+ },
2237
+ setContentJson(doc) {
2238
+ const jsonString = stringifyCachedJson((0, schemas_1.normalizeDocumentJson)(doc, documentSchema));
2239
+ runPersistentContentCommand(() => bridgeRef.current?.replaceJsonString(jsonString) ?? null);
2240
+ },
2241
+ clearContent() {
2242
+ const jsonString = stringifyCachedJson((0, schemas_1.normalizeDocumentJson)({ type: 'doc', content: [] }, documentSchema));
2243
+ resetContentJsonString(jsonString);
2244
+ },
2245
+ getContent() {
2246
+ if (!bridgeRef.current || bridgeRef.current.isDestroyed)
2247
+ return '';
2248
+ if (!prepareBridgeForExternalContentRead()) {
2249
+ return bridgeRef.current.getCachedHtml() ?? '';
2250
+ }
2251
+ return bridgeRef.current.getHtml();
2252
+ },
2253
+ getContentJson() {
2254
+ if (!bridgeRef.current || bridgeRef.current.isDestroyed)
2255
+ return {};
2256
+ if (!prepareBridgeForExternalContentRead()) {
2257
+ return bridgeRef.current.getCachedJson() ?? {};
2258
+ }
2259
+ return bridgeRef.current.getJson();
2260
+ },
2261
+ getTextContent() {
2262
+ if (!bridgeRef.current || bridgeRef.current.isDestroyed)
2263
+ return '';
2264
+ if (!prepareBridgeForExternalContentRead()) {
2265
+ return (bridgeRef.current.getCachedHtml() ?? '').replace(/<[^>]+>/g, '');
2266
+ }
2267
+ return bridgeRef.current.getHtml().replace(/<[^>]+>/g, '');
2268
+ },
2269
+ async getCaretRect() {
2270
+ const nativeView = nativeViewRef.current;
2271
+ if (!nativeView?.getCaretRect)
2272
+ return null;
2273
+ const raw = await Promise.resolve(nativeView.getCaretRect());
2274
+ return parseCaretRectJson(raw);
2275
+ },
2276
+ undo() {
2277
+ runAndApply(() => bridgeRef.current?.undo() ?? null);
2278
+ },
2279
+ redo() {
2280
+ runAndApply(() => bridgeRef.current?.redo() ?? null);
2281
+ },
2282
+ canUndo() {
2283
+ if (!bridgeRef.current || bridgeRef.current.isDestroyed)
2284
+ return false;
2285
+ return bridgeRef.current.canUndo();
2286
+ },
2287
+ canRedo() {
2288
+ if (!bridgeRef.current || bridgeRef.current.isDestroyed)
2289
+ return false;
2290
+ return bridgeRef.current.canRedo();
2291
+ },
2292
+ }), [
2293
+ documentSchema,
2294
+ insertImage,
2295
+ prepareBridgeForExternalContentRead,
2296
+ runAndApply,
2297
+ runPersistentContentCommand,
2298
+ resetContentJsonString,
2299
+ ]);
2300
+ const activeMentionTrigger = mentionQueryEvent?.trigger || resolveMentionTrigger(addons);
2301
+ const activeMentionSuggestions = (0, react_1.useMemo)(() => isFocused && mentionQueryEvent != null && addons?.mentions != null
2302
+ ? filterMentionSuggestions(addons.mentions.suggestions ?? [], mentionQueryEvent.query, activeMentionTrigger)
2303
+ : [], [activeMentionTrigger, addons?.mentions, isFocused, mentionQueryEvent]);
2304
+ const inlineToolbarMentionTheme = theme?.mentions ?? addons?.mentions?.theme;
2305
+ const activeMentionSuggestionThemes = (0, react_1.useMemo)(() => {
2306
+ if (mentionQueryEvent == null ||
2307
+ addons?.mentions == null ||
2308
+ typeof addons.mentions.resolveTheme !== 'function' ||
2309
+ activeMentionSuggestions.length === 0) {
2310
+ return undefined;
2311
+ }
2312
+ const suggestionThemes = {};
2313
+ for (const suggestion of activeMentionSuggestions) {
2314
+ const selectionEvent = {
2315
+ trigger: activeMentionTrigger,
2316
+ suggestion,
2317
+ attrs: resolveMentionSuggestionAttrs(suggestion, activeMentionTrigger),
2318
+ range: mentionQueryEvent.range,
2319
+ ...(typeof mentionQueryEvent.documentVersion === 'number'
2320
+ ? { documentVersion: mentionQueryEvent.documentVersion }
2321
+ : {}),
2322
+ };
2323
+ const attrs = resolveMentionSelectionAttrs(selectionEvent);
2324
+ let resolvedTheme;
2325
+ try {
2326
+ const nextTheme = addons.mentions.resolveTheme({
2327
+ ...selectionEvent,
2328
+ attrs,
2329
+ });
2330
+ resolvedTheme = isRecord(nextTheme)
2331
+ ? nextTheme
2332
+ : undefined;
2333
+ }
2334
+ catch (error) {
2335
+ if (__DEV__) {
2336
+ console.error('NativeRichTextEditor: mentions.resolveTheme threw', error);
2337
+ }
2338
+ }
2339
+ const mergedTheme = mergeMentionSuggestionTheme(inlineToolbarMentionTheme, resolvedTheme);
2340
+ if (mergedTheme != null) {
2341
+ suggestionThemes[suggestion.key] = mergedTheme;
2342
+ }
2343
+ }
2344
+ return Object.keys(suggestionThemes).length > 0 ? suggestionThemes : undefined;
2345
+ }, [
2346
+ activeMentionSuggestions,
2347
+ activeMentionTrigger,
2348
+ addons?.mentions,
2349
+ inlineToolbarMentionTheme,
2350
+ mentionQueryEvent,
2351
+ resolveMentionSelectionAttrs,
2352
+ ]);
2353
+ const shouldPublishStandaloneMentionSuggestions = editable &&
2354
+ !showToolbar &&
2355
+ registeredToolbarFrames.length > 0 &&
2356
+ mentionQueryEvent != null &&
2357
+ activeMentionSuggestions.length > 0 &&
2358
+ addons?.mentions != null;
2359
+ (0, react_1.useEffect)(() => {
2360
+ if (editorInstanceId === 0) {
2361
+ return;
2362
+ }
2363
+ if (!shouldPublishStandaloneMentionSuggestions || mentionQueryEvent == null) {
2364
+ (0, EditorToolbar_1.setEditorToolbarMentionState)(editorInstanceId, null);
2365
+ return () => (0, EditorToolbar_1.setEditorToolbarMentionState)(editorInstanceId, null);
2366
+ }
2367
+ (0, EditorToolbar_1.setEditorToolbarMentionState)(editorInstanceId, {
2368
+ trigger: activeMentionTrigger,
2369
+ suggestions: activeMentionSuggestions,
2370
+ theme: inlineToolbarMentionTheme,
2371
+ suggestionThemes: activeMentionSuggestionThemes,
2372
+ onSelectSuggestion: handleInlineMentionSuggestionPress,
2373
+ });
2374
+ return () => (0, EditorToolbar_1.setEditorToolbarMentionState)(editorInstanceId, null);
2375
+ }, [
2376
+ activeMentionSuggestions,
2377
+ activeMentionSuggestionThemes,
2378
+ activeMentionTrigger,
2379
+ editorInstanceId,
2380
+ handleInlineMentionSuggestionPress,
2381
+ inlineToolbarMentionTheme,
2382
+ mentionQueryEvent,
2383
+ shouldPublishStandaloneMentionSuggestions,
2384
+ ]);
2385
+ if (!isReady)
2386
+ return null;
2387
+ const isLinkActive = activeState.marks.link === true;
2388
+ const allowsLink = activeState.allowedMarks.includes('link');
2389
+ const canInsertImage = activeState.insertableNodes.includes(schemas_1.IMAGE_NODE_NAME);
2390
+ const canRequestLink = typeof onRequestLink === 'function';
2391
+ const canRequestImage = typeof onRequestImage === 'function';
2392
+ const cachedToolbarItems = toolbarItemsSerializationCacheRef.current;
2393
+ let toolbarItemsJson;
2394
+ if (cachedToolbarItems?.toolbarItems === toolbarItems &&
2395
+ cachedToolbarItems.editable === editable &&
2396
+ cachedToolbarItems.isLinkActive === isLinkActive &&
2397
+ cachedToolbarItems.allowsLink === allowsLink &&
2398
+ cachedToolbarItems.canRequestLink === canRequestLink &&
2399
+ cachedToolbarItems.canRequestImage === canRequestImage &&
2400
+ cachedToolbarItems.canInsertImage === canInsertImage) {
2401
+ toolbarItemsJson = cachedToolbarItems.serialized;
2402
+ }
2403
+ else {
2404
+ const mappedItems = mapToolbarItemsForNative(toolbarItems, activeState, editable, onRequestLink, onRequestImage);
2405
+ toolbarItemsJson = stringifyCachedJson(mappedItems);
2406
+ toolbarItemsSerializationCacheRef.current = {
2407
+ toolbarItems,
2408
+ editable,
2409
+ isLinkActive,
2410
+ allowsLink,
2411
+ canRequestLink,
2412
+ canRequestImage,
2413
+ canInsertImage,
2414
+ mappedItems,
2415
+ serialized: toolbarItemsJson,
2416
+ };
2417
+ }
2418
+ const usesNativeKeyboardToolbar = toolbarPlacement === 'keyboard' && (react_native_1.Platform.OS === 'ios' || react_native_1.Platform.OS === 'android');
2419
+ const shouldRenderJsToolbar = !usesNativeKeyboardToolbar && showToolbar && editable;
2420
+ const inlineToolbarChrome = {
2421
+ backgroundColor: theme?.toolbar?.backgroundColor,
2422
+ borderColor: theme?.toolbar?.borderColor,
2423
+ borderWidth: theme?.toolbar?.borderWidth,
2424
+ borderRadius: theme?.toolbar?.borderRadius,
2425
+ };
2426
+ const inlineToolbarMarginTop = theme?.toolbar?.marginTop ?? 8;
2427
+ const inlineToolbarShowTopBorder = theme?.toolbar?.showTopBorder ?? false;
2428
+ const inlineToolbarContentTopBorderStyle = inlineToolbarShowTopBorder
2429
+ ? {
2430
+ borderTopWidth: theme?.toolbar?.borderWidth ?? react_native_1.StyleSheet.hairlineWidth,
2431
+ borderTopColor: theme?.toolbar?.borderColor ?? INLINE_TOOLBAR_BORDER_COLOR,
2432
+ }
2433
+ : null;
2434
+ const inlineMentionSuggestions = toolbarPlacement === 'inline' ? activeMentionSuggestions : [];
2435
+ const shouldShowInlineMentionSuggestions = shouldRenderJsToolbar &&
2436
+ toolbarPlacement === 'inline' &&
2437
+ isFocused &&
2438
+ inlineMentionSuggestions.length > 0;
2439
+ const containerMinHeight = react_native_1.StyleSheet.flatten(containerStyle)?.minHeight;
2440
+ const nativeViewStyleParts = [];
2441
+ if (containerMinHeight != null) {
2442
+ nativeViewStyleParts.push({ minHeight: containerMinHeight });
2443
+ }
2444
+ if (style != null) {
2445
+ nativeViewStyleParts.push(style);
2446
+ }
2447
+ if (heightBehavior === 'autoGrow' && autoGrowHeight != null) {
2448
+ nativeViewStyleParts.push({ height: autoGrowHeight });
2449
+ }
2450
+ const nativeViewStyle = nativeViewStyleParts.length <= 1 ? nativeViewStyleParts[0] : nativeViewStyleParts;
2451
+ const toolbarFrameJson = serializeToolbarFrames(editable && isFocused
2452
+ ? [
2453
+ ...(toolbarPlacement === 'inline' && inlineToolbarFrame != null
2454
+ ? [inlineToolbarFrame]
2455
+ : []),
2456
+ ...registeredToolbarFrames,
2457
+ ]
2458
+ : undefined);
2459
+ const jsToolbar = ((0, jsx_runtime_1.jsx)(react_native_1.View, { ref: toolbarRef, testID: 'native-editor-js-toolbar', style: [
2460
+ styles.inlineToolbar,
2461
+ { marginTop: inlineToolbarMarginTop },
2462
+ inlineToolbarChrome.backgroundColor != null
2463
+ ? { backgroundColor: inlineToolbarChrome.backgroundColor }
2464
+ : null,
2465
+ inlineToolbarChrome.borderColor != null
2466
+ ? { borderColor: inlineToolbarChrome.borderColor }
2467
+ : null,
2468
+ inlineToolbarChrome.borderWidth != null
2469
+ ? { borderWidth: inlineToolbarChrome.borderWidth }
2470
+ : null,
2471
+ inlineToolbarChrome.borderRadius != null
2472
+ ? { borderRadius: inlineToolbarChrome.borderRadius }
2473
+ : null,
2474
+ ], onLayout: updateToolbarFrame, children: shouldShowInlineMentionSuggestions ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { testID: 'native-editor-inline-mention-suggestions', style: [
2475
+ styles.inlineMentionSuggestionsContainer,
2476
+ inlineToolbarContentTopBorderStyle,
2477
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, contentContainerStyle: styles.inlineMentionSuggestionsContent, keyboardShouldPersistTaps: 'always', children: inlineMentionSuggestions.map((suggestion) => {
2478
+ const label = resolveMentionSuggestionLabel(suggestion, mentionQueryEvent?.trigger ?? resolveMentionTrigger(addons));
2479
+ const suggestionTheme = activeMentionSuggestionThemes?.[suggestion.key] ??
2480
+ inlineToolbarMentionTheme;
2481
+ return ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { testID: `native-editor-inline-mention-suggestion-${suggestion.key}`, onPress: () => handleInlineMentionSuggestionPress(suggestion), accessibilityRole: 'button', accessibilityLabel: label, style: ({ pressed }) => [
2482
+ styles.inlineMentionSuggestion,
2483
+ {
2484
+ backgroundColor: pressed
2485
+ ? (suggestionTheme?.optionHighlightedBackgroundColor ??
2486
+ 'rgba(0, 122, 255, 0.12)')
2487
+ : (suggestionTheme?.backgroundColor ??
2488
+ '#F2F2F7'),
2489
+ borderColor: suggestionTheme?.borderColor ?? 'transparent',
2490
+ borderWidth: suggestionTheme?.borderWidth ?? 0,
2491
+ borderRadius: suggestionTheme?.borderRadius ?? 12,
2492
+ },
2493
+ ], children: ({ pressed }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { numberOfLines: 1, style: [
2494
+ styles.inlineMentionSuggestionTitle,
2495
+ {
2496
+ fontWeight: suggestionTheme?.fontWeight ??
2497
+ '600',
2498
+ color: pressed
2499
+ ? (suggestionTheme?.optionHighlightedTextColor ??
2500
+ suggestionTheme?.optionTextColor ??
2501
+ '#000000')
2502
+ : (suggestionTheme?.optionTextColor ??
2503
+ suggestionTheme?.textColor ??
2504
+ '#000000'),
2505
+ },
2506
+ ], children: label }), suggestion.subtitle ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { numberOfLines: 1, style: [
2507
+ styles.inlineMentionSuggestionSubtitle,
2508
+ {
2509
+ color: suggestionTheme?.optionSecondaryTextColor ??
2510
+ '#8E8E93',
2511
+ },
2512
+ ], children: suggestion.subtitle })) : null] })) }, suggestion.key));
2513
+ }) }) })) : ((0, jsx_runtime_1.jsx)(EditorToolbar_1.EditorToolbar, { activeState: activeState, historyState: historyState, toolbarItems: toolbarItems, theme: theme?.toolbar, showTopBorder: inlineToolbarShowTopBorder, preserveEditorFocus: false, onToggleMark: (mark) => runAndApply(() => bridgeRef.current?.toggleMark(mark) ?? null, {
2514
+ skipNativeApplyIfContentUnchanged: true,
2515
+ }), onToggleListType: (listType) => runAndApply(() => bridgeRef.current?.toggleList(listType) ?? null), onToggleHeading: (level) => runAndApply(() => bridgeRef.current?.toggleHeading(level) ?? null), onToggleBlockquote: () => runAndApply(() => bridgeRef.current?.toggleBlockquote() ?? null), onInsertNodeType: (nodeType) => runAndApply(() => bridgeRef.current?.insertNode(nodeType) ?? null), onRunCommand: (command) => {
2516
+ switch (command) {
2517
+ case 'indentList':
2518
+ runAndApply(() => bridgeRef.current?.indentListItem() ?? null);
2519
+ break;
2520
+ case 'outdentList':
2521
+ runAndApply(() => bridgeRef.current?.outdentListItem() ?? null);
2522
+ break;
2523
+ case 'undo':
2524
+ runAndApply(() => bridgeRef.current?.undo() ?? null);
2525
+ break;
2526
+ case 'redo':
2527
+ runAndApply(() => bridgeRef.current?.redo() ?? null);
2528
+ break;
2529
+ }
2530
+ }, onRequestLink: openLinkRequest, onRequestImage: openImageRequest, onToolbarAction: onToolbarAction, onToggleBold: () => runAndApply(() => bridgeRef.current?.toggleMark('bold') ?? null, {
2531
+ skipNativeApplyIfContentUnchanged: true,
2532
+ }), onToggleItalic: () => runAndApply(() => bridgeRef.current?.toggleMark('italic') ?? null, {
2533
+ skipNativeApplyIfContentUnchanged: true,
2534
+ }), onToggleUnderline: () => runAndApply(() => bridgeRef.current?.toggleMark('underline') ?? null, {
2535
+ skipNativeApplyIfContentUnchanged: true,
2536
+ }), onToggleStrike: () => runAndApply(() => bridgeRef.current?.toggleMark('strike') ?? null, {
2537
+ skipNativeApplyIfContentUnchanged: true,
2538
+ }), onToggleBulletList: () => runAndApply(() => bridgeRef.current?.toggleList('bulletList') ?? null), onToggleOrderedList: () => runAndApply(() => bridgeRef.current?.toggleList('orderedList') ?? null), onIndentList: () => runAndApply(() => bridgeRef.current?.indentListItem() ?? null), onOutdentList: () => runAndApply(() => bridgeRef.current?.outdentListItem() ?? null), onInsertHorizontalRule: () => runAndApply(() => bridgeRef.current?.insertNode('horizontalRule') ?? null), onInsertLineBreak: () => runAndApply(() => bridgeRef.current?.insertNode('hardBreak') ?? null), onUndo: () => runAndApply(() => bridgeRef.current?.undo() ?? null), onRedo: () => runAndApply(() => bridgeRef.current?.redo() ?? null) })) }));
2539
+ return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.container, containerStyle], children: [(0, jsx_runtime_1.jsx)(NativeEditorView, { ref: nativeViewRef, style: nativeViewStyle, editorId: editorInstanceId, placeholder: placeholder, editable: editable, autoFocus: autoFocus, autoCapitalize: autoCapitalize, autoCorrect: autoCorrect, keyboardType: keyboardType, keyboardAppearance: keyboardAppearance, showToolbar: showToolbar, toolbarPlacement: toolbarPlacement, heightBehavior: heightBehavior, allowImageResizing: allowImageResizing, themeJson: themeJson, addonsJson: addonsJson, toolbarItemsJson: toolbarItemsJson, remoteSelectionsJson: remoteSelectionsJson, toolbarFrameJson: toolbarFrameJson, editorUpdateJson: pendingNativeUpdate.json, editorUpdateEditorId: pendingNativeUpdate.editorId, editorUpdateRevision: pendingNativeUpdate.revision, editorResetUpdateJson: pendingNativeResetUpdate.json, editorResetUpdateEditorId: pendingNativeResetUpdate.editorId, editorResetUpdateRevision: pendingNativeResetUpdate.revision, onEditorUpdate: handleUpdate, onSelectionChange: handleSelectionChange, onFocusChange: handleFocusChange, onContentHeightChange: handleContentHeightChange, ...(react_native_1.Platform.OS === 'android' ? { onEditorReady: handleEditorReady } : {}), onToolbarAction: handleToolbarAction, onAddonEvent: handleAddonEvent }, DEV_NATIVE_VIEW_KEY), shouldRenderJsToolbar && jsToolbar] }));
2540
+ });
2541
+ const styles = react_native_1.StyleSheet.create({
2542
+ container: {
2543
+ position: 'relative',
2544
+ },
2545
+ inlineToolbar: {
2546
+ borderWidth: react_native_1.StyleSheet.hairlineWidth,
2547
+ borderColor: INLINE_TOOLBAR_BORDER_COLOR,
2548
+ overflow: 'hidden',
2549
+ },
2550
+ inlineMentionSuggestionsContainer: {
2551
+ overflow: 'hidden',
2552
+ },
2553
+ inlineMentionSuggestionsContent: {
2554
+ paddingHorizontal: 12,
2555
+ paddingVertical: 8,
2556
+ alignItems: 'center',
2557
+ },
2558
+ inlineMentionSuggestion: {
2559
+ minWidth: 88,
2560
+ minHeight: 40,
2561
+ marginRight: 8,
2562
+ paddingHorizontal: 12,
2563
+ paddingVertical: 8,
2564
+ justifyContent: 'center',
2565
+ },
2566
+ inlineMentionSuggestionTitle: {
2567
+ fontSize: 14,
2568
+ fontWeight: '600',
2569
+ },
2570
+ inlineMentionSuggestionSubtitle: {
2571
+ marginTop: 1,
2572
+ fontSize: 12,
2573
+ },
2574
+ });