@apollohg/react-native-rich-text-editor 2.0.0 → 2.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 (180) hide show
  1. package/README.md +8 -116
  2. package/THIRD_PARTY_NOTICES.md +1 -1
  3. package/android/build.gradle +3 -0
  4. package/android/src/main/java/com/apollohg/editor/EditorEditTextImages.kt +25 -0
  5. package/android/src/main/java/com/apollohg/editor/EditorV2RenderValidation.kt +1 -0
  6. package/android/src/main/java/com/apollohg/editor/ImageResizeOverlayView.kt +101 -33
  7. package/android/src/main/java/com/apollohg/editor/RenderMediaSpans.kt +15 -3
  8. package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +14 -0
  9. package/android/src/main/java/com/apollohg/editor/viewer/AndroidProseLayoutEngine.kt +1 -1
  10. package/app.plugin.js +42 -39
  11. package/dist/AtomHost.js +18 -11
  12. package/dist/EditorAddon.js +4 -2
  13. package/dist/EditorAddonNormalization.js +17 -9
  14. package/dist/EditorMentionThemeNormalization.js +6 -3
  15. package/dist/EditorMentionThemeValidation.js +28 -11
  16. package/dist/EditorStyleSheetNormalization.js +85 -38
  17. package/dist/EditorStyleSheetTypes.d.ts +1 -1
  18. package/dist/EditorTheme.d.ts +4 -0
  19. package/dist/EditorTheme.js +5 -3
  20. package/dist/EditorToolbarComponent.d.ts +1 -1
  21. package/dist/EditorToolbarComponent.js +1 -1
  22. package/dist/EditorToolbarRegistry.js +5 -5
  23. package/dist/EditorToolbarTypes.d.ts +12 -12
  24. package/dist/EditorToolbarVisuals.js +1 -1
  25. package/dist/ExternalTextComposition.js +89 -43
  26. package/dist/ImageLoadingPolicy.d.ts +1 -1
  27. package/dist/NativeEditorBoundaryError.d.ts +7 -7
  28. package/dist/NativeEditorBoundaryError.js +9 -5
  29. package/dist/NativeEditorCollaborationTransport.d.ts +7 -7
  30. package/dist/NativeEditorCollaborationTransport.js +28 -15
  31. package/dist/NativeEditorCreateConfig.js +11 -6
  32. package/dist/NativeEditorCreateJson.js +51 -25
  33. package/dist/NativeEditorCreateValidation.js +11 -6
  34. package/dist/NativeEditorDocumentBridge.d.ts +1 -1
  35. package/dist/NativeEditorDocumentBridge.js +32 -18
  36. package/dist/NativeEditorDocumentHandle.d.ts +4 -4
  37. package/dist/NativeEditorDocumentHandle.js +1 -1
  38. package/dist/NativeEditorLocalAwareness.js +25 -13
  39. package/dist/NativeEditorNativeModule.d.ts +8 -1
  40. package/dist/NativeEditorNativeModule.js +7 -5
  41. package/dist/NativeEditorRenderNormalization.js +66 -25
  42. package/dist/NativeEditorResultNormalization.js +53 -29
  43. package/dist/NativeEditorTypes.d.ts +10 -10
  44. package/dist/NativeEditorV2Decimal.js +6 -3
  45. package/dist/NativeProseViewer.js +11 -3
  46. package/dist/ResourceLimits.d.ts +2 -2
  47. package/dist/ResourceLimits.js +7 -2
  48. package/dist/RichTextEditor.js +1 -1
  49. package/dist/RichTextEditorNativeTypes.d.ts +1 -1
  50. package/dist/RichTextEditorNativeView.d.ts +1 -3
  51. package/dist/RichTextEditorSerialization.js +24 -14
  52. package/dist/RichTextEditorTypes.d.ts +1 -1
  53. package/dist/YjsCollaboration.d.ts +7 -7
  54. package/dist/YjsCollaboration.js +42 -26
  55. package/dist/addons.d.ts +4 -4
  56. package/dist/addons.js +3 -3
  57. package/dist/atomInstances.js +6 -3
  58. package/dist/atomUpdates.js +7 -4
  59. package/dist/atoms.js +20 -12
  60. package/dist/attributeValidation.js +55 -24
  61. package/dist/contentExpression.js +80 -42
  62. package/dist/listMarkers.d.ts +1 -1
  63. package/dist/listMarkers.js +1 -1
  64. package/dist/schemaDefinition.d.ts +1 -1
  65. package/dist/schemaDefinition.js +23 -14
  66. package/dist/schemaDocument.js +1 -1
  67. package/dist/schemaDocumentConstruction.js +25 -14
  68. package/dist/schemaNormalization.js +51 -29
  69. package/dist/schemaPresets.d.ts +1 -1
  70. package/dist/schemaPresets.js +9 -4
  71. package/dist/schemaResolution.js +46 -25
  72. package/dist/specs/PreparedProseViewerNativeComponent.d.ts +4 -0
  73. package/dist/useEditorToolbarInteractions.d.ts +2 -2
  74. package/dist/useEditorToolbarInteractions.js +45 -23
  75. package/dist/useEditorToolbarItems.d.ts +1 -1
  76. package/dist/useEditorToolbarItems.js +4 -3
  77. package/dist/useEditorToolbarPresentation.d.ts +3 -3
  78. package/dist/useEditorToolbarPresentation.js +4 -4
  79. package/dist/useEditorToolbarState.d.ts +1 -1
  80. package/dist/useEditorToolbarState.js +1 -1
  81. package/dist/useFocusPreservingFrames.js +11 -8
  82. package/dist/useNativeEditor.d.ts +1 -1
  83. package/dist/useNativeEditor.js +21 -11
  84. package/dist/useRichTextEditorCommands.d.ts +2 -2
  85. package/dist/useRichTextEditorCommands.js +59 -29
  86. package/dist/useRichTextEditorEvents.d.ts +3 -3
  87. package/dist/useRichTextEditorEvents.js +75 -32
  88. package/dist/useRichTextEditorMentions.d.ts +4 -4
  89. package/dist/useRichTextEditorMentions.js +48 -29
  90. package/dist/useRichTextEditorPresentation.d.ts +4 -4
  91. package/dist/useRichTextEditorPresentation.js +25 -23
  92. package/dist/useRichTextEditorState.d.ts +3 -3
  93. package/dist/useRichTextEditorState.js +20 -15
  94. package/dist/useRichTextEditorUpdates.d.ts +1 -1
  95. package/dist/useRichTextEditorUpdates.js +99 -41
  96. package/dist/useViewerAtoms.js +63 -36
  97. package/ios/EditorCore.xcframework/ios-arm64/libeditor_core.a +0 -0
  98. package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a +0 -0
  99. package/ios/EditorInteractionOverlays.swift +114 -122
  100. package/ios/EditorLayoutManager.swift +13 -3
  101. package/ios/EditorStyleSheet.swift +12 -1
  102. package/ios/EditorTextView+Images.swift +32 -10
  103. package/ios/EditorTextView.swift +1 -1
  104. package/ios/EditorV2Adapter+RenderValidation.swift +2 -2
  105. package/ios/NativeToolbar+ButtonStyle.swift +17 -5
  106. package/ios/NativeToolbar+Diagnostics.swift +15 -0
  107. package/ios/RenderBridge+Atoms.swift +0 -1
  108. package/ios/RenderBridge+Attachments.swift +0 -29
  109. package/ios/RichTextEditorView.swift +4 -12
  110. package/ios/Viewer/CoreTextProseLayoutEngine+AttributedText.swift +3 -2
  111. package/package.json +11 -3
  112. package/react-native.config.js +1 -1
  113. package/rust/android/arm64-v8a/libeditor_core.so +0 -0
  114. package/rust/android/armeabi-v7a/libeditor_core.so +0 -0
  115. package/rust/android/x86/libeditor_core.so +0 -0
  116. package/rust/android/x86_64/libeditor_core.so +0 -0
  117. package/src/AtomHost.tsx +58 -28
  118. package/src/EditorAddon.ts +18 -8
  119. package/src/EditorAddonNormalization.ts +40 -11
  120. package/src/EditorMentionThemeNormalization.ts +15 -4
  121. package/src/EditorMentionThemeValidation.ts +51 -14
  122. package/src/EditorStyleSheet.ts +1 -0
  123. package/src/EditorStyleSheetNormalization.ts +195 -64
  124. package/src/EditorStyleSheetTypes.ts +1 -1
  125. package/src/EditorTheme.ts +13 -5
  126. package/src/EditorToolbarComponent.tsx +2 -1
  127. package/src/EditorToolbarRegistry.tsx +23 -13
  128. package/src/EditorToolbarTypes.tsx +12 -12
  129. package/src/EditorToolbarVisuals.tsx +6 -3
  130. package/src/EditorUpdateRevision.ts +1 -0
  131. package/src/ExternalTextComposition.ts +259 -49
  132. package/src/ImageLoadingPolicy.ts +3 -1
  133. package/src/NativeEditorBoundaryError.ts +38 -11
  134. package/src/NativeEditorCollaborationTransport.ts +108 -27
  135. package/src/NativeEditorCreateConfig.ts +92 -19
  136. package/src/NativeEditorCreateJson.ts +163 -26
  137. package/src/NativeEditorCreateValidation.ts +33 -12
  138. package/src/NativeEditorDocumentBridge.ts +111 -19
  139. package/src/NativeEditorDocumentHandle.ts +9 -4
  140. package/src/NativeEditorLocalAwareness.ts +70 -15
  141. package/src/NativeEditorNativeModule.ts +13 -9
  142. package/src/NativeEditorRenderNormalization.ts +128 -29
  143. package/src/NativeEditorResultNormalization.ts +143 -32
  144. package/src/NativeEditorTypes.ts +12 -11
  145. package/src/NativeEditorV2Decimal.ts +12 -3
  146. package/src/NativeProseViewer.tsx +46 -16
  147. package/src/ResourceLimits.ts +15 -8
  148. package/src/RichTextEditor.tsx +3 -1
  149. package/src/RichTextEditorNativeTypes.tsx +1 -1
  150. package/src/RichTextEditorNativeView.tsx +2 -8
  151. package/src/RichTextEditorSerialization.tsx +70 -24
  152. package/src/RichTextEditorTypes.tsx +1 -1
  153. package/src/YjsCollaboration.ts +143 -47
  154. package/src/addons.ts +12 -8
  155. package/src/atomInstances.ts +25 -5
  156. package/src/atomUpdates.ts +23 -6
  157. package/src/atoms.ts +85 -18
  158. package/src/attributeValidation.ts +97 -33
  159. package/src/contentExpression.ts +218 -63
  160. package/src/listMarkers.ts +1 -1
  161. package/src/schemaDefinition.ts +82 -20
  162. package/src/schemaDocument.ts +15 -1
  163. package/src/schemaDocumentConstruction.ts +80 -16
  164. package/src/schemaNormalization.ts +155 -41
  165. package/src/schemaPresets.ts +37 -31
  166. package/src/schemaResolution.ts +126 -27
  167. package/src/specs/PreparedProseViewerNativeComponent.ts +4 -2
  168. package/src/useEditorToolbarInteractions.tsx +85 -36
  169. package/src/useEditorToolbarItems.tsx +50 -22
  170. package/src/useEditorToolbarPresentation.tsx +81 -58
  171. package/src/useEditorToolbarState.tsx +8 -6
  172. package/src/useFocusPreservingFrames.ts +43 -15
  173. package/src/useNativeEditor.ts +111 -51
  174. package/src/useRichTextEditorCommands.tsx +127 -54
  175. package/src/useRichTextEditorEvents.tsx +144 -42
  176. package/src/useRichTextEditorMentions.tsx +118 -37
  177. package/src/useRichTextEditorPresentation.tsx +128 -114
  178. package/src/useRichTextEditorState.tsx +76 -40
  179. package/src/useRichTextEditorUpdates.tsx +207 -56
  180. package/src/useViewerAtoms.tsx +162 -66
@@ -27,21 +27,34 @@ export function normalizeNativeEditorV2Result<T>(
27
27
  raw: unknown,
28
28
  normalizeValue: (value: unknown) => T | null
29
29
  ): NativeEditorResult<T> | null {
30
- if (!isPlainRecord(raw)) return null;
30
+ if (!isPlainRecord(raw)) {
31
+ return null;
32
+ }
33
+
31
34
  const hasValue = raw.value !== null && raw.value !== undefined;
32
35
  const hasError = raw.error !== null && raw.error !== undefined;
33
- if (hasValue === hasError) return null;
36
+
37
+ if (hasValue === hasError) {
38
+ return null;
39
+ }
40
+
34
41
  if (hasError) {
35
42
  const error = normalizeNativeEditorV2Error({ error: raw.error });
43
+
36
44
  return error == null ? null : { ok: false, error };
37
45
  }
46
+
38
47
  const value = normalizeValue(raw.value);
48
+
39
49
  return value == null ? null : { ok: true, value };
40
50
  }
41
51
 
42
52
  /** Parse a JSON-string result value; anything else is a contract violation. */
43
53
  export function parseNativeEditorV2JsonValue(value: unknown): unknown | null {
44
- if (typeof value !== 'string' || value === '') return null;
54
+ if (typeof value !== 'string' || value === '') {
55
+ return null;
56
+ }
57
+
45
58
  try {
46
59
  return JSON.parse(value) as unknown;
47
60
  } catch {
@@ -54,11 +67,18 @@ export function parseNativeEditorV2JsonValue(value: unknown): unknown | null {
54
67
  * purpose: the frozen v2 contract moves bytes as bytes.
55
68
  */
56
69
  export function normalizeNativeEditorV2Bytes(value: unknown): Uint8Array | null {
57
- if (value instanceof Uint8Array) return value;
70
+ if (value instanceof Uint8Array) {
71
+ return value;
72
+ }
73
+
58
74
  if (ArrayBuffer.isView(value)) {
59
75
  return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
60
76
  }
61
- if (value instanceof ArrayBuffer) return new Uint8Array(value);
77
+
78
+ if (value instanceof ArrayBuffer) {
79
+ return new Uint8Array(value);
80
+ }
81
+
62
82
  return null;
63
83
  }
64
84
 
@@ -96,9 +116,11 @@ export function nativeEditorV2U32(value: unknown): number | null {
96
116
  /** Require one exact JavaScript/platform u32 value at the v2 boundary. */
97
117
  export function requireNativeEditorV2U32(value: unknown, field: string): number {
98
118
  const normalized = nativeEditorV2U32(value);
119
+
99
120
  if (normalized == null) {
100
121
  throw invalidV2RequestError(`NativeEditorBridge: invalid u32 ${field}`);
101
122
  }
123
+
102
124
  return normalized;
103
125
  }
104
126
 
@@ -106,7 +128,7 @@ export function optionalBoolean(value: unknown): boolean | null {
106
128
  return typeof value === 'boolean' ? value : null;
107
129
  }
108
130
 
109
- export const V2_DOCUMENT_STATES = ['LocalReady', 'AwaitRemote', 'RoomReady'] as const;
131
+ export const V2_DOCUMENT_STATES = [ 'LocalReady', 'AwaitRemote', 'RoomReady' ] as const;
110
132
 
111
133
  /**
112
134
  * Readiness of the engine document. `AwaitRemote` is a room document still
@@ -129,7 +151,7 @@ export const V2_TRANSPORT_STATES = [
129
151
  /** Raw transport lifecycle. `YjsTransportStatus` is the friendlier projection of it. */
130
152
  export type NativeEditorTransportState = (typeof V2_TRANSPORT_STATES)[number];
131
153
 
132
- export const V2_RENDER_STATES = ['Loading', 'Ready'] as const;
154
+ export const V2_RENDER_STATES = [ 'Loading', 'Ready' ] as const;
133
155
 
134
156
  /** Whether the engine has a render snapshot to draw. */
135
157
  export type NativeEditorRenderState = (typeof V2_RENDER_STATES)[number];
@@ -170,7 +192,11 @@ export function normalizeNativeEditorV2StateValue(
170
192
  value: unknown
171
193
  ): NativeEditorState | null {
172
194
  const parsed = typeof value === 'string' ? parseNativeEditorV2JsonValue(value) : value;
173
- if (!isPlainRecord(parsed)) return null;
195
+
196
+ if (!isPlainRecord(parsed)) {
197
+ return null;
198
+ }
199
+
174
200
  const documentState = whitelisted(parsed.documentState, V2_DOCUMENT_STATES);
175
201
  const transportState = whitelisted(parsed.transportState, V2_TRANSPORT_STATES);
176
202
  const renderState = whitelisted(parsed.renderState, V2_RENDER_STATES);
@@ -179,6 +205,7 @@ export function normalizeNativeEditorV2StateValue(
179
205
  const stateRevision = normalizeRevisionField(parsed, 'stateRevision');
180
206
  const canUndo = optionalBoolean(parsed.canUndo);
181
207
  const canRedo = optionalBoolean(parsed.canRedo);
208
+
182
209
  if (
183
210
  documentState == null ||
184
211
  transportState == null ||
@@ -191,6 +218,7 @@ export function normalizeNativeEditorV2StateValue(
191
218
  ) {
192
219
  return null;
193
220
  }
221
+
194
222
  return {
195
223
  documentState,
196
224
  transportState,
@@ -219,22 +247,33 @@ export function normalizeNativeEditorV2MutationOutcomeValue(
219
247
  value: unknown
220
248
  ): NativeEditorMutationOutcome | null {
221
249
  const parsed = parseNativeEditorV2JsonValue(value);
222
- if (!isPlainRecord(parsed)) return null;
250
+
251
+ if (!isPlainRecord(parsed)) {
252
+ return null;
253
+ }
254
+
223
255
  if (parsed.type === 'notApplicable') {
224
256
  return { type: 'notApplicable' };
225
257
  }
258
+
226
259
  if (parsed.type === 'replacement') {
227
260
  const changed = optionalBoolean(parsed.changed);
228
261
  const documentRevision = normalizeRevisionField(parsed, 'documentRevision');
229
- if (changed == null || documentRevision == null) return null;
262
+
263
+ if (changed == null || documentRevision == null) {
264
+ return null;
265
+ }
266
+
230
267
  return { type: 'replacement', changed, documentRevision };
231
268
  }
269
+
232
270
  if (parsed.type === 'transaction') {
233
271
  const changed = optionalBoolean(parsed.changed);
234
272
  const documentRevision = normalizeRevisionField(parsed, 'documentRevision');
235
273
  const stateRevision = normalizeRevisionField(parsed, 'stateRevision');
236
274
  const canUndo = optionalBoolean(parsed.canUndo);
237
275
  const canRedo = optionalBoolean(parsed.canRedo);
276
+
238
277
  if (
239
278
  changed == null ||
240
279
  documentRevision == null ||
@@ -244,6 +283,7 @@ export function normalizeNativeEditorV2MutationOutcomeValue(
244
283
  ) {
245
284
  return null;
246
285
  }
286
+
247
287
  return {
248
288
  type: 'transaction',
249
289
  changed,
@@ -253,6 +293,7 @@ export function normalizeNativeEditorV2MutationOutcomeValue(
253
293
  canRedo,
254
294
  };
255
295
  }
296
+
256
297
  return null;
257
298
  }
258
299
 
@@ -265,22 +306,38 @@ export function normalizeNativeEditorV2CommitValue(
265
306
  value: unknown
266
307
  ): NativeEditorCommitInfo | null {
267
308
  const parsed = parseNativeEditorV2JsonValue(value);
268
- if (!isPlainRecord(parsed)) return null;
309
+
310
+ if (!isPlainRecord(parsed)) {
311
+ return null;
312
+ }
313
+
269
314
  const changed = optionalBoolean(parsed.changed);
270
315
  const documentRevision = normalizeRevisionField(parsed, 'documentRevision');
271
- if (changed == null || documentRevision == null) return null;
316
+
317
+ if (changed == null || documentRevision == null) {
318
+ return null;
319
+ }
320
+
272
321
  return { changed, documentRevision };
273
322
  }
274
323
 
275
324
  export function normalizeNativeEditorV2ChangedValue(value: unknown): boolean | null {
276
325
  const parsed = parseNativeEditorV2JsonValue(value);
277
- if (!isPlainRecord(parsed)) return null;
326
+
327
+ if (!isPlainRecord(parsed)) {
328
+ return null;
329
+ }
330
+
278
331
  return optionalBoolean(parsed.changed);
279
332
  }
280
333
 
281
334
  export function normalizeNativeEditorV2HtmlValue(value: unknown): string | null {
282
335
  const parsed = parseNativeEditorV2JsonValue(value);
283
- if (!isPlainRecord(parsed) || typeof parsed.html !== 'string') return null;
336
+
337
+ if (!isPlainRecord(parsed) || typeof parsed.html !== 'string') {
338
+ return null;
339
+ }
340
+
284
341
  return parsed.html;
285
342
  }
286
343
 
@@ -299,7 +356,8 @@ export function hasExactOwnKeys(
299
356
  expected: readonly string[]
300
357
  ): boolean {
301
358
  const actual = Object.keys(record).sort();
302
- const sortedExpected = [...expected].sort();
359
+ const sortedExpected = [ ...expected ].sort();
360
+
303
361
  return (
304
362
  actual.length === sortedExpected.length &&
305
363
  actual.every((key, index) => key === sortedExpected[index])
@@ -311,24 +369,37 @@ export function hasOnlyOwnKeys(
311
369
  allowed: readonly string[]
312
370
  ): boolean {
313
371
  const allowedKeys = new Set(allowed);
314
- return Object.keys(record).every((key) => allowedKeys.has(key));
372
+
373
+ return Object.keys(record).every(key => allowedKeys.has(key));
315
374
  }
316
375
 
317
376
  export function booleanRecord(value: unknown): value is Record<string, boolean> {
318
377
  return (
319
- isPlainRecord(value) && Object.values(value).every((entry) => typeof entry === 'boolean')
378
+ isPlainRecord(value) && Object.values(value).every(entry => typeof entry === 'boolean')
320
379
  );
321
380
  }
322
381
 
323
382
  export function stringArray(value: unknown): value is string[] {
324
- return Array.isArray(value) && value.every((entry) => typeof entry === 'string');
383
+ return Array.isArray(value) && value.every(entry => typeof entry === 'string');
325
384
  }
326
385
 
327
386
  export function validJsonValue(value: unknown): boolean {
328
- if (value === null) return true;
329
- if (typeof value === 'string' || typeof value === 'boolean') return true;
330
- if (typeof value === 'number') return Number.isFinite(value);
331
- if (Array.isArray(value)) return value.every(validJsonValue);
387
+ if (value === null) {
388
+ return true;
389
+ }
390
+
391
+ if (typeof value === 'string' || typeof value === 'boolean') {
392
+ return true;
393
+ }
394
+
395
+ if (typeof value === 'number') {
396
+ return Number.isFinite(value);
397
+ }
398
+
399
+ if (Array.isArray(value)) {
400
+ return value.every(validJsonValue);
401
+ }
402
+
332
403
  return isPlainRecord(value) && Object.values(value).every(validJsonValue);
333
404
  }
334
405
 
@@ -343,31 +414,56 @@ export function validRenderMark(value: unknown): value is RenderMark {
343
414
 
344
415
  export function normalizeNativeEditorV2PeersValue(value: unknown): NativeEditorPeerInfo[] | null {
345
416
  const parsed = typeof value === 'string' ? parseNativeEditorV2JsonValue(value) : value;
346
- if (!isPlainRecord(parsed) || !Array.isArray(parsed.peers)) return null;
417
+
418
+ if (!isPlainRecord(parsed) || !Array.isArray(parsed.peers)) {
419
+ return null;
420
+ }
421
+
347
422
  const peers: NativeEditorPeerInfo[] = [];
423
+
348
424
  for (const rawPeer of parsed.peers) {
349
- if (!isPlainRecord(rawPeer)) return null;
425
+ if (!isPlainRecord(rawPeer)) {
426
+ return null;
427
+ }
428
+
350
429
  const clientId = normalizeNativeEditorV2DecimalId(rawPeer.clientId);
351
430
  const clock = nativeEditorV2U32(rawPeer.clock);
352
431
  const isLocal = optionalBoolean(rawPeer.isLocal);
353
- if (clientId == null || clock == null || isLocal == null) return null;
354
- if (rawPeer.state !== null && !isPlainRecord(rawPeer.state)) return null;
432
+
433
+ if (clientId == null || clock == null || isLocal == null) {
434
+ return null;
435
+ }
436
+
437
+ if (rawPeer.state !== null && !isPlainRecord(rawPeer.state)) {
438
+ return null;
439
+ }
440
+
355
441
  let cursor: NativeEditorPeerInfo['cursor'] = null;
442
+
356
443
  if (rawPeer.cursor !== null && rawPeer.cursor !== undefined) {
357
- if (!isPlainRecord(rawPeer.cursor)) return null;
444
+ if (!isPlainRecord(rawPeer.cursor)) {
445
+ return null;
446
+ }
447
+
358
448
  const anchor = nativeEditorV2U32(rawPeer.cursor.anchor);
359
449
  const head = nativeEditorV2U32(rawPeer.cursor.head);
360
- if (anchor == null || head == null) return null;
450
+
451
+ if (anchor == null || head == null) {
452
+ return null;
453
+ }
454
+
361
455
  cursor = { anchor, head };
362
456
  }
457
+
363
458
  peers.push({
364
459
  clientId,
365
460
  clock,
366
461
  isLocal,
367
- state: (rawPeer.state as Record<string, unknown> | null) ?? null,
462
+ state: (rawPeer.state) ?? null,
368
463
  cursor,
369
464
  });
370
465
  }
466
+
371
467
  return peers;
372
468
  }
373
469
 
@@ -376,12 +472,17 @@ export const REJECTED_V2_RECORD_PREVIEW_CHARS = 4_000;
376
472
  /** Dev-only preview of a rejected boundary record, bounded so a large document never floods the log. */
377
473
  export function describeRejectedV2Record(raw: unknown): string {
378
474
  let serialized: string;
475
+
379
476
  try {
380
477
  serialized = JSON.stringify(raw);
381
478
  } catch {
382
479
  return `<unserializable ${typeof raw}>`;
383
480
  }
384
- if (serialized === undefined) return `<${typeof raw}>`;
481
+
482
+ if (serialized === undefined) {
483
+ return `<${typeof raw}>`;
484
+ }
485
+
385
486
  return serialized.length > REJECTED_V2_RECORD_PREVIEW_CHARS
386
487
  ? `${serialized.slice(0, REJECTED_V2_RECORD_PREVIEW_CHARS)}… (${serialized.length} chars)`
387
488
  : serialized;
@@ -437,33 +538,43 @@ export function unwrapNativeEditorV2Result<T>(
437
538
  normalizeValue: (value: unknown) => T | null
438
539
  ): T {
439
540
  const result = normalizeNativeEditorV2Result(raw, normalizeValue);
541
+
440
542
  if (result == null) {
441
543
  // The thrown error cannot carry the payload, so name the rejected
442
- // record here otherwise the failure is unattributable in the app.
544
+ // record here : otherwise the failure is unattributable in the app.
443
545
  if (__DEV__) {
444
546
  console.error(
445
547
  'NativeEditorBridge: native module returned a record this boundary rejected',
446
548
  describeRejectedV2Record(raw)
447
549
  );
448
550
  }
551
+
449
552
  throw invalidV2ResultError();
450
553
  }
451
- if (!result.ok) throw nativeEditorV2ErrorToException(result.error);
554
+
555
+ if (!result.ok) {
556
+ throw nativeEditorV2ErrorToException(result.error);
557
+ }
558
+
452
559
  return result.value;
453
560
  }
454
561
 
455
562
  export function requireV2DecimalId(value: string, field: string): string {
456
563
  const normalized = normalizeNativeEditorV2DecimalId(value);
564
+
457
565
  if (normalized == null) {
458
566
  throw invalidV2RequestError(`NativeEditorBridge: invalid ${field} for v2 request`);
459
567
  }
568
+
460
569
  return normalized;
461
570
  }
462
571
 
463
572
  export function requireV2Bytes(value: unknown, field: string): Uint8Array {
464
573
  const normalized = normalizeNativeEditorV2Bytes(value);
574
+
465
575
  if (normalized == null) {
466
576
  throw invalidV2RequestError(`NativeEditorBridge: invalid ${field} for v2 request`);
467
577
  }
578
+
468
579
  return normalized;
469
580
  }
@@ -43,7 +43,8 @@ export class NativeEditorLocalAwarenessSelectionValue {
43
43
  constructor(
44
44
  readonly anchor: number,
45
45
  readonly head: number
46
- ) {}
46
+ ) {
47
+ }
47
48
  }
48
49
 
49
50
  export type NativeEditorLocalAwarenessSelection = NativeEditorLocalAwarenessSelectionValue;
@@ -61,11 +62,11 @@ export interface NativeEditorLocalAwarenessIntent {
61
62
  * How this intent treats the local cursor. Rust owns it as a sticky
62
63
  * index, so the three cases are deliberately distinct:
63
64
  *
64
- * - **omitted** retain the cursor already published. Use this for
65
+ * - **omitted** : retain the cursor already published. Use this for
65
66
  * focus-only or state-only updates: restating a document position
66
67
  * that the document has since invalidated would be refused.
67
- * - **`null`** publish presence with no cursor at all.
68
- * - **a factory selection** set the cursor to these positions, which
68
+ * - **`null`** : publish presence with no cursor at all.
69
+ * - **a factory selection** : set the cursor to these positions, which
69
70
  * must resolve against the current document.
70
71
  */
71
72
  selection?: NativeEditorLocalAwarenessSelection | null;
@@ -79,7 +80,7 @@ export interface ListContext {
79
80
  index: number;
80
81
  /** Number of sibling items in the enclosing list. */
81
82
  total: number;
82
- /** The list's `start` attribute the number the first item takes. */
83
+ /** The list's `start` attribute : the number the first item takes. */
83
84
  start: number;
84
85
  isFirst: boolean;
85
86
  isLast: boolean;
@@ -133,7 +134,7 @@ export interface RenderBlocksPatch {
133
134
  renderBlocks: RenderElement[][];
134
135
  }
135
136
 
136
- /** What the current selection can do the state a toolbar renders from. */
137
+ /** What the current selection can do : the state a toolbar renders from. */
137
138
  export interface ActiveState {
138
139
  /** Marks active at the selection, keyed by mark name. */
139
140
  marks: Record<string, boolean>;
@@ -260,12 +261,12 @@ export interface NativeEditorRoomSnapshot {
260
261
  }
261
262
 
262
263
  /**
263
- * How a document handle's content starts out. Fixed at creation there is no
264
+ * How a document handle's content starts out. Fixed at creation : there is no
264
265
  * prop equivalent.
265
266
  *
266
- * - `localEmpty` the schema's empty document.
267
- * - `localJson` / `localHtml` seeded from the given content.
268
- * - `room` a collaborative document. It renders nothing until the server's
267
+ * - `localEmpty` : the schema's empty document.
268
+ * - `localJson` / `localHtml` : seeded from the given content.
269
+ * - `room` : a collaborative document. It renders nothing until the server's
269
270
  * document arrives, unless a `snapshot` seeds it offline.
270
271
  */
271
272
  export type NativeEditorInitialization =
@@ -336,7 +337,7 @@ export type NativeEditorPositionAffinity = 'before' | 'after';
336
337
 
337
338
  /**
338
339
  * Mirrors the Rust `PositionEnvelope`. `offset` is measured in the currency
339
- * named by `kind` scalar offsets are Unicode scalars, not document positions.
340
+ * named by `kind` : scalar offsets are Unicode scalars, not document positions.
340
341
  */
341
342
  export interface NativeEditorPositionEnvelope {
342
343
  offset: number;
@@ -6,8 +6,17 @@ const V2_U64_MAX = '18446744073709551615';
6
6
  * lexical after syntax validation, so JavaScript never coerces the value.
7
7
  */
8
8
  export function normalizeNativeEditorV2U64(value: unknown): string | null {
9
- if (typeof value !== 'string' || !CANONICAL_V2_U64.test(value)) return null;
10
- if (value.length < V2_U64_MAX.length) return value;
11
- if (value.length > V2_U64_MAX.length || value > V2_U64_MAX) return null;
9
+ if (typeof value !== 'string' || !CANONICAL_V2_U64.test(value)) {
10
+ return null;
11
+ }
12
+
13
+ if (value.length < V2_U64_MAX.length) {
14
+ return value;
15
+ }
16
+
17
+ if (value.length > V2_U64_MAX.length || value > V2_U64_MAX) {
18
+ return null;
19
+ }
20
+
12
21
  return value;
13
22
  }
@@ -135,12 +135,14 @@ const serializedJsonCache = new WeakMap<object, string>();
135
135
 
136
136
  function stringifyCachedJson(value: DocumentJSON): string {
137
137
  const cached = serializedJsonCache.get(value);
138
+
138
139
  if (cached != null) {
139
140
  return cached;
140
141
  }
141
142
 
142
143
  const serialized = JSON.stringify(value);
143
144
  serializedJsonCache.set(value, serialized);
145
+
144
146
  return serialized;
145
147
  }
146
148
 
@@ -160,11 +162,11 @@ function resolveViewerConfiguration(
160
162
  ...(resourceLimits ? { limits: { resource: resourceLimits } } : {}),
161
163
  ...(mentions?.trigger || mentions?.prefix
162
164
  ? {
163
- mentions: {
164
- ...(mentions.trigger ? { trigger: mentions.trigger } : {}),
165
- ...(mentions.prefix ? { prefix: mentions.prefix } : {}),
166
- },
167
- }
165
+ mentions: {
166
+ ...(mentions.trigger ? { trigger: mentions.trigger } : {}),
167
+ ...(mentions.prefix ? { prefix: mentions.prefix } : {}),
168
+ },
169
+ }
168
170
  : {}),
169
171
  };
170
172
  }
@@ -209,11 +211,14 @@ export function RichTextViewer(props: RichTextViewerProps) {
209
211
  onError,
210
212
  ...viewProps
211
213
  } = props;
212
- const { mentions, codeHighlighting } = useMemo(() => normalizeEditorAddons(addons), [addons]);
214
+
215
+ const { mentions, codeHighlighting } = useMemo(() => normalizeEditorAddons(addons), [ addons ]);
216
+
213
217
  const resolvedResourceLimits = useMemo(
214
218
  () => (resourceLimits ? resolveEditorResourceLimits(resourceLimits) : undefined),
215
- [resourceLimits]
219
+ [ resourceLimits ]
216
220
  );
221
+
217
222
  const configJson = useMemo(
218
223
  () =>
219
224
  serializePreparedProseViewerConfiguration(
@@ -226,25 +231,38 @@ export function RichTextViewer(props: RichTextViewerProps) {
226
231
  codeHighlighting
227
232
  )
228
233
  ),
229
- [allowBase64Images, mentions, resolvedResourceLimits, schema, atoms, codeHighlighting]
234
+ [ allowBase64Images,
235
+ mentions,
236
+ resolvedResourceLimits,
237
+ schema,
238
+ atoms,
239
+ codeHighlighting ]
230
240
  );
241
+
231
242
  const themeJson = useMemo(
232
243
  () => serializeEditorTheme(theme, mentions?.theme),
233
- [mentions?.theme, theme]
244
+ [ mentions?.theme, theme ]
234
245
  );
246
+
235
247
  const imagePolicyJson = useMemo(
236
248
  () => serializeEditorImageLoadingPolicy(imageLoadingPolicy),
237
- [imageLoadingPolicy]
249
+ [ imageLoadingPolicy ]
238
250
  );
251
+
239
252
  const sourceKind = contentJSON === undefined ? 'html' : 'json';
253
+
240
254
  const source = useMemo(() => {
241
255
  if (contentJSON === undefined) {
242
256
  return contentHTML ?? '';
243
257
  }
258
+
244
259
  return typeof contentJSON === 'string' ? contentJSON : stringifyCachedJson(contentJSON);
245
- }, [contentHTML, contentJSON]);
260
+ }, [ contentHTML, contentJSON ]);
261
+
246
262
  const atomIdentity = useMemo(
247
- () => ({}),
263
+ () => ({
264
+ sourceKind, source, configJson, themeJson, imagePolicyJson, renderImages, collapseTrailingEmptyParagraphs, fontEnvironmentRevision,
265
+ }),
248
266
  [
249
267
  sourceKind,
250
268
  source,
@@ -256,6 +274,7 @@ export function RichTextViewer(props: RichTextViewerProps) {
256
274
  fontEnvironmentRevision,
257
275
  ]
258
276
  );
277
+
259
278
  const viewerAtoms = useViewerAtoms({
260
279
  atoms,
261
280
  identity: atomIdentity,
@@ -266,21 +285,25 @@ export function RichTextViewer(props: RichTextViewerProps) {
266
285
  onUpdateAtomAttrs,
267
286
  onError,
268
287
  });
288
+
269
289
  const handlePressLink = useCallback(
270
290
  (event: NativeSyntheticEvent<RichTextViewerLinkPressNativeEvent>) => {
271
291
  onPressLink?.(event.nativeEvent);
272
292
  },
273
- [onPressLink]
293
+ [ onPressLink ]
274
294
  );
295
+
275
296
  const handlePressMention = useCallback(
276
297
  (event: NativeSyntheticEvent<RichTextViewerMentionPressNativeEvent>) => {
277
298
  const { docPos, label, attrsJson } = event.nativeEvent;
278
299
  let attrs: unknown;
300
+
279
301
  try {
280
302
  attrs = JSON.parse(attrsJson);
281
303
  } catch {
282
304
  attrs = null;
283
305
  }
306
+
284
307
  if (attrs === null || typeof attrs !== 'object' || Array.isArray(attrs)) {
285
308
  onError?.({
286
309
  domain: 'viewer',
@@ -288,17 +311,20 @@ export function RichTextViewer(props: RichTextViewerProps) {
288
311
  message: 'The prepared mention attributes are not a JSON object.',
289
312
  fatal: false,
290
313
  });
314
+
291
315
  return;
292
316
  }
317
+
293
318
  mentions?.onPress?.({ docPos, label, attrs: attrs as Record<string, unknown> });
294
319
  },
295
- [mentions, onError]
320
+ [ mentions, onError ]
296
321
  );
322
+
297
323
  const handleError = useCallback(
298
324
  (event: NativeSyntheticEvent<RichTextViewerErrorEvent>) => {
299
325
  onError?.(event.nativeEvent);
300
326
  },
301
- [onError]
327
+ [ onError ]
302
328
  );
303
329
 
304
330
  const nativeViewer = (
@@ -320,7 +346,11 @@ export function RichTextViewer(props: RichTextViewerProps) {
320
346
  onError={onError ? handleError : undefined}
321
347
  />
322
348
  );
323
- if (!viewerAtoms.enabled) return nativeViewer;
349
+
350
+ if (!viewerAtoms.enabled) {
351
+ return nativeViewer;
352
+ }
353
+
324
354
  return (
325
355
  <View {...viewProps}>
326
356
  <View style={{ alignSelf: 'stretch' }} onLayout={viewerAtoms.onContainerLayout}>