@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
@@ -7,7 +7,7 @@ import {
7
7
  import { applyRenderPatch, collectAtomInstanceBlocks } from './atomInstances';
8
8
  import { allocateEditorUpdateRevision } from './EditorUpdateRevision';
9
9
  import { setActiveEditorToolbarFrameOwnerForEditor } from './EditorToolbar';
10
- import { useRichTextEditorState } from './useRichTextEditorState';
10
+ import { type useRichTextEditorState } from './useRichTextEditorState';
11
11
  import {
12
12
  selectedAtomKeys,
13
13
  equalStringSets,
@@ -84,52 +84,59 @@ export function useRichTextEditorUpdates(
84
84
  const updateAtomSelection = useCallback(
85
85
  (selection: Selection, instances = atomStateRef.current.instances) => {
86
86
  const next = selectedAtomKeys(selection, instances);
87
- setSelectedKeys((current) => (equalStringSets(current, next) ? current : next));
87
+ setSelectedKeys(current => (equalStringSets(current, next) ? current : next));
88
88
  },
89
- []
89
+ [ atomStateRef, setSelectedKeys ]
90
90
  );
91
91
 
92
92
  const refreshAtomsFromUpdate = useCallback(
93
93
  (update: NativeEditorAtomicRenderSnapshot) => {
94
94
  const previous = atomStateRef.current;
95
+
95
96
  const source =
96
97
  update.renderPatch != null &&
97
98
  update.renderPatch.baseDocumentVersion !== previous.documentVersion
98
99
  ? bridge.renderUpdate()
99
100
  : update;
101
+
100
102
  if (
101
103
  source.renderPatch != null &&
102
104
  source.renderPatch.baseDocumentVersion !== previous.documentVersion
103
105
  ) {
104
106
  return;
105
107
  }
108
+
106
109
  const blocks =
107
110
  source.renderBlocks != null
108
111
  ? source.renderBlocks
109
112
  : applyRenderPatch(previous.blocks, {
110
- startIndex: source.renderPatch.startIndex,
111
- deleteCount: source.renderPatch.deleteCount,
112
- renderBlocks: source.renderPatch.renderBlocks,
113
- });
113
+ startIndex: source.renderPatch.startIndex,
114
+ deleteCount: source.renderPatch.deleteCount,
115
+ renderBlocks: source.renderPatch.renderBlocks,
116
+ });
117
+
114
118
  const patchedCollection =
115
119
  source.renderPatch != null && previous.hasOnlyStableAtomKeys
116
120
  ? collectAtomInstanceBlocks(
117
- source.renderPatch.renderBlocks,
118
- registeredAtomTypes
119
- )
121
+ source.renderPatch.renderBlocks,
122
+ registeredAtomTypes
123
+ )
120
124
  : null;
125
+
121
126
  const collection =
122
127
  source.renderPatch != null && patchedCollection?.hasOnlyStableKeys
123
128
  ? {
124
- instanceBlocks: applyRenderPatch(previous.instanceBlocks, {
125
- startIndex: source.renderPatch.startIndex,
126
- deleteCount: source.renderPatch.deleteCount,
127
- renderBlocks: patchedCollection.instanceBlocks,
128
- }),
129
- hasOnlyStableKeys: true,
130
- }
129
+ instanceBlocks: applyRenderPatch(previous.instanceBlocks, {
130
+ startIndex: source.renderPatch.startIndex,
131
+ deleteCount: source.renderPatch.deleteCount,
132
+ renderBlocks: patchedCollection.instanceBlocks,
133
+ }),
134
+ hasOnlyStableKeys: true,
135
+ }
131
136
  : collectAtomInstanceBlocks(blocks, registeredAtomTypes);
137
+
132
138
  const instances = collection.instanceBlocks.flat();
139
+
133
140
  const next = {
134
141
  blocks,
135
142
  instanceBlocks: collection.instanceBlocks,
@@ -137,19 +144,28 @@ export function useRichTextEditorUpdates(
137
144
  documentVersion: source.documentVersion,
138
145
  hasOnlyStableAtomKeys: collection.hasOnlyStableKeys,
139
146
  };
147
+
140
148
  atomStateRef.current = next;
149
+
141
150
  if (!equalAtomInstances(previous.instances, next.instances)) {
142
151
  setAtomState(next);
143
152
  }
153
+
144
154
  updateAtomSelection(selectionRef.current, next.instances);
145
155
  },
146
- [bridge, registeredAtomTypes, updateAtomSelection]
156
+ [ atomStateRef,
157
+ bridge,
158
+ registeredAtomTypes,
159
+ selectionRef,
160
+ setAtomState,
161
+ updateAtomSelection ]
147
162
  );
148
163
 
149
164
  useEffect(() => {
150
165
  const current = atomStateRef.current;
151
166
  const collection = collectAtomInstanceBlocks(current.blocks, registeredAtomTypes);
152
167
  const instances = collection.instances;
168
+
153
169
  const next = {
154
170
  blocks: current.blocks,
155
171
  instanceBlocks: collection.instanceBlocks,
@@ -157,12 +173,19 @@ export function useRichTextEditorUpdates(
157
173
  documentVersion: current.documentVersion,
158
174
  hasOnlyStableAtomKeys: collection.hasOnlyStableKeys,
159
175
  };
176
+
160
177
  atomStateRef.current = next;
178
+
161
179
  if (!equalAtomInstances(current.instances, instances)) {
162
180
  setAtomState(next);
163
181
  }
182
+
164
183
  updateAtomSelection(selectionRef.current, instances);
165
- }, [registeredAtomTypes, updateAtomSelection]);
184
+ }, [ atomStateRef,
185
+ registeredAtomTypes,
186
+ selectionRef,
187
+ setAtomState,
188
+ updateAtomSelection ]);
166
189
 
167
190
  useLayoutEffect(() => {
168
191
  if (
@@ -172,18 +195,28 @@ export function useRichTextEditorUpdates(
172
195
  ) {
173
196
  return;
174
197
  }
198
+
175
199
  atomSeedEditorIdRef.current = editorId;
176
200
  refreshAtomsFromUpdate(bridge.renderUpdate());
177
- }, [bridge, document.isReady, documentHandle, editorId, refreshAtomsFromUpdate]);
201
+ }, [ atomSeedEditorIdRef,
202
+ bridge,
203
+ document.isReady,
204
+ documentHandle,
205
+ editorId,
206
+ refreshAtomsFromUpdate ]);
178
207
 
179
208
  const applyTypedUpdateState = useCallback(
180
209
  (
181
210
  update: Pick<NativeEditorAtomicRenderSnapshot, 'selection' | 'activeState'>,
182
211
  isCurrent: () => boolean = () => true
183
212
  ) => {
184
- if (!isCurrent()) return false;
213
+ if (!isCurrent()) {
214
+ return false;
215
+ }
216
+
185
217
  selectionRef.current = update.selection;
186
218
  updateAtomSelection(update.selection);
219
+
187
220
  if (
188
221
  update.selection.type === 'text' &&
189
222
  update.selection.anchorScalar != null &&
@@ -194,37 +227,74 @@ export function useRichTextEditorUpdates(
194
227
  head: update.selection.headScalar,
195
228
  };
196
229
  }
197
- if (!isCurrent()) return false;
230
+
231
+ if (!isCurrent()) {
232
+ return false;
233
+ }
234
+
198
235
  const nextActiveState = update.activeState;
199
236
  activeStateRef.current = nextActiveState;
200
- if (!isCurrent()) return false;
237
+
238
+ if (!isCurrent()) {
239
+ return false;
240
+ }
241
+
201
242
  setActiveState(nextActiveState);
202
- if (!isCurrent()) return false;
243
+
244
+ if (!isCurrent()) {
245
+ return false;
246
+ }
247
+
203
248
  const key = stringifyCachedJson(nextActiveState);
249
+
204
250
  if (key !== activeStateKeyRef.current) {
205
- if (!isCurrent()) return false;
251
+ if (!isCurrent()) {
252
+ return false;
253
+ }
254
+
206
255
  activeStateKeyRef.current = key;
207
- if (!isCurrent()) return false;
256
+
257
+ if (!isCurrent()) {
258
+ return false;
259
+ }
260
+
208
261
  onActiveStateChangeRef.current?.(nextActiveState);
209
262
  }
263
+
210
264
  return isCurrent();
211
265
  },
212
- [updateAtomSelection]
266
+ [ activeStateKeyRef,
267
+ activeStateRef,
268
+ onActiveStateChangeRef,
269
+ scalarSelectionRef,
270
+ selectionRef,
271
+ setActiveState,
272
+ updateAtomSelection ]
213
273
  );
214
274
 
215
275
  const applyUpdateState = useCallback(
216
276
  (updateJson: string | null | undefined) => {
217
- if (typeof updateJson !== 'string' || updateJson.length === 0) return null;
277
+ if (typeof updateJson !== 'string' || updateJson.length === 0) {
278
+ return null;
279
+ }
280
+
218
281
  let parsed: Record<string, unknown>;
282
+
219
283
  try {
220
284
  const candidate = JSON.parse(updateJson) as unknown;
221
- if (!isRecord(candidate)) return null;
285
+
286
+ if (!isRecord(candidate)) {
287
+ return null;
288
+ }
289
+
222
290
  parsed = candidate;
223
291
  } catch {
224
292
  return null;
225
293
  }
294
+
226
295
  const nextSelection = parseSelectionFromUpdate(parsed.selection);
227
296
  const nextActiveState = parseActiveStateFromUpdate(parsed.activeState);
297
+
228
298
  if (nextSelection && nextActiveState) {
229
299
  applyTypedUpdateState({
230
300
  selection: nextSelection,
@@ -236,67 +306,124 @@ export function useRichTextEditorUpdates(
236
306
  activeStateRef.current = nextActiveState;
237
307
  setActiveState(nextActiveState);
238
308
  const key = stringifyCachedJson(nextActiveState);
309
+
239
310
  if (key !== activeStateKeyRef.current) {
240
311
  activeStateKeyRef.current = key;
241
312
  onActiveStateChangeRef.current?.(nextActiveState);
242
313
  }
243
314
  }
315
+
244
316
  return parsed;
245
317
  },
246
- [applyTypedUpdateState]
318
+ [ activeStateKeyRef,
319
+ activeStateRef,
320
+ applyTypedUpdateState,
321
+ onActiveStateChangeRef,
322
+ selectionRef,
323
+ setActiveState ]
247
324
  );
248
325
 
249
326
  const pushEngineUpdateToView = useCallback(() => {
250
- if (documentHandle.isDestroyed) return;
327
+ if (documentHandle.isDestroyed) {
328
+ return;
329
+ }
330
+
251
331
  const sourceEditorId = documentHandle.editorId;
252
332
  const sourceBindingGeneration = pushedUpdateBindingGenerationRef.current;
333
+
253
334
  const isCurrentSource = () => {
254
335
  const current =
255
336
  sourceEditorId === currentPushedUpdateEditorIdRef.current &&
256
337
  sourceBindingGeneration === pushedUpdateBindingGenerationRef.current;
338
+
257
339
  return current;
258
340
  };
341
+
259
342
  const allocation = allocateEditorUpdateRevision(pushRevisionRef.current);
343
+
260
344
  if ('error' in allocation) {
261
345
  bridge._emitAutonomousError(allocation.error);
346
+
262
347
  return;
263
348
  }
349
+
264
350
  const reset = bridge._pendingViewReset;
265
351
  const snapshot = bridge.renderUpdate();
352
+
266
353
  // renderUpdate can synchronously re-enter React and bind this
267
354
  // component to another handle. Do not let any side effect from A
268
355
  // reach B once that happens.
269
- if (!isCurrentSource()) return;
356
+ if (!isCurrentSource()) {
357
+ return;
358
+ }
359
+
270
360
  refreshAtomsFromUpdate(snapshot);
271
- if (!isCurrentSource()) return;
361
+
362
+ if (!isCurrentSource()) {
363
+ return;
364
+ }
365
+
272
366
  const updateJson = JSON.stringify(snapshot);
273
- if (!isCurrentSource()) return;
367
+
368
+ if (!isCurrentSource()) {
369
+ return;
370
+ }
371
+
274
372
  if (!applyTypedUpdateState(snapshot, isCurrentSource)) {
275
373
  return;
276
374
  }
277
- if (!isCurrentSource()) return;
375
+
376
+ if (!isCurrentSource()) {
377
+ return;
378
+ }
379
+
278
380
  lastPushedEngineRevisionRef.current = snapshot.documentVersion;
279
- if (!isCurrentSource()) return;
381
+
382
+ if (!isCurrentSource()) {
383
+ return;
384
+ }
385
+
280
386
  pushRevisionRef.current = allocation.revision;
281
- if (!isCurrentSource()) return;
387
+
388
+ if (!isCurrentSource()) {
389
+ return;
390
+ }
391
+
282
392
  setPushedUpdate({
283
393
  json: updateJson,
284
394
  resetJson: reset?.json,
285
395
  revision: allocation.revision,
286
396
  editorId: sourceEditorId,
287
397
  });
288
- if (bridge._pendingViewReset === reset) bridge._pendingViewReset = null;
289
- }, [applyTypedUpdateState, bridge, documentHandle, refreshAtomsFromUpdate]);
398
+
399
+ if (bridge._pendingViewReset === reset) {
400
+ bridge._pendingViewReset = null;
401
+ }
402
+ }, [ applyTypedUpdateState,
403
+ bridge,
404
+ currentPushedUpdateEditorIdRef,
405
+ documentHandle.editorId,
406
+ documentHandle.isDestroyed,
407
+ lastPushedEngineRevisionRef,
408
+ pushRevisionRef,
409
+ pushedUpdateBindingGenerationRef,
410
+ refreshAtomsFromUpdate,
411
+ setPushedUpdate ]);
290
412
 
291
413
  useLayoutEffect(() => {
292
414
  const pending = pendingFocusedRebindRef.current;
293
- if (pending?.editorId !== editorId || !document.isReady) return;
415
+
416
+ if (pending?.editorId !== editorId || !document.isReady) {
417
+ return;
418
+ }
419
+
294
420
  pendingFocusedRebindRef.current = null;
295
421
 
296
422
  const snapshot = bridge.renderUpdate();
297
423
  const anchor = Math.min(pending.anchor, snapshot.scalarLength);
298
424
  const head = Math.min(pending.head, snapshot.scalarLength);
299
425
  const collapsed = anchor === head;
426
+
300
427
  const setSelection = (affinity: NativeEditorPositionAffinity) =>
301
428
  bridge.setSelection({
302
429
  baseDocumentRevision: snapshot.documentVersion,
@@ -311,35 +438,46 @@ export function useRichTextEditorUpdates(
311
438
  try {
312
439
  setSelection(collapsed ? 'after' : 'before');
313
440
  } catch (error) {
314
- if (!collapsed || !isPositionInvalidError(error)) throw error;
441
+ if (!collapsed || !isPositionInvalidError(error)) {
442
+ throw error;
443
+ }
444
+
315
445
  setSelection('before');
316
446
  }
447
+
317
448
  pushEngineUpdateToView();
318
449
  } catch (error) {
319
- if (!isPositionInvalidError(error)) throw error;
450
+ if (!isPositionInvalidError(error)) {
451
+ throw error;
452
+ }
320
453
  }
321
454
 
322
455
  setActiveEditorToolbarFrameOwnerForEditor(toolbarFrameOwnerId, true);
323
456
  onFocusRef.current?.();
324
- }, [bridge, document.isReady, editorId, pushEngineUpdateToView, toolbarFrameOwnerId]);
457
+ }, [ bridge,
458
+ document.isReady,
459
+ editorId,
460
+ onFocusRef,
461
+ pendingFocusedRebindRef,
462
+ pushEngineUpdateToView,
463
+ toolbarFrameOwnerId ]);
325
464
 
326
465
  // A pending update is owned by the handle that produced its snapshot.
327
466
  // Drop it when this component rebinds, so no old session state reaches
328
467
  // the next native view binding.
329
468
  useEffect(() => {
330
- setPushedUpdate((current) => {
331
- if (current == null || current.editorId === editorId) return current;
469
+ setPushedUpdate(current => {
470
+ if (current == null || current.editorId === editorId) {
471
+ return current;
472
+ }
473
+
332
474
  return null;
333
475
  });
334
- }, [editorId]);
476
+ }, [ editorId, setPushedUpdate ]);
335
477
 
336
478
  const pendingViewReset = bridge._pendingViewReset;
337
479
 
338
- // After a JS-driven engine change (controlled apply, remote commit,
339
- // document-API mutation) the view learns the new state here. Native-
340
- // driven commits (typing, native toolbar) already updated the view
341
- // through the adapter and are never echoed back. The first observed
342
- // revision is skipped: the view pulls the initial state natively on bind.
480
+ // Native commits already update the view; echo only subsequent JS-driven revisions.
343
481
  useEffect(() => {
344
482
  // The document hook refreshes its state after the rebind commit. Its
345
483
  // first render can therefore still contain A's revision; do not let
@@ -347,13 +485,20 @@ export function useRichTextEditorUpdates(
347
485
  if (didRebindRevisionScope) {
348
486
  return;
349
487
  }
350
- if (!document.isReady || document.documentRevision == null) return;
488
+
489
+ if (!document.isReady || document.documentRevision == null) {
490
+ return;
491
+ }
492
+
351
493
  const revision = document.documentRevision;
494
+
352
495
  if (!pendingViewReset && !didObserveInitialRevisionRef.current) {
353
496
  didObserveInitialRevisionRef.current = true;
354
497
  lastPushedEngineRevisionRef.current = revision;
498
+
355
499
  return;
356
500
  }
501
+
357
502
  if (
358
503
  !pendingViewReset &&
359
504
  lastPushedEngineRevisionRef.current != null &&
@@ -361,6 +506,7 @@ export function useRichTextEditorUpdates(
361
506
  ) {
362
507
  return;
363
508
  }
509
+
364
510
  if (
365
511
  !pendingViewReset &&
366
512
  (revision === lastNativeDrivenRevisionRef.current ||
@@ -370,13 +516,15 @@ export function useRichTextEditorUpdates(
370
516
  if (document.documentOrigin === 'remoteCollaboration' && !documentHandle.isDestroyed) {
371
517
  refreshAtomsFromUpdate(bridge.renderUpdate());
372
518
  }
519
+
373
520
  lastPushedEngineRevisionRef.current = revision;
521
+
374
522
  return;
375
523
  }
524
+
376
525
  lastNativeDrivenRevisionRef.current = null;
377
526
  pushEngineUpdateToView();
378
- }, [
379
- pendingViewReset,
527
+ }, [ pendingViewReset,
380
528
  didRebindRevisionScope,
381
529
  document.isReady,
382
530
  document.documentRevision,
@@ -385,13 +533,16 @@ export function useRichTextEditorUpdates(
385
533
  documentHandle,
386
534
  pushEngineUpdateToView,
387
535
  refreshAtomsFromUpdate,
388
- ]);
536
+ didObserveInitialRevisionRef,
537
+ lastPushedEngineRevisionRef,
538
+ lastNativeDrivenRevisionRef ]);
389
539
 
390
540
  const afterLocalEngineMutation = useCallback(() => {
391
541
  onLocalCommitRef.current?.();
392
542
  pushEngineUpdateToView();
393
543
  document.refresh();
394
- }, [document, pushEngineUpdateToView]);
544
+ }, [ document, onLocalCommitRef, pushEngineUpdateToView ]);
545
+
395
546
  return {
396
547
  refreshAtomsFromUpdate,
397
548
  afterLocalEngineMutation,