@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
@@ -22,7 +22,7 @@ function useRichTextEditorCommands(context) {
22
22
  if (!editableRef.current) {
23
23
  throw new atomInstances_1.AtomUpdateAttrsError('not-applicable', 'The editor is not editable');
24
24
  }
25
- const instance = atomStateRef.current.instances.find((candidate) => candidate.key === atomKey && candidate.nodeType === nodeType);
25
+ const instance = atomStateRef.current.instances.find(candidate => candidate.key === atomKey && candidate.nodeType === nodeType);
26
26
  if (instance == null ||
27
27
  (!hasStableKey &&
28
28
  (atomStateRef.current.documentVersion !== expectedDocumentVersion ||
@@ -60,20 +60,29 @@ function useRichTextEditorCommands(context) {
60
60
  throw new atomInstances_1.AtomUpdateAttrsError('engine-error', 'Unexpected atom update outcome');
61
61
  }
62
62
  afterLocalEngineMutation();
63
- }, [afterLocalEngineMutation, bridge, document, documentHandle, refreshAtomsFromUpdate]);
63
+ }, [afterLocalEngineMutation,
64
+ atomStateRef,
65
+ bridge,
66
+ document,
67
+ documentHandle.isDestroyed,
68
+ latestRevisionRef,
69
+ refreshAtomsFromUpdate]);
64
70
  const runAtomAction = (0, react_1.useCallback)(async (owner, instance, documentVersion, action) => {
65
71
  if (owner !== atomOwnerRef.current ||
66
72
  documentHandle.isDestroyed ||
67
- latestRevisionRef.current == null)
73
+ latestRevisionRef.current == null) {
68
74
  throw new atomInstances_1.AtomUpdateAttrsError('not-ready', 'The editor is not ready.');
69
- if (action === 'delete' && !editableRef.current)
75
+ }
76
+ if (action === 'delete' && !editableRef.current) {
70
77
  throw new atomInstances_1.AtomUpdateAttrsError('not-applicable', 'The editor is not editable.');
71
- const current = atomStateRef.current.instances.find((candidate) => candidate.key === instance.key && candidate.nodeType === instance.nodeType);
78
+ }
79
+ const current = atomStateRef.current.instances.find(candidate => candidate.key === instance.key && candidate.nodeType === instance.nodeType);
72
80
  if (!current ||
73
81
  (!instance.hasStableKey &&
74
82
  (documentVersion !== atomStateRef.current.documentVersion ||
75
- current.docPos !== instance.docPos)))
83
+ current.docPos !== instance.docPos))) {
76
84
  throw new atomInstances_1.AtomUpdateAttrsError('not-applicable', 'The atom no longer exists.');
85
+ }
77
86
  try {
78
87
  const baseDocumentRevision = latestRevisionRef.current;
79
88
  const selection = bridge.setSelection({
@@ -84,27 +93,31 @@ function useRichTextEditorCommands(context) {
84
93
  edge: action === 'select' || action === 'delete' ? 'node' : action,
85
94
  },
86
95
  });
87
- if (selection.type !== 'transaction')
96
+ if (selection.type !== 'transaction') {
88
97
  throw new atomInstances_1.AtomUpdateAttrsError('not-applicable', 'The atom could not be selected.');
98
+ }
89
99
  if (action === 'delete') {
90
100
  const outcome = bridge.applyCommand({
91
101
  baseDocumentRevision,
92
102
  command: { type: 'deleteBackward' },
93
103
  });
94
- if (outcome.type !== 'transaction')
104
+ if (outcome.type !== 'transaction') {
95
105
  throw new atomInstances_1.AtomUpdateAttrsError('not-applicable', 'The atom could not be deleted.');
106
+ }
96
107
  afterLocalEngineMutation();
97
108
  }
98
109
  else {
99
110
  pushEngineUpdateToView();
100
111
  document.refresh();
101
- if (action !== 'select')
112
+ if (action !== 'select') {
102
113
  nativeViewRef.current?.focus?.();
114
+ }
103
115
  }
104
116
  }
105
117
  catch (error) {
106
- if (error instanceof atomInstances_1.AtomUpdateAttrsError)
118
+ if (error instanceof atomInstances_1.AtomUpdateAttrsError) {
107
119
  throw error;
120
+ }
108
121
  if ((0, RichTextEditorSerialization_1.isRevisionMismatchError)(error)) {
109
122
  refreshAtomsFromUpdate(bridge.renderUpdate());
110
123
  document.refresh();
@@ -112,14 +125,15 @@ function useRichTextEditorCommands(context) {
112
125
  }
113
126
  throw new atomInstances_1.AtomUpdateAttrsError('engine-error', error instanceof Error ? error.message : String(error));
114
127
  }
115
- }, [
116
- afterLocalEngineMutation,
128
+ }, [afterLocalEngineMutation,
129
+ atomStateRef,
117
130
  bridge,
118
131
  document,
119
- documentHandle,
132
+ documentHandle.isDestroyed,
133
+ latestRevisionRef,
134
+ nativeViewRef,
120
135
  pushEngineUpdateToView,
121
- refreshAtomsFromUpdate,
122
- ]);
136
+ refreshAtomsFromUpdate]);
123
137
  const runEngineMutation = (0, react_1.useCallback)((invoke) => {
124
138
  if (!editableRef.current) {
125
139
  throw new NativeEditorBoundaryError_1.NativeEditorOperationError({
@@ -151,15 +165,16 @@ function useRichTextEditorCommands(context) {
151
165
  throw error;
152
166
  }
153
167
  afterLocalEngineMutation();
154
- }, [afterLocalEngineMutation, document]);
168
+ }, [afterLocalEngineMutation, document, latestRevisionRef]);
155
169
  const applyEngineCommand = (0, react_1.useCallback)((command) => {
156
- runEngineMutation((baseDocumentRevision) => bridge.applyCommand({ command, baseDocumentRevision }));
170
+ runEngineMutation(baseDocumentRevision => bridge.applyCommand({ command, baseDocumentRevision }));
157
171
  }, [bridge, runEngineMutation]);
158
172
  const commandToggleMark = (0, react_1.useCallback)((markType) => applyEngineCommand({ type: 'toggleMark', markType }), [applyEngineCommand]);
159
173
  const commandSetLink = (0, react_1.useCallback)((href) => {
160
174
  const trimmedHref = href.trim();
161
- if (!trimmedHref)
175
+ if (!trimmedHref) {
162
176
  return;
177
+ }
163
178
  applyEngineCommand({
164
179
  type: 'setMark',
165
180
  markType: 'link',
@@ -183,7 +198,7 @@ function useRichTextEditorCommands(context) {
183
198
  ? 'list_item'
184
199
  : 'listItem',
185
200
  });
186
- }, [applyEngineCommand]);
201
+ }, [activeStateRef, applyEngineCommand]);
187
202
  const commandIndentListItem = (0, react_1.useCallback)(() => applyEngineCommand({ type: 'indentListItem' }), [applyEngineCommand]);
188
203
  const commandOutdentListItem = (0, react_1.useCallback)(() => applyEngineCommand({ type: 'outdentListItem' }), [applyEngineCommand]);
189
204
  const commandInsertNode = (0, react_1.useCallback)((nodeType) => applyEngineCommand({ type: 'insertNode', nodeType }), [applyEngineCommand]);
@@ -194,9 +209,10 @@ function useRichTextEditorCommands(context) {
194
209
  });
195
210
  }, [applyEngineCommand, documentDescriptor]);
196
211
  const commandInsertText = (0, react_1.useCallback)((text) => {
197
- if (!text)
212
+ if (!text) {
198
213
  return;
199
- runEngineMutation((baseDocumentRevision) => bridge.applyInput({ text, baseDocumentRevision }));
214
+ }
215
+ runEngineMutation(baseDocumentRevision => bridge.applyInput({ text, baseDocumentRevision }));
200
216
  }, [bridge, runEngineMutation]);
201
217
  const commandInsertContentHtml = (0, react_1.useCallback)((html) => applyEngineCommand({ type: 'insertContentHtml', html }), [applyEngineCommand]);
202
218
  const commandInsertContentJson = (0, react_1.useCallback)((doc) => applyEngineCommand({ type: 'insertContentJson', json: doc }), [applyEngineCommand]);
@@ -209,13 +225,17 @@ function useRichTextEditorCommands(context) {
209
225
  setLink: commandSetLink,
210
226
  unsetLink: commandUnsetLink,
211
227
  });
212
- }, [commandSetLink, commandUnsetLink]);
228
+ }, [activeStateRef,
229
+ commandSetLink,
230
+ commandUnsetLink,
231
+ onRequestLinkRef,
232
+ selectionRef]);
213
233
  const openImageRequest = (0, react_1.useCallback)(() => {
214
234
  onRequestImageRef.current?.({
215
235
  selection: selectionRef.current,
216
236
  insertImage: commandInsertImage,
217
237
  });
218
- }, [commandInsertImage]);
238
+ }, [commandInsertImage, onRequestImageRef, selectionRef]);
219
239
  (0, react_1.useImperativeHandle)(ref, () => ({
220
240
  focus() {
221
241
  nativeViewRef.current?.focus?.();
@@ -254,8 +274,9 @@ function useRichTextEditorCommands(context) {
254
274
  getTextContent: document.getTextContent,
255
275
  async getCaretRect() {
256
276
  const nativeView = nativeViewRef.current;
257
- if (!nativeView?.getCaretRect)
277
+ if (!nativeView?.getCaretRect) {
258
278
  return null;
279
+ }
259
280
  const raw = await Promise.resolve(nativeView.getCaretRect());
260
281
  return (0, RichTextEditorSerialization_1.parseCaretRectJson)(raw);
261
282
  },
@@ -263,9 +284,7 @@ function useRichTextEditorCommands(context) {
263
284
  redo: document.redo,
264
285
  canUndo: document.canUndo,
265
286
  canRedo: document.canRedo,
266
- }), [
267
- document,
268
- commandToggleMark,
287
+ }), [commandToggleMark,
269
288
  commandSetLink,
270
289
  commandUnsetLink,
271
290
  commandToggleBlockquote,
@@ -278,9 +297,20 @@ function useRichTextEditorCommands(context) {
278
297
  commandInsertText,
279
298
  commandInsertContentHtml,
280
299
  commandInsertContentJson,
281
- editable,
300
+ document.setContent,
301
+ document.setContentJson,
302
+ document.clearContent,
303
+ document.getContent,
304
+ document.getContentJson,
305
+ document.getIsEmpty,
306
+ document.getTextContent,
307
+ document.undo,
308
+ document.redo,
309
+ document.canUndo,
310
+ document.canRedo,
311
+ nativeViewRef,
282
312
  externalCompositionManager,
283
- ]);
313
+ editable]);
284
314
  return {
285
315
  openLinkRequest,
286
316
  openImageRequest,
@@ -1,7 +1,7 @@
1
1
  import { type NativeSyntheticEvent } from 'react-native';
2
- import { useRichTextEditorState } from './useRichTextEditorState';
3
- import { useRichTextEditorUpdates } from './useRichTextEditorUpdates';
4
- import { useRichTextEditorCommands } from './useRichTextEditorCommands';
2
+ import { type useRichTextEditorState } from './useRichTextEditorState';
3
+ import { type useRichTextEditorUpdates } from './useRichTextEditorUpdates';
4
+ import { type useRichTextEditorCommands } from './useRichTextEditorCommands';
5
5
  import { type NativeUpdateEvent, type NativeErrorEvent, type NativeExternalTextCompositionEvent, type NativeSelectionEvent, type NativeFocusEvent, type NativeContentHeightEvent, type NativeAtomLayoutEvent, type NativeToolbarActionEvent } from './RichTextEditorNativeTypes';
6
6
  export declare function useRichTextEditorEvents(context: Pick<ReturnType<typeof useRichTextEditorState>, 'editorId' | 'documentHandle' | 'lastAcceptedNativeCommitRevisionRef' | 'lastNativeDrivenRevisionRef' | 'document' | 'onLocalCommitRef' | 'nativeErrorBindingRef' | 'nativeErrorBinding' | 'externalCompositionManager' | 'scalarSelectionRef' | 'selectionRef' | 'onSelectionChangeRef' | 'isFocusedRef' | 'toolbarFrameOwnerId' | 'setIsFocused' | 'onFocusRef' | 'onBlurRef' | 'heightBehavior' | 'setAutoGrowHeight' | 'setAtomContentWidth' | 'setNativeAtomViewport' | 'setAtomPositions' | 'onToolbarActionRef'> & Pick<ReturnType<typeof useRichTextEditorUpdates>, 'refreshAtomsFromUpdate' | 'applyTypedUpdateState' | 'applyUpdateState' | 'updateAtomSelection'> & Pick<ReturnType<typeof useRichTextEditorCommands>, 'openLinkRequest' | 'openImageRequest'>): {
7
7
  isForThisEditor: (payload: {
@@ -10,11 +10,13 @@ function useRichTextEditorEvents(context) {
10
10
  const { editorId, documentHandle, lastAcceptedNativeCommitRevisionRef, lastNativeDrivenRevisionRef, refreshAtomsFromUpdate, applyTypedUpdateState, document, onLocalCommitRef, nativeErrorBindingRef, nativeErrorBinding, externalCompositionManager, scalarSelectionRef, applyUpdateState, selectionRef, updateAtomSelection, onSelectionChangeRef, isFocusedRef, toolbarFrameOwnerId, setIsFocused, onFocusRef, onBlurRef, heightBehavior, setAutoGrowHeight, setAtomContentWidth, setNativeAtomViewport, setAtomPositions, openLinkRequest, openImageRequest, onToolbarActionRef, } = context;
11
11
  const isForThisEditor = (0, react_1.useCallback)((payload) => payload.editorId === editorId, [editorId]);
12
12
  const handleEditorUpdate = (0, react_1.useCallback)((event) => {
13
- if (documentHandle.isDestroyed)
13
+ if (documentHandle.isDestroyed) {
14
14
  return;
15
+ }
15
16
  const accepted = (0, RichTextEditorSerialization_1.acceptNativeCommitPayload)(event.nativeEvent, documentHandle.editorId, lastAcceptedNativeCommitRevisionRef.current);
16
- if (accepted == null)
17
+ if (accepted == null) {
17
18
  return;
19
+ }
18
20
  // Record both native-scoped revisions before any observable
19
21
  // state/callback/refresh work. This makes duplicate delivery and
20
22
  // the handle's same-revision signal deterministic.
@@ -25,10 +27,18 @@ function useRichTextEditorEvents(context) {
25
27
  // The adapter already committed; re-read for content callbacks.
26
28
  document.refresh();
27
29
  onLocalCommitRef.current?.();
28
- }, [applyTypedUpdateState, document, documentHandle, refreshAtomsFromUpdate]);
30
+ }, [applyTypedUpdateState,
31
+ document,
32
+ documentHandle.editorId,
33
+ documentHandle.isDestroyed,
34
+ lastAcceptedNativeCommitRevisionRef,
35
+ lastNativeDrivenRevisionRef,
36
+ onLocalCommitRef,
37
+ refreshAtomsFromUpdate]);
29
38
  const admitNativeBindingEvent = (0, react_1.useCallback)((payload) => {
30
- if (!(0, RichTextEditorSerialization_1.isRecord)(payload))
39
+ if (!(0, RichTextEditorSerialization_1.isRecord)(payload)) {
31
40
  return null;
41
+ }
32
42
  const currentBinding = nativeErrorBindingRef.current;
33
43
  if (currentBinding !== nativeErrorBinding ||
34
44
  !currentBinding.mounted ||
@@ -45,21 +55,24 @@ function useRichTextEditorEvents(context) {
45
55
  return null;
46
56
  }
47
57
  return currentBinding;
48
- }, [nativeErrorBinding]);
58
+ }, [nativeErrorBinding, nativeErrorBindingRef]);
49
59
  const handleEditorError = (0, react_1.useCallback)((event) => {
50
60
  const payload = event?.nativeEvent;
51
61
  const binding = admitNativeBindingEvent(payload);
52
- if (binding == null || !(0, RichTextEditorSerialization_1.isRecord)(payload))
62
+ if (binding == null || !(0, RichTextEditorSerialization_1.isRecord)(payload)) {
53
63
  return;
64
+ }
54
65
  binding.handle.bridge._emitAutonomousError(payload.error);
55
66
  }, [admitNativeBindingEvent]);
56
67
  const handleExternalTextCompositionEnd = (0, react_1.useCallback)((event) => {
57
68
  const payload = event?.nativeEvent;
58
69
  const binding = admitNativeBindingEvent(payload);
59
- if (binding == null || !(0, RichTextEditorSerialization_1.isRecord)(payload))
70
+ if (binding == null || !(0, RichTextEditorSerialization_1.isRecord)(payload)) {
60
71
  return;
61
- if (typeof payload.resultJson !== 'string')
72
+ }
73
+ if (typeof payload.resultJson !== 'string') {
62
74
  return;
75
+ }
63
76
  try {
64
77
  externalCompositionManager.handleNativeEnd(binding.editorId, payload.resultJson);
65
78
  }
@@ -68,8 +81,9 @@ function useRichTextEditorEvents(context) {
68
81
  }
69
82
  }, [admitNativeBindingEvent, externalCompositionManager]);
70
83
  const handleSelectionChange = (0, react_1.useCallback)((event) => {
71
- if (documentHandle.isDestroyed || !isForThisEditor(event.nativeEvent))
84
+ if (documentHandle.isDestroyed || !isForThisEditor(event.nativeEvent)) {
72
85
  return;
86
+ }
73
87
  const { anchor, head, stateJson } = event.nativeEvent;
74
88
  scalarSelectionRef.current = { anchor, head };
75
89
  let selection = { type: 'text', anchor, head };
@@ -81,10 +95,17 @@ function useRichTextEditorEvents(context) {
81
95
  selectionRef.current = selection;
82
96
  updateAtomSelection(selection);
83
97
  onSelectionChangeRef.current?.(selection);
84
- }, [applyUpdateState, documentHandle, isForThisEditor, updateAtomSelection]);
98
+ }, [applyUpdateState,
99
+ documentHandle.isDestroyed,
100
+ isForThisEditor,
101
+ onSelectionChangeRef,
102
+ scalarSelectionRef,
103
+ selectionRef,
104
+ updateAtomSelection]);
85
105
  const handleFocusChange = (0, react_1.useCallback)((event) => {
86
- if (documentHandle.isDestroyed || !isForThisEditor(event.nativeEvent))
106
+ if (documentHandle.isDestroyed || !isForThisEditor(event.nativeEvent)) {
87
107
  return;
108
+ }
88
109
  const focused = event.nativeEvent.isFocused;
89
110
  const wasFocused = isFocusedRef.current;
90
111
  isFocusedRef.current = focused;
@@ -96,37 +117,49 @@ function useRichTextEditorEvents(context) {
96
117
  else if (!focused && wasFocused) {
97
118
  onBlurRef.current?.();
98
119
  }
99
- }, [documentHandle, isForThisEditor, toolbarFrameOwnerId]);
120
+ }, [documentHandle.isDestroyed,
121
+ isFocusedRef,
122
+ isForThisEditor,
123
+ onBlurRef,
124
+ onFocusRef,
125
+ setIsFocused,
126
+ toolbarFrameOwnerId]);
100
127
  const handleContentHeightChange = (0, react_1.useCallback)((event) => {
101
- if (documentHandle.isDestroyed || !isForThisEditor(event.nativeEvent))
128
+ if (documentHandle.isDestroyed || !isForThisEditor(event.nativeEvent)) {
102
129
  return;
103
- if (heightBehavior !== 'autoGrow')
130
+ }
131
+ if (heightBehavior !== 'autoGrow') {
104
132
  return;
133
+ }
105
134
  const density = react_native_1.Platform.OS === 'android' ? react_native_1.PixelRatio.get() : 1;
106
135
  const nextHeight = Math.ceil(event.nativeEvent.contentHeight / density);
107
- if (!(nextHeight > 0))
136
+ if (!(nextHeight > 0)) {
108
137
  return;
109
- setAutoGrowHeight((prev) => (prev === nextHeight ? prev : nextHeight));
110
- }, [documentHandle, heightBehavior, isForThisEditor]);
138
+ }
139
+ setAutoGrowHeight(prev => (prev === nextHeight ? prev : nextHeight));
140
+ }, [documentHandle.isDestroyed, heightBehavior, isForThisEditor, setAutoGrowHeight]);
111
141
  const handleAtomLayout = (0, react_1.useCallback)((event) => {
112
- if (documentHandle.isDestroyed || !isForThisEditor(event.nativeEvent))
142
+ if (documentHandle.isDestroyed || !isForThisEditor(event.nativeEvent)) {
113
143
  return;
144
+ }
114
145
  const width = event.nativeEvent.width;
115
- if (!Number.isFinite(width) || width < 0)
146
+ if (!Number.isFinite(width) || width < 0) {
116
147
  return;
117
- setAtomContentWidth((current) => (current === width ? current : width));
148
+ }
149
+ setAtomContentWidth(current => (current === width ? current : width));
118
150
  const viewport = event.nativeEvent.viewport;
119
151
  if (viewport &&
120
152
  Number.isFinite(viewport.y) &&
121
153
  Number.isFinite(viewport.height) &&
122
154
  viewport.height >= 0) {
123
- setNativeAtomViewport((previous) => previous?.y === viewport.y && previous.height === viewport.height
155
+ setNativeAtomViewport(previous => previous?.y === viewport.y && previous.height === viewport.height
124
156
  ? previous
125
157
  : viewport);
126
158
  }
127
159
  const positions = event.nativeEvent.positions;
128
- if (!Array.isArray(positions))
160
+ if (!Array.isArray(positions)) {
129
161
  return;
162
+ }
130
163
  const next = new Map();
131
164
  for (const position of positions) {
132
165
  if (typeof position?.key !== 'string' ||
@@ -140,9 +173,10 @@ function useRichTextEditorEvents(context) {
140
173
  }
141
174
  next.set(position.key, position);
142
175
  }
143
- setAtomPositions((current) => {
144
- if (current.size !== next.size)
176
+ setAtomPositions(current => {
177
+ if (current.size !== next.size) {
145
178
  return next;
179
+ }
146
180
  for (const [key, position] of next) {
147
181
  const previous = current.get(key);
148
182
  if (previous?.x !== position.x ||
@@ -150,15 +184,21 @@ function useRichTextEditorEvents(context) {
150
184
  previous.hostY !== position.hostY ||
151
185
  previous.y !== position.y ||
152
186
  previous.height !== position.height ||
153
- previous.width !== position.width)
187
+ previous.width !== position.width) {
154
188
  return next;
189
+ }
155
190
  }
156
191
  return current;
157
192
  });
158
- }, [documentHandle, isForThisEditor]);
193
+ }, [documentHandle.isDestroyed,
194
+ isForThisEditor,
195
+ setAtomContentWidth,
196
+ setAtomPositions,
197
+ setNativeAtomViewport]);
159
198
  const handleToolbarAction = (0, react_1.useCallback)((event) => {
160
- if (documentHandle.isDestroyed || !isForThisEditor(event.nativeEvent))
199
+ if (documentHandle.isDestroyed || !isForThisEditor(event.nativeEvent)) {
161
200
  return;
201
+ }
162
202
  const { key, updateJson, stateJson, documentRevision } = event.nativeEvent;
163
203
  // A toolbar event carrying update data is a native commit. It
164
204
  // must satisfy the same atomic admission path as typing; a pure
@@ -192,16 +232,19 @@ function useRichTextEditorEvents(context) {
192
232
  return;
193
233
  }
194
234
  onToolbarActionRef.current?.(key);
195
- }, [
196
- applyTypedUpdateState,
235
+ }, [applyTypedUpdateState,
197
236
  applyUpdateState,
198
237
  document,
199
- documentHandle,
238
+ documentHandle.editorId,
239
+ documentHandle.isDestroyed,
200
240
  isForThisEditor,
241
+ lastAcceptedNativeCommitRevisionRef,
242
+ lastNativeDrivenRevisionRef,
243
+ onLocalCommitRef,
244
+ onToolbarActionRef,
201
245
  openImageRequest,
202
246
  openLinkRequest,
203
- refreshAtomsFromUpdate,
204
- ]);
247
+ refreshAtomsFromUpdate]);
205
248
  return {
206
249
  isForThisEditor,
207
250
  handleEditorUpdate,
@@ -1,9 +1,9 @@
1
1
  import { type EditorMentionTheme } from './EditorTheme';
2
2
  import { type NativeSyntheticEvent } from 'react-native';
3
- import { useRichTextEditorState } from './useRichTextEditorState';
4
- import { useRichTextEditorCommands } from './useRichTextEditorCommands';
5
- import { useRichTextEditorUpdates } from './useRichTextEditorUpdates';
6
- import { useRichTextEditorEvents } from './useRichTextEditorEvents';
3
+ import { type useRichTextEditorState } from './useRichTextEditorState';
4
+ import { type useRichTextEditorCommands } from './useRichTextEditorCommands';
5
+ import { type useRichTextEditorUpdates } from './useRichTextEditorUpdates';
6
+ import { type useRichTextEditorEvents } from './useRichTextEditorEvents';
7
7
  import { type NativeAddonEvent } from './RichTextEditorNativeTypes';
8
8
  export declare function useRichTextEditorMentions(context: Pick<ReturnType<typeof useRichTextEditorState>, 'addonsRef' | 'bridge' | 'documentHandle' | 'currentPushedUpdateEditorIdRef' | 'documentDescriptor' | 'latestRevisionRef' | 'document' | 'setMentionQuery' | 'mentionQuery' | 'addons' | 'editable' | 'isFocused' | 'activeStateRef' | 'toolbarFrameOwnerId'> & Pick<ReturnType<typeof useRichTextEditorCommands>, 'editableRef'> & Pick<ReturnType<typeof useRichTextEditorUpdates>, 'afterLocalEngineMutation'> & Pick<ReturnType<typeof useRichTextEditorEvents>, 'isForThisEditor'>): {
9
9
  mentionSuggestionTheme: EditorMentionTheme | undefined;
@@ -23,7 +23,7 @@ function useRichTextEditorMentions(context) {
23
23
  return (0, RichTextEditorSerialization_1.isRecord)(resolvedAttrs)
24
24
  ? { ...selectionEvent.attrs, ...resolvedAttrs }
25
25
  : selectionEvent.attrs;
26
- }, []);
26
+ }, [addonsRef]);
27
27
  const resolveMentionTheme = (0, react_1.useCallback)((selectionEvent) => {
28
28
  let resolvedTheme;
29
29
  try {
@@ -34,8 +34,9 @@ function useRichTextEditorMentions(context) {
34
34
  console.error('NativeRichTextEditor: mentions.resolveTheme threw', error);
35
35
  }
36
36
  }
37
- if (resolvedTheme === undefined || resolvedTheme === null)
37
+ if (resolvedTheme === undefined || resolvedTheme === null) {
38
38
  return undefined;
39
+ }
39
40
  // A rejected theme is dropped rather than written into the
40
41
  // document: every later renderUpdate revalidates it, so one bad
41
42
  // value would make the content permanently unrenderable.
@@ -46,7 +47,7 @@ function useRichTextEditorMentions(context) {
46
47
  return undefined;
47
48
  }
48
49
  return resolvedTheme;
49
- }, []);
50
+ }, [addonsRef]);
50
51
  const resolveMentionInsertionAttrs = (0, react_1.useCallback)((selectionEvent) => {
51
52
  const attrs = resolveMentionSelectionAttrs(selectionEvent);
52
53
  const resolvedTheme = resolveMentionTheme({ ...selectionEvent, attrs });
@@ -56,8 +57,9 @@ function useRichTextEditorMentions(context) {
56
57
  }, [resolveMentionSelectionAttrs, resolveMentionTheme]);
57
58
  const insertMentionSuggestion = (0, react_1.useCallback)((request) => {
58
59
  const mentions = addonsRef.current?.mentions;
59
- if (!mentions || !editableRef.current)
60
+ if (!mentions || !editableRef.current) {
60
61
  return;
62
+ }
61
63
  const snapshot = bridge.renderUpdate({
62
64
  anchor: request.range.anchor,
63
65
  head: request.range.head,
@@ -93,7 +95,7 @@ function useRichTextEditorMentions(context) {
93
95
  // own cursor resolution: a collapsed caret prefers After with a
94
96
  // deterministic Before fallback at text-boundary positions; a
95
97
  // range uses Before. The fallback changes only the stickiness
96
- // of the SAME position it is not a guessed-position retry.
98
+ // of the SAME position : it is not a guessed-position retry.
97
99
  const collapsed = anchorScalar === headScalar;
98
100
  const syncSelection = (affinity) => bridge.setSelection({
99
101
  baseDocumentRevision: snapshot.documentVersion,
@@ -108,8 +110,9 @@ function useRichTextEditorMentions(context) {
108
110
  syncSelection(collapsed ? 'after' : 'before');
109
111
  }
110
112
  catch (error) {
111
- if (!collapsed || !(0, RichTextEditorSerialization_1.isPositionInvalidError)(error))
113
+ if (!collapsed || !(0, RichTextEditorSerialization_1.isPositionInvalidError)(error)) {
112
114
  throw error;
115
+ }
113
116
  syncSelection('before');
114
117
  }
115
118
  const outcome = bridge.applyCommand({
@@ -121,8 +124,9 @@ function useRichTextEditorMentions(context) {
121
124
  }),
122
125
  },
123
126
  });
124
- if (outcome.type !== 'transaction' || !outcome.changed)
127
+ if (outcome.type !== 'transaction' || !outcome.changed) {
125
128
  return;
129
+ }
126
130
  latestRevisionRef.current = outcome.documentRevision;
127
131
  }
128
132
  catch (error) {
@@ -139,30 +143,36 @@ function useRichTextEditorMentions(context) {
139
143
  attrs,
140
144
  documentVersion: snapshot.documentVersion,
141
145
  });
142
- }, [
146
+ }, [addonsRef,
143
147
  afterLocalEngineMutation,
144
148
  bridge,
149
+ currentPushedUpdateEditorIdRef,
145
150
  document,
146
151
  documentDescriptor,
147
- documentHandle,
148
- resolveMentionInsertionAttrs,
149
- ]);
152
+ documentHandle.editorId,
153
+ documentHandle.isDestroyed,
154
+ editableRef,
155
+ latestRevisionRef,
156
+ resolveMentionInsertionAttrs]);
150
157
  const handleAddonEvent = (0, react_1.useCallback)((event) => {
151
- if (documentHandle.isDestroyed || !isForThisEditor(event.nativeEvent))
158
+ if (documentHandle.isDestroyed || !isForThisEditor(event.nativeEvent)) {
152
159
  return;
160
+ }
153
161
  let parsed;
154
162
  try {
155
163
  const value = JSON.parse(event.nativeEvent.eventJson);
156
- if (!(0, RichTextEditorSerialization_1.isRecord)(value) || typeof value.type !== 'string')
164
+ if (!(0, RichTextEditorSerialization_1.isRecord)(value) || typeof value.type !== 'string') {
157
165
  return;
166
+ }
158
167
  parsed = value;
159
168
  }
160
169
  catch {
161
170
  return;
162
171
  }
163
172
  const mentions = addonsRef.current?.mentions;
164
- if (!mentions)
173
+ if (!mentions) {
165
174
  return;
175
+ }
166
176
  const documentVersion = typeof parsed.documentVersion === 'string' ? parsed.documentVersion : undefined;
167
177
  if (parsed.type === 'mentionsQueryChange') {
168
178
  if (typeof parsed.query !== 'string' ||
@@ -190,9 +200,10 @@ function useRichTextEditorMentions(context) {
190
200
  !(0, RichTextEditorSerialization_1.isRecord)(parsed.attrs)) {
191
201
  return;
192
202
  }
193
- const suggestion = mentions.suggestions?.find((candidate) => candidate.key === parsed.suggestionKey);
194
- if (!suggestion)
203
+ const suggestion = mentions.suggestions?.find(candidate => candidate.key === parsed.suggestionKey);
204
+ if (!suggestion) {
195
205
  return;
206
+ }
196
207
  mentions.onSelect?.({
197
208
  trigger: parsed.trigger,
198
209
  suggestion,
@@ -214,9 +225,10 @@ function useRichTextEditorMentions(context) {
214
225
  parsed.range.head > 4294967295) {
215
226
  return;
216
227
  }
217
- const suggestion = mentions.suggestions?.find((candidate) => candidate.key === parsed.suggestionKey);
218
- if (!suggestion)
228
+ const suggestion = mentions.suggestions?.find(candidate => candidate.key === parsed.suggestionKey);
229
+ if (!suggestion) {
219
230
  return;
231
+ }
220
232
  insertMentionSuggestion({
221
233
  trigger: parsed.trigger,
222
234
  suggestion,
@@ -224,13 +236,19 @@ function useRichTextEditorMentions(context) {
224
236
  range: parsed.range,
225
237
  documentVersion,
226
238
  });
227
- }, [documentHandle, insertMentionSuggestion, isForThisEditor]);
239
+ }, [addonsRef,
240
+ documentHandle.isDestroyed,
241
+ insertMentionSuggestion,
242
+ isForThisEditor,
243
+ setMentionQuery]);
228
244
  const handleMentionSuggestionPress = (0, react_1.useCallback)((suggestion) => {
229
- if (mentionQuery == null)
245
+ if (mentionQuery == null) {
230
246
  return;
231
- const normalized = (0, addons_1.normalizeNativeEditorAddons)(addonsRef.current)?.mentions?.suggestions.find((candidate) => candidate.key === suggestion.key);
232
- if (normalized == null)
247
+ }
248
+ const normalized = (0, addons_1.normalizeNativeEditorAddons)(addonsRef.current)?.mentions?.suggestions.find(candidate => candidate.key === suggestion.key);
249
+ if (normalized == null) {
233
250
  return;
251
+ }
234
252
  setMentionQuery(null);
235
253
  insertMentionSuggestion({
236
254
  trigger: mentionQuery.trigger,
@@ -239,7 +257,7 @@ function useRichTextEditorMentions(context) {
239
257
  range: mentionQuery.range,
240
258
  documentVersion: mentionQuery.documentVersion,
241
259
  });
242
- }, [insertMentionSuggestion, mentionQuery]);
260
+ }, [addonsRef, insertMentionSuggestion, mentionQuery, setMentionQuery]);
243
261
  const mentionsEnabled = addons.mentions != null;
244
262
  const mentionTrigger = addons.mentions?.trigger?.trim() || '@';
245
263
  (0, react_1.useEffect)(() => {
@@ -257,13 +275,15 @@ function useRichTextEditorMentions(context) {
257
275
  return undefined;
258
276
  }
259
277
  const normalized = (0, addons_1.normalizeNativeEditorAddons)(addons)?.mentions?.suggestions;
260
- if (normalized == null)
278
+ if (normalized == null) {
261
279
  return undefined;
280
+ }
262
281
  const themes = {};
263
282
  for (const suggestion of mentionSuggestions) {
264
- const normalizedSuggestion = normalized.find((candidate) => candidate.key === suggestion.key);
265
- if (normalizedSuggestion == null)
283
+ const normalizedSuggestion = normalized.find(candidate => candidate.key === suggestion.key);
284
+ if (normalizedSuggestion == null) {
266
285
  continue;
286
+ }
267
287
  const selectionEvent = {
268
288
  trigger: mentionQuery.trigger,
269
289
  suggestion,
@@ -281,14 +301,13 @@ function useRichTextEditorMentions(context) {
281
301
  }
282
302
  }
283
303
  return Object.keys(themes).length > 0 ? themes : undefined;
284
- }, [
304
+ }, [activeStateRef,
285
305
  addons,
286
306
  mentionQuery,
287
307
  mentionSuggestionTheme,
288
308
  mentionSuggestions,
289
309
  resolveMentionSelectionAttrs,
290
- resolveMentionTheme,
291
- ]);
310
+ resolveMentionTheme]);
292
311
  (0, react_1.useEffect)(() => {
293
312
  if (!shouldPublishMentionSuggestions ||
294
313
  mentionQuery == null ||