@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
@@ -1,5 +1,5 @@
1
1
  import {
2
- NativeEditorErrorBase,
2
+ type NativeEditorErrorBase,
3
3
  NativeEditorNonRetryableError,
4
4
  nativeEditorV2ErrorToException,
5
5
  normalizeNativeEditorV2Error,
@@ -69,12 +69,18 @@ import {
69
69
  */
70
70
  export class NativeEditorDocumentBridge {
71
71
  private readonly _editorId: string;
72
+
72
73
  private _destroyed = false;
74
+
73
75
  /** @internal Reset intent waiting for the interactive view handoff. */
74
76
  _pendingViewReset: { json: string } | null = null;
77
+
75
78
  private _nextRequestId = 0n;
79
+
76
80
  private readonly _errorListeners = new Set<(error: NativeEditorErrorBase) => void>();
81
+
77
82
  private _collaborationProtocolAdapter: NativeCollaborationProtocolAdapter | null = null;
83
+
78
84
  private _collaborationProtocolAdapterSubscription: { remove(): void } | null = null;
79
85
 
80
86
  /** @internal Created by createNativeEditorDocumentHandle. */
@@ -91,15 +97,21 @@ export class NativeEditorDocumentBridge {
91
97
  }
92
98
 
93
99
  private assertAlive(): void {
94
- if (this._destroyed) throw destroyedHandleError();
100
+ if (this._destroyed) {
101
+ throw destroyedHandleError();
102
+ }
95
103
  }
96
104
 
97
105
  private callV2<T>(invoke: () => unknown, normalizeValue: (value: unknown) => T | null): T {
98
106
  this.assertAlive();
99
107
  const raw = invoke();
108
+
100
109
  // A re-entrant destroy racing the native call makes any result
101
110
  // arriving now a result for a destroyed handle: non-retryable.
102
- if (this._destroyed) throw destroyedHandleError();
111
+ if (this._destroyed) {
112
+ throw destroyedHandleError();
113
+ }
114
+
103
115
  return unwrapNativeEditorV2Result(raw, normalizeValue);
104
116
  }
105
117
 
@@ -107,7 +119,9 @@ export class NativeEditorDocumentBridge {
107
119
  if (this._nextRequestId >= 18_446_744_073_709_551_615n) {
108
120
  throw invalidV2RequestError('NativeEditorBridge: v2 request id exhausted');
109
121
  }
122
+
110
123
  this._nextRequestId += 1n;
124
+
111
125
  return this._nextRequestId.toString();
112
126
  }
113
127
 
@@ -122,19 +136,28 @@ export class NativeEditorDocumentBridge {
122
136
  `"version":${V2_ENVELOPE_VERSION}`,
123
137
  `"requestId":"${this.nextRequestId()}"`,
124
138
  ];
139
+
125
140
  if (baseDocumentRevision !== undefined) {
126
141
  const digits = requireV2DecimalId(baseDocumentRevision, 'baseDocumentRevision');
127
142
  parts.push(`"baseDocumentRevision":"${digits}"`);
128
143
  }
144
+
129
145
  const payloadJson = JSON.stringify(payload);
130
146
  const inner = payloadJson.slice(1, payloadJson.length - 1);
131
- if (inner.length > 0) parts.push(inner);
147
+
148
+ if (inner.length > 0) {
149
+ parts.push(inner);
150
+ }
151
+
132
152
  return `{${parts.join(',')}}`;
133
153
  }
134
154
 
135
155
  /** Destroy the session. Repeated destroy is safe. */
136
156
  destroy(): void {
137
- if (this._destroyed) return;
157
+ if (this._destroyed) {
158
+ return;
159
+ }
160
+
138
161
  try {
139
162
  unwrapNativeEditorV2Result(
140
163
  invokeNativeEditorV2('editorV2Destroy', this._editorId),
@@ -153,6 +176,7 @@ export class NativeEditorDocumentBridge {
153
176
  throw error;
154
177
  }
155
178
  }
179
+
156
180
  this._destroyed = true;
157
181
  this._collaborationProtocolAdapter = null;
158
182
  this._collaborationProtocolAdapterSubscription?.remove();
@@ -163,6 +187,7 @@ export class NativeEditorDocumentBridge {
163
187
  /** Subscribe to autonomous native failures; returns the unsubscribe. */
164
188
  addErrorListener(listener: (error: NativeEditorErrorBase) => void): () => void {
165
189
  this._errorListeners.add(listener);
190
+
166
191
  return () => {
167
192
  this._errorListeners.delete(listener);
168
193
  };
@@ -175,13 +200,18 @@ export class NativeEditorDocumentBridge {
175
200
  * contract violation so the view stays usable.
176
201
  */
177
202
  _emitAutonomousError(raw: unknown): void {
178
- if (this._destroyed) return;
203
+ if (this._destroyed) {
204
+ return;
205
+ }
206
+
179
207
  const candidate = isPlainRecord(raw) && 'error' in raw ? raw : { error: raw };
180
208
  const normalized = normalizeNativeEditorV2Error(candidate);
209
+
181
210
  const exception =
182
211
  normalized == null
183
212
  ? invalidV2ResultError()
184
213
  : nativeEditorV2ErrorToException(normalized);
214
+
185
215
  for (const listener of this._errorListeners) {
186
216
  listener(exception);
187
217
  }
@@ -228,13 +258,21 @@ export class NativeEditorDocumentBridge {
228
258
  this.assertAlive();
229
259
  const mirrorAnchor = mirrorScalarSelection?.anchor ?? null;
230
260
  const mirrorHead = mirrorScalarSelection?.head ?? null;
261
+
231
262
  if ((mirrorAnchor == null) !== (mirrorHead == null)) {
232
263
  throw invalidV2RequestError(
233
264
  'NativeEditorBridge: render update mirror requires both scalar anchor and head'
234
265
  );
235
266
  }
236
- if (mirrorAnchor != null) requireNativeEditorV2U32(mirrorAnchor, 'mirrorScalarAnchor');
237
- if (mirrorHead != null) requireNativeEditorV2U32(mirrorHead, 'mirrorScalarHead');
267
+
268
+ if (mirrorAnchor != null) {
269
+ requireNativeEditorV2U32(mirrorAnchor, 'mirrorScalarAnchor');
270
+ }
271
+
272
+ if (mirrorHead != null) {
273
+ requireNativeEditorV2U32(mirrorHead, 'mirrorScalarHead');
274
+ }
275
+
238
276
  return this.callV2(
239
277
  () =>
240
278
  invokeNativeEditorV2(
@@ -250,14 +288,23 @@ export class NativeEditorDocumentBridge {
250
288
  replaceDocument(request: NativeEditorReplaceDocumentRequest): NativeEditorCommitInfo {
251
289
  this.assertAlive();
252
290
  const payload: Record<string, unknown> = {};
253
- if (request.setJson !== undefined) payload.setJson = request.setJson;
254
- if (request.setHtml !== undefined) payload.setHtml = request.setHtml;
291
+
292
+ if (request.setJson !== undefined) {
293
+ payload.setJson = request.setJson;
294
+ }
295
+
296
+ if (request.setHtml !== undefined) {
297
+ payload.setHtml = request.setHtml;
298
+ }
299
+
255
300
  payload.history = request.history;
256
301
  const requestJson = this.buildEnvelopeJson(payload);
302
+
257
303
  const result = this.callV2(
258
304
  () => invokeNativeEditorV2('editorV2ReplaceDocument', this._editorId, requestJson),
259
305
  normalizeNativeEditorV2CommitValue
260
306
  );
307
+
261
308
  if (request.history === 'resetAndClear' && 'documentRevision' in result) {
262
309
  this._pendingViewReset = {
263
310
  json: JSON.stringify({ ...payload, documentRevision: result.documentRevision }),
@@ -265,15 +312,18 @@ export class NativeEditorDocumentBridge {
265
312
  } else {
266
313
  this._pendingViewReset = null;
267
314
  }
315
+
268
316
  return result;
269
317
  }
270
318
 
271
319
  applyInput(request: NativeEditorInputRequest): NativeEditorMutationOutcome {
272
320
  this.assertAlive();
321
+
273
322
  const requestJson = this.buildEnvelopeJson(
274
323
  { text: request.text },
275
324
  request.baseDocumentRevision
276
325
  );
326
+
277
327
  return this.callV2(
278
328
  () => invokeNativeEditorV2('editorV2ApplyInput', this._editorId, requestJson),
279
329
  normalizeNativeEditorV2MutationOutcomeValue
@@ -282,10 +332,12 @@ export class NativeEditorDocumentBridge {
282
332
 
283
333
  applyCommand(request: NativeEditorCommandRequest): NativeEditorMutationOutcome {
284
334
  this.assertAlive();
335
+
285
336
  const requestJson = this.buildEnvelopeJson(
286
337
  { command: request.command },
287
338
  request.baseDocumentRevision
288
339
  );
340
+
289
341
  return this.callV2(
290
342
  () => invokeNativeEditorV2('editorV2ApplyCommand', this._editorId, requestJson),
291
343
  normalizeNativeEditorV2MutationOutcomeValue
@@ -295,14 +347,23 @@ export class NativeEditorDocumentBridge {
295
347
  applyLocalApi(request: NativeEditorLocalApiRequest): NativeEditorMutationOutcome {
296
348
  this.assertAlive();
297
349
  const payload: Record<string, unknown> = {};
298
- if (request.setJson !== undefined) payload.setJson = request.setJson;
299
- if (request.setHtml !== undefined) payload.setHtml = request.setHtml;
350
+
351
+ if (request.setJson !== undefined) {
352
+ payload.setJson = request.setJson;
353
+ }
354
+
355
+ if (request.setHtml !== undefined) {
356
+ payload.setHtml = request.setHtml;
357
+ }
358
+
300
359
  payload.history = request.history;
301
360
  const requestJson = this.buildEnvelopeJson(payload, request.baseDocumentRevision);
361
+
302
362
  const result = this.callV2(
303
363
  () => invokeNativeEditorV2('editorV2ApplyLocalApi', this._editorId, requestJson),
304
364
  normalizeNativeEditorV2MutationOutcomeValue
305
365
  );
366
+
306
367
  if (request.history === 'resetAndClear' && 'documentRevision' in result) {
307
368
  this._pendingViewReset = {
308
369
  json: JSON.stringify({ ...payload, documentRevision: result.documentRevision }),
@@ -310,15 +371,18 @@ export class NativeEditorDocumentBridge {
310
371
  } else {
311
372
  this._pendingViewReset = null;
312
373
  }
374
+
313
375
  return result;
314
376
  }
315
377
 
316
378
  setSelection(request: NativeEditorSelectionRequest): NativeEditorMutationOutcome {
317
379
  this.assertAlive();
380
+
318
381
  const requestJson = this.buildEnvelopeJson(
319
382
  { selection: request.selection },
320
383
  request.baseDocumentRevision
321
384
  );
385
+
322
386
  return this.callV2(
323
387
  () => invokeNativeEditorV2('editorV2SetSelection', this._editorId, requestJson),
324
388
  normalizeNativeEditorV2MutationOutcomeValue
@@ -328,6 +392,7 @@ export class NativeEditorDocumentBridge {
328
392
  undo(): boolean {
329
393
  this.assertAlive();
330
394
  const requestJson = this.buildEnvelopeJson({});
395
+
331
396
  return this.callV2(
332
397
  () => invokeNativeEditorV2('editorV2Undo', this._editorId, requestJson),
333
398
  normalizeNativeEditorV2ChangedValue
@@ -337,6 +402,7 @@ export class NativeEditorDocumentBridge {
337
402
  redo(): boolean {
338
403
  this.assertAlive();
339
404
  const requestJson = this.buildEnvelopeJson({});
405
+
340
406
  return this.callV2(
341
407
  () => invokeNativeEditorV2('editorV2Redo', this._editorId, requestJson),
342
408
  normalizeNativeEditorV2ChangedValue
@@ -356,6 +422,7 @@ export class NativeEditorDocumentBridge {
356
422
  ): NativeEditorCommitInfo {
357
423
  this.assertAlive();
358
424
  const bytes = requireV2Bytes(encodedState, 'snapshot encodedState');
425
+
359
426
  return this.callV2(
360
427
  () =>
361
428
  invokeNativeEditorV2(
@@ -373,13 +440,16 @@ export class NativeEditorDocumentBridge {
373
440
  const wireConfig = config === null ? null : collaborationTransportWireConfig(config);
374
441
  const previousAdapter = this._collaborationProtocolAdapter;
375
442
  const nextAdapter = config?.protocolAdapter ?? null;
443
+
376
444
  if (nextAdapter !== null && this._collaborationProtocolAdapterSubscription === null) {
377
445
  this._collaborationProtocolAdapterSubscription = getNativeModule().addListener(
378
446
  'onCollaborationTransportEvent',
379
- (rawEvent) => this.handleCollaborationProtocolAdapterEvent(rawEvent)
447
+ rawEvent => this.handleCollaborationProtocolAdapterEvent(rawEvent)
380
448
  );
381
449
  }
450
+
382
451
  this._collaborationProtocolAdapter = nextAdapter;
452
+
383
453
  try {
384
454
  this.callV2(
385
455
  () =>
@@ -392,6 +462,7 @@ export class NativeEditorDocumentBridge {
392
462
  );
393
463
  } catch (error) {
394
464
  this._collaborationProtocolAdapter = previousAdapter;
465
+
395
466
  if (
396
467
  previousAdapter === null &&
397
468
  this._collaborationProtocolAdapterSubscription !== null
@@ -399,8 +470,10 @@ export class NativeEditorDocumentBridge {
399
470
  this._collaborationProtocolAdapterSubscription.remove();
400
471
  this._collaborationProtocolAdapterSubscription = null;
401
472
  }
473
+
402
474
  throw error;
403
475
  }
476
+
404
477
  if (nextAdapter === null && this._collaborationProtocolAdapterSubscription !== null) {
405
478
  this._collaborationProtocolAdapterSubscription.remove();
406
479
  this._collaborationProtocolAdapterSubscription = null;
@@ -408,27 +481,38 @@ export class NativeEditorDocumentBridge {
408
481
  }
409
482
 
410
483
  private handleCollaborationProtocolAdapterEvent(rawEvent: unknown): void {
411
- if (this._destroyed || this._collaborationProtocolAdapter === null) return;
484
+ if (this._destroyed || this._collaborationProtocolAdapter === null) {
485
+ return;
486
+ }
487
+
412
488
  const event = normalizeNativeCollaborationTransportEvent(rawEvent);
489
+
413
490
  if (event?.editorId !== this._editorId || event.kind !== 'protocolAdapter') {
414
491
  return;
415
492
  }
493
+
416
494
  const adapter = this._collaborationProtocolAdapter;
495
+
417
496
  const context: NativeCollaborationProtocolAdapterContext = {
418
497
  attemptId: event.attemptId,
419
498
  generation: event.generation,
420
499
  negotiatedProtocol: event.negotiatedProtocol,
421
500
  };
501
+
422
502
  const callback =
423
503
  event.phase === 'open'
424
504
  ? () => adapter.onOpen(context)
425
505
  : () => adapter.onMessage(context, event.frame!);
506
+
426
507
  void Promise.resolve()
427
508
  .then(callback)
428
- .then((result) => serializeCollaborationProtocolAdapterResult(result))
509
+ .then(result => serializeCollaborationProtocolAdapterResult(result))
429
510
  .catch(() => '{"action":"reject"}')
430
- .then((responseJson) => {
431
- if (this._destroyed) return;
511
+ .then(responseJson => {
512
+ if (this._destroyed) {
513
+ return;
514
+ }
515
+
432
516
  try {
433
517
  this.callV2(
434
518
  () =>
@@ -451,10 +535,12 @@ export class NativeEditorDocumentBridge {
451
535
 
452
536
  setLocalAwareness(intent: NativeEditorLocalAwarenessIntent | null): void {
453
537
  this.assertAlive();
538
+
454
539
  const awarenessJson =
455
540
  intent === null
456
541
  ? 'null'
457
542
  : serializeLocalAwarenessIntent(validateLocalAwarenessIntent(intent));
543
+
458
544
  this.callV2(
459
545
  () =>
460
546
  invokeNativeEditorV2(
@@ -470,16 +556,22 @@ export class NativeEditorDocumentBridge {
470
556
  listener: (event: NativeCollaborationTransportEvent) => void
471
557
  ): () => void {
472
558
  this.assertAlive();
559
+
473
560
  const subscription = getNativeModule().addListener(
474
561
  'onCollaborationTransportEvent',
475
- (rawEvent) => {
476
- if (this._destroyed) return;
562
+ rawEvent => {
563
+ if (this._destroyed) {
564
+ return;
565
+ }
566
+
477
567
  const event = normalizeNativeCollaborationTransportEvent(rawEvent);
568
+
478
569
  if (event?.editorId === this._editorId) {
479
570
  listener(event);
480
571
  }
481
572
  }
482
573
  );
574
+
483
575
  return () => subscription.remove();
484
576
  }
485
577
  }
@@ -1,5 +1,5 @@
1
1
  import { type ResolvedDocumentSchema } from './schemas';
2
- import { NativeEditorErrorBase } from './NativeEditorBoundaryError';
2
+ import { type NativeEditorErrorBase } from './NativeEditorBoundaryError';
3
3
  import { NativeEditorDocumentBridge } from './NativeEditorDocumentBridge';
4
4
  import {
5
5
  type NativeCollaborationTransportConfig,
@@ -34,8 +34,8 @@ export const NATIVE_EDITOR_DOCUMENT_HANDLE_DESCRIPTORS = new WeakMap<
34
34
  * One native document session, shared by everything that touches the
35
35
  * document: the editor view, the headless `useNativeEditorDocument` binding,
36
36
  * and the collaboration controller. Obtain one from
37
- * {@link createNativeEditorDocumentHandle} it cannot be constructed
38
- * directly and `destroy()` it when its owner unmounts.
37
+ * {@link createNativeEditorDocumentHandle} : it cannot be constructed
38
+ * directly : and `destroy()` it when its owner unmounts.
39
39
  */
40
40
  export interface NativeEditorDocumentHandle {
41
41
  readonly [NATIVE_EDITOR_DOCUMENT_HANDLE_BRAND]: true;
@@ -72,6 +72,7 @@ export class NativeEditorDocumentHandleImpl implements NativeEditorDocumentHandl
72
72
  'NativeEditorBridge: NativeEditorDocumentHandle cannot be constructed directly'
73
73
  );
74
74
  }
75
+
75
76
  AUTHENTIC_NATIVE_EDITOR_DOCUMENT_HANDLES.add(this);
76
77
  NATIVE_EDITOR_DOCUMENT_HANDLE_DESCRIPTORS.set(this, documentDescriptor);
77
78
  }
@@ -109,11 +110,13 @@ export function _getNativeEditorDocumentHandleDescriptor(
109
110
  ): ResolvedDocumentSchema {
110
111
  _assertNativeEditorDocumentHandle(handle);
111
112
  const documentDescriptor = NATIVE_EDITOR_DOCUMENT_HANDLE_DESCRIPTORS.get(handle);
113
+
112
114
  if (documentDescriptor === undefined) {
113
115
  throw invalidV2RequestError(
114
116
  'NativeEditorBridge: authentic NativeEditorDocumentHandle has no document descriptor'
115
117
  );
116
118
  }
119
+
117
120
  return documentDescriptor;
118
121
  }
119
122
 
@@ -134,7 +137,7 @@ export function _assertNativeEditorDocumentHandle(
134
137
 
135
138
  /**
136
139
  * Create the native document session every other API binds to. Create it once
137
- * per document `useMemo`, not on each render and destroy it when its
140
+ * per document : `useMemo`, not on each render : and destroy it when its
138
141
  * owner unmounts.
139
142
  *
140
143
  * @throws NativeEditorErrorBase when the config is rejected: a malformed
@@ -155,10 +158,12 @@ export function createNativeEditorDocumentHandle(
155
158
  config: NativeEditorCreateConfig
156
159
  ): NativeEditorDocumentHandle {
157
160
  const { configJson, snapshotState, documentDescriptor } = buildV2CreateRequest(config);
161
+
158
162
  const value = unwrapNativeEditorV2Result(
159
163
  invokeNativeEditorV2('editorV2Create', configJson, snapshotState),
160
164
  normalizeNativeEditorV2CreateValue
161
165
  );
166
+
162
167
  return new NativeEditorDocumentHandleImpl(
163
168
  NATIVE_EDITOR_DOCUMENT_HANDLE_TOKEN,
164
169
  value.editorId,
@@ -6,7 +6,7 @@ import {
6
6
  } from './NativeEditorTypes';
7
7
  import { normalizeV2JsonValue, serializeV2CreateEnvelope } from './NativeEditorCreateJson';
8
8
 
9
- export const LOCAL_AWARENESS_INTENT_KEYS = new Set(['state', 'focused', 'selection']);
9
+ export const LOCAL_AWARENESS_INTENT_KEYS = new Set([ 'state', 'focused', 'selection' ]);
10
10
 
11
11
  export const LOCAL_AWARENESS_SELECTION_VALUES = new WeakMap<
12
12
  object,
@@ -28,26 +28,37 @@ export function createNativeEditorLocalAwarenessSelection(
28
28
  ): NativeEditorLocalAwarenessSelection {
29
29
  const acceptedAnchor = nativeEditorV2U32(anchor);
30
30
  const acceptedHead = nativeEditorV2U32(head);
31
- if (acceptedAnchor == null || acceptedHead == null) invalidLocalAwarenessIntent();
31
+
32
+ if (acceptedAnchor == null || acceptedHead == null) {
33
+ invalidLocalAwarenessIntent();
34
+ }
32
35
 
33
36
  const selection: NativeEditorLocalAwarenessSelection =
34
37
  new NativeEditorLocalAwarenessSelectionValue(acceptedAnchor, acceptedHead);
38
+
35
39
  Object.freeze(selection);
36
40
  LOCAL_AWARENESS_SELECTION_VALUES.set(selection, selection);
41
+
37
42
  return selection;
38
43
  }
39
44
 
40
45
  export function isLocalAwarenessRecord(value: unknown): value is Record<string, unknown> {
41
- if (value == null || typeof value !== 'object' || Array.isArray(value)) return false;
46
+ if (value == null || typeof value !== 'object' || Array.isArray(value)) {
47
+ return false;
48
+ }
49
+
42
50
  const prototype = Object.getPrototypeOf(value);
51
+
43
52
  return prototype === Object.prototype || prototype === null;
44
53
  }
45
54
 
46
55
  export function localAwarenessOwnDataValue(record: Record<string, unknown>, key: string): unknown {
47
56
  const descriptor = Object.getOwnPropertyDescriptor(record, key);
57
+
48
58
  if (descriptor === undefined || !('value' in descriptor) || descriptor.enumerable !== true) {
49
59
  invalidLocalAwarenessIntent();
50
60
  }
61
+
51
62
  return descriptor.value;
52
63
  }
53
64
 
@@ -55,35 +66,58 @@ export function validateLocalAwarenessSelection(selection: unknown): {
55
66
  anchor: number;
56
67
  head: number;
57
68
  } {
58
- if (selection == null || typeof selection !== 'object') invalidLocalAwarenessIntent();
69
+ if (selection == null || typeof selection !== 'object') {
70
+ invalidLocalAwarenessIntent();
71
+ }
59
72
 
60
73
  // Do not inspect caller-held values before provenance succeeds: a Proxy
61
74
  // can imitate every structural and own-data check, but it cannot inherit
62
75
  // this module's WeakMap identity.
63
76
  const factoryValue = LOCAL_AWARENESS_SELECTION_VALUES.get(selection);
64
- if (factoryValue === undefined) invalidLocalAwarenessIntent();
77
+
78
+ if (factoryValue === undefined) {
79
+ invalidLocalAwarenessIntent();
80
+ }
65
81
 
66
82
  const anchor = nativeEditorV2U32(factoryValue.anchor);
67
83
  const head = nativeEditorV2U32(factoryValue.head);
84
+
68
85
  if (anchor == null || head == null) {
69
86
  invalidLocalAwarenessIntent();
70
87
  }
88
+
71
89
  return { anchor, head };
72
90
  }
73
91
 
74
92
  /** Reject caller-owned sticky cursor data before a native call can occur. */
75
93
  export function rejectReservedAwarenessCursor(value: unknown): void {
76
- const pending: unknown[] = [value];
94
+ const pending: unknown[] = [ value ];
77
95
  const seen = new WeakSet<object>();
96
+
78
97
  while (pending.length > 0) {
79
98
  const current = pending.pop();
80
- if (current == null || typeof current !== 'object') continue;
81
- if (seen.has(current)) continue;
99
+
100
+ if (current == null || typeof current !== 'object') {
101
+ continue;
102
+ }
103
+
104
+ if (seen.has(current)) {
105
+ continue;
106
+ }
107
+
82
108
  seen.add(current);
109
+
83
110
  for (const key of Reflect.ownKeys(current)) {
84
- if (key === 'cursor') invalidLocalAwarenessIntent('reserved cursor key is not allowed');
111
+ if (key === 'cursor') {
112
+ invalidLocalAwarenessIntent('reserved cursor key is not allowed');
113
+ }
114
+
85
115
  const descriptor = Object.getOwnPropertyDescriptor(current, key);
86
- if (descriptor == null || !('value' in descriptor)) invalidLocalAwarenessIntent();
116
+
117
+ if (descriptor == null || !('value' in descriptor)) {
118
+ invalidLocalAwarenessIntent();
119
+ }
120
+
87
121
  pending.push(descriptor.value);
88
122
  }
89
123
  }
@@ -95,13 +129,19 @@ export function normalizeLocalAwarenessState(value: unknown): Record<string, unk
95
129
  seen: new WeakSet<object>(),
96
130
  work: 0,
97
131
  });
132
+
98
133
  if (!isLocalAwarenessRecord(normalized) || Object.getPrototypeOf(normalized) !== null) {
99
134
  invalidLocalAwarenessIntent();
100
135
  }
136
+
101
137
  rejectReservedAwarenessCursor(normalized);
138
+
102
139
  return normalized;
103
140
  } catch (error) {
104
- if (error instanceof NativeEditorEngineBoundaryError) throw error;
141
+ if (error instanceof NativeEditorEngineBoundaryError) {
142
+ throw error;
143
+ }
144
+
105
145
  invalidLocalAwarenessIntent();
106
146
  }
107
147
  }
@@ -120,27 +160,40 @@ export function validateLocalAwarenessIntent(
120
160
  if (
121
161
  !isLocalAwarenessRecord(intent) ||
122
162
  Reflect.ownKeys(intent).some(
123
- (key) => typeof key !== 'string' || !LOCAL_AWARENESS_INTENT_KEYS.has(key)
163
+ key => typeof key !== 'string' || !LOCAL_AWARENESS_INTENT_KEYS.has(key)
124
164
  ) ||
125
165
  !Object.prototype.hasOwnProperty.call(intent, 'state') ||
126
166
  !Object.prototype.hasOwnProperty.call(intent, 'focused')
127
167
  ) {
128
168
  invalidLocalAwarenessIntent();
129
169
  }
170
+
130
171
  const state = normalizeLocalAwarenessState(localAwarenessOwnDataValue(intent, 'state'));
131
172
  const focused = localAwarenessOwnDataValue(intent, 'focused');
132
- if (typeof focused !== 'boolean') invalidLocalAwarenessIntent();
173
+
174
+ if (typeof focused !== 'boolean') {
175
+ invalidLocalAwarenessIntent();
176
+ }
133
177
 
134
178
  if (!Object.prototype.hasOwnProperty.call(intent, 'selection')) {
135
179
  // Absent: retain whatever cursor Rust already holds.
136
180
  return { state, focused };
137
181
  }
182
+
138
183
  const rawSelection = localAwarenessOwnDataValue(intent, 'selection');
139
- if (rawSelection === null) return { state, focused, selection: null };
184
+
185
+ if (rawSelection === null) {
186
+ return { state, focused, selection: null };
187
+ }
188
+
140
189
  const selection = validateLocalAwarenessSelection(rawSelection);
190
+
141
191
  return { state, focused, selection: { type: 'text', ...selection } };
142
192
  } catch (error) {
143
- if (error instanceof NativeEditorEngineBoundaryError) throw error;
193
+ if (error instanceof NativeEditorEngineBoundaryError) {
194
+ throw error;
195
+ }
196
+
144
197
  invalidLocalAwarenessIntent();
145
198
  }
146
199
  }
@@ -152,6 +205,7 @@ export function serializeLocalAwarenessIntent(
152
205
  const wire = Object.create(null) as Record<string, unknown>;
153
206
  wire.state = intent.state;
154
207
  wire.focused = intent.focused;
208
+
155
209
  if (intent.selection === null) {
156
210
  wire.selection = null;
157
211
  } else if (intent.selection !== undefined) {
@@ -161,6 +215,7 @@ export function serializeLocalAwarenessIntent(
161
215
  selection.head = intent.selection.head;
162
216
  wire.selection = selection;
163
217
  }
218
+
164
219
  return serializeV2CreateEnvelope(wire);
165
220
  } catch {
166
221
  invalidLocalAwarenessIntent();