@apollohg/react-native-prose-editor 0.5.16 → 0.5.17

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.
@@ -61,6 +61,9 @@ function mapToolbarItemsForNative(items, activeState, editable, onRequestLink, o
61
61
  function isImageDataUrl(value) {
62
62
  return /^data:image\//i.test(value.trim());
63
63
  }
64
+ function isRetryableNativeCommandBlock(reason) {
65
+ return reason === 'composition' || (react_native_1.Platform.OS === 'android' && reason === 'pendingUpdate');
66
+ }
64
67
  function isPromiseLike(value) {
65
68
  return (value != null &&
66
69
  typeof value === 'object' &&
@@ -398,15 +401,34 @@ function didContentChange(previousDocumentVersion, update) {
398
401
  typeof update.documentVersion !== 'number' ||
399
402
  update.documentVersion !== previousDocumentVersion);
400
403
  }
404
+ function documentVersionFromUpdateJson(json) {
405
+ try {
406
+ const parsed = JSON.parse(json);
407
+ return typeof parsed.documentVersion === 'number' ? parsed.documentVersion : null;
408
+ }
409
+ catch {
410
+ return null;
411
+ }
412
+ }
413
+ function isCurrentNativeEditorEvent(event, bridge) {
414
+ if (react_native_1.Platform.OS === 'android') {
415
+ return typeof event.editorId === 'number' && bridge?.editorId === event.editorId;
416
+ }
417
+ if (typeof event.editorId !== 'number')
418
+ return true;
419
+ return event.editorId === 0 || bridge?.editorId === event.editorId;
420
+ }
401
421
  function computeRenderedTextLength(elements) {
402
422
  let len = 0;
403
423
  let blockCount = 0;
404
424
  for (const el of elements) {
405
425
  if (el.type === 'blockStart' && el.listContext) {
406
- len += el.listContext.ordered ? `${el.listContext.index}. `.length : '• '.length;
426
+ len += el.listContext.ordered
427
+ ? unicodeScalarCount(`${el.listContext.index}. `)
428
+ : unicodeScalarCount('• ');
407
429
  }
408
430
  else if (el.type === 'textRun' && el.text) {
409
- len += el.text.length;
431
+ len += unicodeScalarCount(el.text);
410
432
  }
411
433
  else if (el.type === 'voidInline' ||
412
434
  el.type === 'voidBlock' ||
@@ -414,7 +436,7 @@ function computeRenderedTextLength(elements) {
414
436
  el.type === 'opaqueBlockAtom') {
415
437
  if (el.type === 'opaqueInlineAtom' || el.type === 'opaqueBlockAtom') {
416
438
  const visibleText = el.nodeType === 'mention' ? (el.label ?? '?') : `[${el.label ?? '?'}]`;
417
- len += visibleText.length;
439
+ len += unicodeScalarCount(visibleText);
418
440
  }
419
441
  else {
420
442
  // U+FFFC placeholder / hard break
@@ -508,6 +530,19 @@ function useSerializedValue(value, serialize, revision) {
508
530
  };
509
531
  return serialized;
510
532
  }
533
+ function doesLiveMentionQueryConflictWithNativeSelectRequest(request, currentQuery, requestDocumentVersion, currentDocumentVersion) {
534
+ if (currentQuery == null)
535
+ return false;
536
+ const currentQueryDocumentVersion = typeof currentQuery.documentVersion === 'number' ? currentQuery.documentVersion : null;
537
+ const isSameDocument = currentQueryDocumentVersion != null
538
+ ? currentQueryDocumentVersion === requestDocumentVersion
539
+ : requestDocumentVersion == null || requestDocumentVersion === currentDocumentVersion;
540
+ if (!isSameDocument)
541
+ return false;
542
+ return (currentQuery.trigger !== request.trigger ||
543
+ currentQuery.range.anchor !== request.range.anchor ||
544
+ currentQuery.range.head !== request.range.head);
545
+ }
511
546
  exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEditor({ initialContent, initialJSON, value, valueJSON, valueJSONRevision, schema, placeholder, editable = true, maxLength, autoFocus = false, autoCapitalize, autoCorrect, keyboardType, heightBehavior = 'autoGrow', showToolbar = true, toolbarPlacement = 'keyboard', toolbarItems = EditorToolbar_1.DEFAULT_EDITOR_TOOLBAR_ITEMS, onToolbarAction, onRequestLink, onRequestImage, autoDetectLinks = false, onContentChange, onContentChangeJSON, onSelectionChange, onActiveStateChange, onHistoryStateChange, onFocus, onBlur, style, containerStyle, theme, addons, remoteSelections, allowBase64Images = false, allowImageResizing = true, }, ref) {
512
547
  const bridgeRef = (0, react_1.useRef)(null);
513
548
  const nativeViewRef = (0, react_1.useRef)(null);
@@ -519,8 +554,20 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
519
554
  const registeredToolbarFrames = (0, EditorToolbar_1.useEditorToolbarFrames)();
520
555
  const [pendingNativeUpdate, setPendingNativeUpdate] = (0, react_1.useState)({
521
556
  json: undefined,
557
+ editorId: undefined,
522
558
  revision: 0,
523
559
  });
560
+ const pendingNativeUpdateRef = (0, react_1.useRef)(pendingNativeUpdate);
561
+ pendingNativeUpdateRef.current = pendingNativeUpdate;
562
+ const pendingNativeUpdateInFlightRef = (0, react_1.useRef)(null);
563
+ const [blockedNativeCommandRetry, setBlockedNativeCommandRetry] = (0, react_1.useState)(0);
564
+ const [detachedControlledSyncRetry, setDetachedControlledSyncRetry] = (0, react_1.useState)(0);
565
+ const [controlledNativeUpdateRetry, setControlledNativeUpdateRetry] = (0, react_1.useState)(0);
566
+ const pendingDetachedControlledSyncRef = (0, react_1.useRef)(false);
567
+ const pendingControlledSyncAfterNativeUpdateRef = (0, react_1.useRef)(false);
568
+ const pendingBlockedNativeCommandRetryRef = (0, react_1.useRef)(false);
569
+ const pendingNativeCommandRetryRef = (0, react_1.useRef)(null);
570
+ const blockedNativeCommandRetryTimerRef = (0, react_1.useRef)(null);
524
571
  const [autoGrowHeight, setAutoGrowHeight] = (0, react_1.useState)(null);
525
572
  // Toolbar state from EditorUpdate events
526
573
  const [activeState, setActiveState] = (0, react_1.useState)({
@@ -531,6 +578,8 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
531
578
  allowedMarks: [],
532
579
  insertableNodes: [],
533
580
  });
581
+ const activeStateRef = (0, react_1.useRef)(activeState);
582
+ activeStateRef.current = activeState;
534
583
  const [historyState, setHistoryState] = (0, react_1.useState)({
535
584
  canUndo: false,
536
585
  canRedo: false,
@@ -540,9 +589,28 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
540
589
  const selectionRef = (0, react_1.useRef)({ type: 'text', anchor: 0, head: 0 });
541
590
  const renderedTextLengthRef = (0, react_1.useRef)(0);
542
591
  const documentVersionRef = (0, react_1.useRef)(null);
592
+ const controlledHtmlSyncRef = (0, react_1.useRef)({
593
+ value: undefined,
594
+ documentVersion: null,
595
+ });
596
+ const controlledJsonSyncRef = (0, react_1.useRef)({
597
+ value: undefined,
598
+ documentVersion: null,
599
+ });
543
600
  const toolbarRef = (0, react_1.useRef)(null);
544
601
  const mentionQueryEventRef = (0, react_1.useRef)(null);
602
+ const mentionQueryEditorIdRef = (0, react_1.useRef)(null);
545
603
  mentionQueryEventRef.current = mentionQueryEvent;
604
+ const setMentionQueryEventState = (0, react_1.useCallback)((nextEvent, editorId) => {
605
+ mentionQueryEventRef.current = nextEvent;
606
+ mentionQueryEditorIdRef.current =
607
+ nextEvent == null
608
+ ? null
609
+ : typeof editorId === 'number'
610
+ ? editorId
611
+ : (bridgeRef.current?.editorId ?? null);
612
+ setMentionQueryEvent(nextEvent);
613
+ }, []);
546
614
  const toolbarItemsSerializationCacheRef = (0, react_1.useRef)(null);
547
615
  // Stable callback refs to avoid re-renders
548
616
  const onContentChangeRef = (0, react_1.useRef)(onContentChange);
@@ -574,27 +642,81 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
574
642
  const themeJson = useSerializedValue(theme, EditorTheme_1.serializeEditorTheme);
575
643
  const addonsJson = useSerializedValue(addons, addons_1.serializeEditorAddons);
576
644
  const remoteSelectionsJson = useSerializedValue(remoteSelections, (selections) => serializeRemoteSelections(selections));
645
+ const clearBlockedNativeCommandRetryTimer = (0, react_1.useCallback)(() => {
646
+ const timer = blockedNativeCommandRetryTimerRef.current;
647
+ if (timer == null)
648
+ return;
649
+ clearTimeout(timer);
650
+ blockedNativeCommandRetryTimerRef.current = null;
651
+ }, []);
652
+ const flushBlockedNativeCommandRetry = (0, react_1.useCallback)(() => {
653
+ const hadPendingRetry = pendingBlockedNativeCommandRetryRef.current ||
654
+ blockedNativeCommandRetryTimerRef.current != null;
655
+ if (!hadPendingRetry)
656
+ return;
657
+ pendingBlockedNativeCommandRetryRef.current = false;
658
+ clearBlockedNativeCommandRetryTimer();
659
+ setBlockedNativeCommandRetry((revision) => revision + 1);
660
+ }, [clearBlockedNativeCommandRetryTimer]);
661
+ const scheduleBlockedNativeCommandRetry = (0, react_1.useCallback)(() => {
662
+ pendingBlockedNativeCommandRetryRef.current = true;
663
+ if (blockedNativeCommandRetryTimerRef.current != null)
664
+ return;
665
+ blockedNativeCommandRetryTimerRef.current = setTimeout(() => {
666
+ blockedNativeCommandRetryTimerRef.current = null;
667
+ if (!pendingBlockedNativeCommandRetryRef.current)
668
+ return;
669
+ pendingBlockedNativeCommandRetryRef.current = false;
670
+ setBlockedNativeCommandRetry((revision) => revision + 1);
671
+ }, 50);
672
+ }, []);
673
+ const enqueueBlockedNativeCommandRetry = (0, react_1.useCallback)((retry) => {
674
+ pendingNativeCommandRetryRef.current = retry;
675
+ scheduleBlockedNativeCommandRetry();
676
+ }, [scheduleBlockedNativeCommandRetry]);
677
+ const clearStaleMentionQueryForDocumentVersion = (0, react_1.useCallback)((documentVersion) => {
678
+ const currentMentionQuery = mentionQueryEventRef.current;
679
+ if (currentMentionQuery != null &&
680
+ (typeof currentMentionQuery.documentVersion !== 'number' ||
681
+ currentMentionQuery.documentVersion < documentVersion)) {
682
+ setMentionQueryEventState(null);
683
+ }
684
+ }, [setMentionQueryEventState]);
577
685
  const syncStateFromUpdate = (0, react_1.useCallback)((update) => {
578
686
  if (!update)
579
687
  return;
688
+ activeStateRef.current = update.activeState;
580
689
  setActiveState(update.activeState);
581
690
  setHistoryState(update.historyState);
582
691
  selectionRef.current = update.selection;
583
692
  renderedTextLengthRef.current = computeRenderedTextLength(update.renderElements);
584
693
  if (typeof update.documentVersion === 'number') {
694
+ const previousDocumentVersion = documentVersionRef.current;
585
695
  documentVersionRef.current = update.documentVersion;
696
+ if (previousDocumentVersion == null ||
697
+ update.documentVersion > previousDocumentVersion) {
698
+ clearStaleMentionQueryForDocumentVersion(update.documentVersion);
699
+ }
586
700
  }
587
- }, []);
701
+ flushBlockedNativeCommandRetry();
702
+ }, [clearStaleMentionQueryForDocumentVersion, flushBlockedNativeCommandRetry]);
588
703
  const syncSelectionStateFromUpdate = (0, react_1.useCallback)((update) => {
589
704
  if (!update)
590
705
  return;
706
+ activeStateRef.current = update.activeState;
591
707
  setActiveState(update.activeState);
592
708
  setHistoryState(update.historyState);
593
709
  selectionRef.current = update.selection;
594
710
  if (typeof update.documentVersion === 'number') {
711
+ const previousDocumentVersion = documentVersionRef.current;
595
712
  documentVersionRef.current = update.documentVersion;
713
+ if (previousDocumentVersion == null ||
714
+ update.documentVersion > previousDocumentVersion) {
715
+ clearStaleMentionQueryForDocumentVersion(update.documentVersion);
716
+ }
596
717
  }
597
- }, []);
718
+ flushBlockedNativeCommandRetry();
719
+ }, [clearStaleMentionQueryForDocumentVersion, flushBlockedNativeCommandRetry]);
598
720
  const emitContentCallbacksForUpdate = (0, react_1.useCallback)((update, previousDocumentVersion) => {
599
721
  if (!update || !bridgeRef.current || bridgeRef.current.isDestroyed)
600
722
  return;
@@ -620,85 +742,222 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
620
742
  onContentChangeJSONRef.current?.(bridgeRef.current.getJson());
621
743
  }
622
744
  }, []);
745
+ const clearPendingNativeUpdateForCurrentEditor = (0, react_1.useCallback)(() => {
746
+ if (react_native_1.Platform.OS !== 'android')
747
+ return;
748
+ const editorId = bridgeRef.current?.editorId;
749
+ if (typeof editorId !== 'number')
750
+ return;
751
+ const inFlight = pendingNativeUpdateInFlightRef.current;
752
+ if (inFlight != null && inFlight.editorId === editorId) {
753
+ pendingNativeUpdateInFlightRef.current = null;
754
+ }
755
+ setPendingNativeUpdate((current) => {
756
+ if (current.json == null && current.editorId === editorId) {
757
+ return current;
758
+ }
759
+ const next = {
760
+ json: undefined,
761
+ editorId,
762
+ revision: current.revision + 1,
763
+ };
764
+ pendingNativeUpdateRef.current = next;
765
+ return next;
766
+ });
767
+ }, []);
768
+ const consumeBlockedCommandInfoForRetry = (0, react_1.useCallback)((bridge) => {
769
+ const blockedInfo = bridge.consumeLastCommandBlockedInfo();
770
+ if (!blockedInfo.blocked)
771
+ return blockedInfo;
772
+ if (blockedInfo.reason === 'detached') {
773
+ pendingDetachedControlledSyncRef.current = true;
774
+ }
775
+ else if (blockedInfo.reason === 'destroyed') {
776
+ pendingDetachedControlledSyncRef.current = false;
777
+ pendingBlockedNativeCommandRetryRef.current = false;
778
+ pendingNativeCommandRetryRef.current = null;
779
+ clearBlockedNativeCommandRetryTimer();
780
+ clearPendingNativeUpdateForCurrentEditor();
781
+ }
782
+ else {
783
+ scheduleBlockedNativeCommandRetry();
784
+ }
785
+ return blockedInfo;
786
+ }, [
787
+ clearBlockedNativeCommandRetryTimer,
788
+ clearPendingNativeUpdateForCurrentEditor,
789
+ scheduleBlockedNativeCommandRetry,
790
+ ]);
791
+ const consumeBlockedCommandForRetry = (0, react_1.useCallback)((bridge) => consumeBlockedCommandInfoForRetry(bridge).blocked, [consumeBlockedCommandInfoForRetry]);
792
+ (0, react_1.useEffect)(() => {
793
+ const retry = pendingNativeCommandRetryRef.current;
794
+ if (retry == null)
795
+ return;
796
+ pendingNativeCommandRetryRef.current = null;
797
+ retry();
798
+ }, [blockedNativeCommandRetry]);
799
+ const hasPendingNativeUpdateInFlightForCurrentEditor = (0, react_1.useCallback)(() => {
800
+ if (react_native_1.Platform.OS !== 'android')
801
+ return false;
802
+ const editorId = bridgeRef.current?.editorId;
803
+ if (typeof editorId !== 'number')
804
+ return false;
805
+ const inFlight = pendingNativeUpdateInFlightRef.current;
806
+ return inFlight != null && inFlight.editorId === editorId;
807
+ }, []);
623
808
  const applyUpdateToNativeView = (0, react_1.useCallback)((update, previousDocumentVersion, skipNativeApplyIfContentUnchanged = false) => {
624
809
  const contentChanged = didContentChange(previousDocumentVersion, update);
810
+ const handleApplyResult = (result) => {
811
+ if (result === false) {
812
+ setBlockedNativeCommandRetry((revision) => revision + 1);
813
+ return false;
814
+ }
815
+ return true;
816
+ };
625
817
  if (!skipNativeApplyIfContentUnchanged || contentChanged) {
626
818
  const updateJson = JSON.stringify(update);
627
819
  if (react_native_1.Platform.OS === 'android') {
628
- setPendingNativeUpdate((current) => ({
629
- json: updateJson,
630
- revision: current.revision + 1,
631
- }));
820
+ const editorId = bridgeRef.current?.editorId;
821
+ setPendingNativeUpdate((current) => {
822
+ const revision = current.revision + 1;
823
+ const next = {
824
+ json: updateJson,
825
+ editorId,
826
+ revision,
827
+ };
828
+ pendingNativeUpdateRef.current = next;
829
+ pendingNativeUpdateInFlightRef.current = { editorId, revision };
830
+ return next;
831
+ });
632
832
  }
633
833
  else {
634
834
  try {
635
835
  const applyResult = nativeViewRef.current?.applyEditorUpdate(updateJson);
636
836
  if (isPromiseLike(applyResult)) {
637
- void applyResult.catch(() => {
837
+ void applyResult
838
+ .then((result) => {
839
+ handleApplyResult(result);
840
+ })
841
+ .catch(() => {
638
842
  // The native view may already be torn down during navigation.
639
843
  });
640
844
  }
845
+ else {
846
+ return handleApplyResult(applyResult);
847
+ }
641
848
  }
642
849
  catch {
643
850
  // The native view may already be torn down during navigation.
644
851
  }
645
852
  }
646
853
  }
647
- return contentChanged;
854
+ return true;
648
855
  }, []);
649
856
  const maybeApplyAutoDetectedLink = (0, react_1.useCallback)((update, previousDocumentVersion) => {
650
- if (!autoDetectLinks ||
651
- !update ||
652
- !didContentChange(previousDocumentVersion, update) ||
653
- !bridgeRef.current ||
654
- bridgeRef.current.isDestroyed ||
655
- !update.activeState.allowedMarks.includes('link') ||
656
- update.selection.type !== 'text' ||
657
- update.selection.anchor == null ||
658
- update.selection.head == null ||
659
- update.selection.anchor !== update.selection.head) {
660
- return update;
661
- }
662
- const cursorDocPos = update.selection.head;
663
- const candidate = findAutoLinkCandidateInDocument(bridgeRef.current.getJson(), cursorDocPos);
664
- if (!candidate) {
665
- return update;
666
- }
667
- const scalarFrom = bridgeRef.current.docToScalar(candidate.docFrom);
668
- const scalarTo = bridgeRef.current.docToScalar(candidate.docTo);
669
- if (!(scalarTo > scalarFrom)) {
670
- return update;
671
- }
672
- const autoLinkUpdate = bridgeRef.current.setMarkAtSelectionScalar(scalarFrom, scalarTo, 'link', { href: candidate.href });
673
- if (!autoLinkUpdate) {
674
- return update;
675
- }
676
- bridgeRef.current.setSelection(update.selection.anchor, update.selection.head);
677
- const selectionState = bridgeRef.current.getSelectionState();
678
- if (selectionState) {
679
- autoLinkUpdate.selection = selectionState.selection;
680
- autoLinkUpdate.activeState = selectionState.activeState;
681
- autoLinkUpdate.historyState = selectionState.historyState;
682
- if (typeof selectionState.documentVersion === 'number') {
683
- autoLinkUpdate.documentVersion = selectionState.documentVersion;
857
+ const applyAutoLink = (candidateUpdate, allowPreflightRetry) => {
858
+ if (!autoDetectLinks ||
859
+ !candidateUpdate ||
860
+ !didContentChange(previousDocumentVersion, candidateUpdate) ||
861
+ !bridgeRef.current ||
862
+ bridgeRef.current.isDestroyed ||
863
+ !candidateUpdate.activeState.allowedMarks.includes('link') ||
864
+ candidateUpdate.selection.type !== 'text' ||
865
+ candidateUpdate.selection.anchor == null ||
866
+ candidateUpdate.selection.head == null ||
867
+ candidateUpdate.selection.anchor !== candidateUpdate.selection.head) {
868
+ return candidateUpdate;
869
+ }
870
+ const cursorDocPos = candidateUpdate.selection.head;
871
+ const candidate = findAutoLinkCandidateInDocument(bridgeRef.current.getJson(), cursorDocPos);
872
+ if (!candidate) {
873
+ return candidateUpdate;
874
+ }
875
+ const scalarFrom = bridgeRef.current.docToScalar(candidate.docFrom);
876
+ const scalarTo = bridgeRef.current.docToScalar(candidate.docTo);
877
+ if (!(scalarTo > scalarFrom)) {
878
+ return candidateUpdate;
879
+ }
880
+ const autoLinkUpdate = bridgeRef.current.setMarkAtSelectionScalar(scalarFrom, scalarTo, 'link', { href: candidate.href });
881
+ if (!autoLinkUpdate) {
882
+ const preflightUpdate = bridgeRef.current.consumeLastCommandPreflightUpdate();
883
+ if (preflightUpdate) {
884
+ return allowPreflightRetry
885
+ ? applyAutoLink(preflightUpdate, false)
886
+ : preflightUpdate;
887
+ }
888
+ consumeBlockedCommandForRetry(bridgeRef.current);
889
+ return candidateUpdate;
890
+ }
891
+ bridgeRef.current.setSelection(candidateUpdate.selection.anchor, candidateUpdate.selection.head);
892
+ const selectionState = bridgeRef.current.getSelectionState();
893
+ if (selectionState) {
894
+ autoLinkUpdate.selection = selectionState.selection;
895
+ autoLinkUpdate.activeState = selectionState.activeState;
896
+ autoLinkUpdate.historyState = selectionState.historyState;
897
+ if (typeof selectionState.documentVersion === 'number') {
898
+ autoLinkUpdate.documentVersion = selectionState.documentVersion;
899
+ }
684
900
  }
901
+ else {
902
+ autoLinkUpdate.selection = candidateUpdate.selection;
903
+ }
904
+ return autoLinkUpdate;
905
+ };
906
+ return applyAutoLink(update, true);
907
+ }, [autoDetectLinks, consumeBlockedCommandForRetry]);
908
+ const syncNativeUpdateFromBridge = (0, react_1.useCallback)((nativeUpdate, previousDocumentVersion, options) => {
909
+ const update = maybeApplyAutoDetectedLink(nativeUpdate, previousDocumentVersion);
910
+ if (!update)
911
+ return null;
912
+ if (update !== nativeUpdate) {
913
+ applyUpdateToNativeView(update, previousDocumentVersion, options?.skipNativeApplyIfContentUnchanged);
685
914
  }
686
- else {
687
- autoLinkUpdate.selection = update.selection;
915
+ syncStateFromUpdate(update);
916
+ onActiveStateChangeRef.current?.(update.activeState);
917
+ onHistoryStateChangeRef.current?.(update.historyState);
918
+ if (!options?.suppressContentCallbacks) {
919
+ emitContentCallbacksForUpdate(update, previousDocumentVersion);
688
920
  }
689
- return autoLinkUpdate;
690
- }, [autoDetectLinks]);
921
+ onSelectionChangeRef.current?.(update.selection);
922
+ return update;
923
+ }, [
924
+ applyUpdateToNativeView,
925
+ emitContentCallbacksForUpdate,
926
+ maybeApplyAutoDetectedLink,
927
+ syncStateFromUpdate,
928
+ ]);
691
929
  // Warn if both value and valueJSON are set
692
930
  if (__DEV__ && value != null && valueJSON != null) {
693
931
  console.warn('NativeRichTextEditor: value and valueJSON are mutually exclusive. ' +
694
932
  'Only value will be used.');
695
933
  }
696
934
  const runAndApply = (0, react_1.useCallback)((mutate, options) => {
935
+ if (hasPendingNativeUpdateInFlightForCurrentEditor()) {
936
+ if (options?.retryBlockedCommand != null) {
937
+ enqueueBlockedNativeCommandRetry(options.retryBlockedCommand);
938
+ options.onBlockedCommandRetryQueued?.();
939
+ }
940
+ return null;
941
+ }
697
942
  const previousDocumentVersion = documentVersionRef.current;
698
943
  const preservedSelection = options?.preserveLiveTextSelection === true ? selectionRef.current : null;
699
944
  let update = mutate();
700
- if (!update)
945
+ if (!update) {
946
+ const bridge = bridgeRef.current;
947
+ if (bridge != null && !bridge.isDestroyed) {
948
+ const preflightUpdate = bridge.consumeLastCommandPreflightUpdate();
949
+ if (preflightUpdate) {
950
+ syncNativeUpdateFromBridge(preflightUpdate, previousDocumentVersion);
951
+ }
952
+ const blockedInfo = consumeBlockedCommandInfoForRetry(bridge);
953
+ if (options?.retryBlockedCommand != null &&
954
+ isRetryableNativeCommandBlock(blockedInfo.reason)) {
955
+ enqueueBlockedNativeCommandRetry(options.retryBlockedCommand);
956
+ options.onBlockedCommandRetryQueued?.();
957
+ }
958
+ }
701
959
  return null;
960
+ }
702
961
  if (!options?.skipAutoDetectLinks) {
703
962
  update = maybeApplyAutoDetectedLink(update, previousDocumentVersion);
704
963
  if (!update) {
@@ -708,6 +967,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
708
967
  if (preservedSelection?.type === 'text' &&
709
968
  typeof preservedSelection.anchor === 'number' &&
710
969
  typeof preservedSelection.head === 'number' &&
970
+ !didContentChange(previousDocumentVersion, update) &&
711
971
  bridgeRef.current != null &&
712
972
  !bridgeRef.current.isDestroyed) {
713
973
  bridgeRef.current.setSelection(preservedSelection.anchor, preservedSelection.head);
@@ -728,10 +988,56 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
728
988
  return update;
729
989
  }, [
730
990
  applyUpdateToNativeView,
991
+ consumeBlockedCommandInfoForRetry,
992
+ enqueueBlockedNativeCommandRetry,
731
993
  emitContentCallbacksForUpdate,
994
+ hasPendingNativeUpdateInFlightForCurrentEditor,
732
995
  maybeApplyAutoDetectedLink,
996
+ syncNativeUpdateFromBridge,
733
997
  syncStateFromUpdate,
734
998
  ]);
999
+ const prepareBridgeForExternalContentRead = (0, react_1.useCallback)((options) => {
1000
+ const bridge = bridgeRef.current;
1001
+ if (!bridge || bridge.isDestroyed)
1002
+ return false;
1003
+ const previousDocumentVersion = documentVersionRef.current;
1004
+ const ready = bridge.prepareForNativeCommand();
1005
+ const preflightUpdate = bridge.consumeLastCommandPreflightUpdate();
1006
+ if (preflightUpdate) {
1007
+ syncNativeUpdateFromBridge(preflightUpdate, previousDocumentVersion);
1008
+ }
1009
+ if (!ready) {
1010
+ consumeBlockedCommandForRetry(bridge);
1011
+ return false;
1012
+ }
1013
+ return (options?.skipWhenContentChanged !== true ||
1014
+ !didContentChange(previousDocumentVersion, preflightUpdate));
1015
+ }, [consumeBlockedCommandForRetry, syncNativeUpdateFromBridge]);
1016
+ const syncPreflightUpdateFromNativeEvent = (0, react_1.useCallback)((updateJson) => {
1017
+ if (typeof updateJson !== 'string' || updateJson.length === 0) {
1018
+ return true;
1019
+ }
1020
+ const bridge = bridgeRef.current;
1021
+ if (!bridge || bridge.isDestroyed)
1022
+ return false;
1023
+ const previousDocumentVersion = documentVersionRef.current;
1024
+ try {
1025
+ const parsed = JSON.parse(updateJson);
1026
+ if (react_native_1.Platform.OS === 'android' &&
1027
+ typeof previousDocumentVersion === 'number' &&
1028
+ typeof parsed.documentVersion !== 'number') {
1029
+ return false;
1030
+ }
1031
+ const update = bridge.parseUpdateJson(updateJson);
1032
+ if (!update)
1033
+ return false;
1034
+ syncNativeUpdateFromBridge(update, previousDocumentVersion);
1035
+ return true;
1036
+ }
1037
+ catch {
1038
+ return false;
1039
+ }
1040
+ }, [syncNativeUpdateFromBridge]);
735
1041
  (0, react_1.useEffect)(() => {
736
1042
  const bridgeConfig = maxLength != null || serializedSchemaJson || allowBase64Images
737
1043
  ? {
@@ -762,39 +1068,159 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
762
1068
  bridge.destroy();
763
1069
  bridgeRef.current = null;
764
1070
  nativeViewRef.current = null;
1071
+ pendingNativeUpdateInFlightRef.current = null;
1072
+ pendingDetachedControlledSyncRef.current = false;
1073
+ pendingControlledSyncAfterNativeUpdateRef.current = false;
1074
+ pendingBlockedNativeCommandRetryRef.current = false;
1075
+ pendingNativeCommandRetryRef.current = null;
1076
+ documentVersionRef.current = null;
1077
+ mentionQueryEventRef.current = null;
1078
+ mentionQueryEditorIdRef.current = null;
1079
+ clearBlockedNativeCommandRetryTimer();
1080
+ setMentionQueryEvent(null);
1081
+ setPendingNativeUpdate((current) => {
1082
+ const next = {
1083
+ json: undefined,
1084
+ editorId: undefined,
1085
+ revision: current.revision + 1,
1086
+ };
1087
+ pendingNativeUpdateRef.current = next;
1088
+ return next;
1089
+ });
765
1090
  setEditorInstanceId(0);
766
1091
  setIsReady(false);
767
1092
  };
768
1093
  // eslint-disable-next-line react-hooks/exhaustive-deps
769
- }, [maxLength, syncStateFromUpdate, allowBase64Images, serializedSchemaJson]);
1094
+ }, [
1095
+ maxLength,
1096
+ syncStateFromUpdate,
1097
+ allowBase64Images,
1098
+ serializedSchemaJson,
1099
+ clearBlockedNativeCommandRetryTimer,
1100
+ ]);
770
1101
  (0, react_1.useEffect)(() => {
771
1102
  if (value == null)
772
1103
  return;
773
1104
  if (!bridgeRef.current || bridgeRef.current.isDestroyed)
774
1105
  return;
1106
+ const previousSync = controlledHtmlSyncRef.current;
1107
+ const didControlledValueChange = previousSync.value !== value;
1108
+ const didDocumentAdvanceForSameValue = !didControlledValueChange &&
1109
+ previousSync.documentVersion !== documentVersionRef.current;
1110
+ if (!prepareBridgeForExternalContentRead({
1111
+ skipWhenContentChanged: !didControlledValueChange,
1112
+ })) {
1113
+ controlledHtmlSyncRef.current = {
1114
+ value,
1115
+ documentVersion: documentVersionRef.current,
1116
+ };
1117
+ return;
1118
+ }
1119
+ if (didDocumentAdvanceForSameValue) {
1120
+ controlledHtmlSyncRef.current = {
1121
+ value,
1122
+ documentVersion: documentVersionRef.current,
1123
+ };
1124
+ return;
1125
+ }
775
1126
  const currentHtml = bridgeRef.current.getHtml();
776
- if (currentHtml === value)
1127
+ if (currentHtml === value) {
1128
+ clearPendingNativeUpdateForCurrentEditor();
1129
+ controlledHtmlSyncRef.current = {
1130
+ value,
1131
+ documentVersion: documentVersionRef.current,
1132
+ };
1133
+ return;
1134
+ }
1135
+ if (hasPendingNativeUpdateInFlightForCurrentEditor()) {
1136
+ pendingControlledSyncAfterNativeUpdateRef.current = true;
777
1137
  return;
778
- runAndApply(() => bridgeRef.current.replaceHtml(value), {
1138
+ }
1139
+ const update = runAndApply(() => bridgeRef.current.replaceHtml(value), {
779
1140
  suppressContentCallbacks: true,
780
1141
  preserveLiveTextSelection: true,
781
1142
  skipAutoDetectLinks: true,
782
1143
  });
783
- }, [value, runAndApply]);
1144
+ if (!update && hasPendingNativeUpdateInFlightForCurrentEditor()) {
1145
+ pendingControlledSyncAfterNativeUpdateRef.current = true;
1146
+ return;
1147
+ }
1148
+ controlledHtmlSyncRef.current = {
1149
+ value,
1150
+ documentVersion: documentVersionRef.current,
1151
+ };
1152
+ }, [
1153
+ value,
1154
+ runAndApply,
1155
+ blockedNativeCommandRetry,
1156
+ controlledNativeUpdateRetry,
1157
+ detachedControlledSyncRetry,
1158
+ prepareBridgeForExternalContentRead,
1159
+ clearPendingNativeUpdateForCurrentEditor,
1160
+ hasPendingNativeUpdateInFlightForCurrentEditor,
1161
+ ]);
784
1162
  (0, react_1.useEffect)(() => {
785
1163
  if (serializedValueJson == null || value != null)
786
1164
  return;
787
1165
  if (!bridgeRef.current || bridgeRef.current.isDestroyed)
788
1166
  return;
1167
+ const previousSync = controlledJsonSyncRef.current;
1168
+ const didControlledValueChange = previousSync.value !== serializedValueJson;
1169
+ const didDocumentAdvanceForSameValue = !didControlledValueChange &&
1170
+ previousSync.documentVersion !== documentVersionRef.current;
1171
+ if (!prepareBridgeForExternalContentRead({
1172
+ skipWhenContentChanged: !didControlledValueChange,
1173
+ })) {
1174
+ controlledJsonSyncRef.current = {
1175
+ value: serializedValueJson,
1176
+ documentVersion: documentVersionRef.current,
1177
+ };
1178
+ return;
1179
+ }
1180
+ if (didDocumentAdvanceForSameValue) {
1181
+ controlledJsonSyncRef.current = {
1182
+ value: serializedValueJson,
1183
+ documentVersion: documentVersionRef.current,
1184
+ };
1185
+ return;
1186
+ }
789
1187
  const currentJson = bridgeRef.current.getJsonString();
790
- if (currentJson === serializedValueJson)
1188
+ if (currentJson === serializedValueJson) {
1189
+ clearPendingNativeUpdateForCurrentEditor();
1190
+ controlledJsonSyncRef.current = {
1191
+ value: serializedValueJson,
1192
+ documentVersion: documentVersionRef.current,
1193
+ };
1194
+ return;
1195
+ }
1196
+ if (hasPendingNativeUpdateInFlightForCurrentEditor()) {
1197
+ pendingControlledSyncAfterNativeUpdateRef.current = true;
791
1198
  return;
792
- runAndApply(() => bridgeRef.current.replaceJsonString(serializedValueJson), {
1199
+ }
1200
+ const update = runAndApply(() => bridgeRef.current.replaceJsonString(serializedValueJson), {
793
1201
  suppressContentCallbacks: true,
794
1202
  preserveLiveTextSelection: true,
795
1203
  skipAutoDetectLinks: true,
796
1204
  });
797
- }, [serializedValueJson, value, runAndApply]);
1205
+ if (!update && hasPendingNativeUpdateInFlightForCurrentEditor()) {
1206
+ pendingControlledSyncAfterNativeUpdateRef.current = true;
1207
+ return;
1208
+ }
1209
+ controlledJsonSyncRef.current = {
1210
+ value: serializedValueJson,
1211
+ documentVersion: documentVersionRef.current,
1212
+ };
1213
+ }, [
1214
+ serializedValueJson,
1215
+ value,
1216
+ runAndApply,
1217
+ blockedNativeCommandRetry,
1218
+ controlledNativeUpdateRetry,
1219
+ detachedControlledSyncRetry,
1220
+ prepareBridgeForExternalContentRead,
1221
+ clearPendingNativeUpdateForCurrentEditor,
1222
+ hasPendingNativeUpdateInFlightForCurrentEditor,
1223
+ ]);
798
1224
  const updateToolbarFrame = (0, react_1.useCallback)(() => {
799
1225
  const toolbar = toolbarRef.current;
800
1226
  if (!toolbar) {
@@ -826,62 +1252,96 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
826
1252
  }
827
1253
  }, [heightBehavior]);
828
1254
  const handleUpdate = (0, react_1.useCallback)((event) => {
829
- if (!bridgeRef.current || bridgeRef.current.isDestroyed)
1255
+ const bridge = bridgeRef.current;
1256
+ if (!bridge || bridge.isDestroyed)
1257
+ return;
1258
+ if (!isCurrentNativeEditorEvent(event.nativeEvent, bridge))
830
1259
  return;
831
1260
  try {
832
1261
  const previousDocumentVersion = documentVersionRef.current;
833
- const nativeUpdate = bridgeRef.current.parseUpdateJson(event.nativeEvent.updateJson);
1262
+ if (react_native_1.Platform.OS === 'android' && typeof previousDocumentVersion === 'number') {
1263
+ const parsed = JSON.parse(event.nativeEvent.updateJson);
1264
+ if (typeof parsed.documentVersion !== 'number') {
1265
+ return;
1266
+ }
1267
+ }
1268
+ const nativeUpdate = bridge.parseUpdateJson(event.nativeEvent.updateJson, { rejectSameDocumentVersion: true });
834
1269
  if (!nativeUpdate)
835
1270
  return;
836
- const update = maybeApplyAutoDetectedLink(nativeUpdate, previousDocumentVersion);
837
- if (!update)
838
- return;
839
- if (update !== nativeUpdate) {
840
- applyUpdateToNativeView(update, previousDocumentVersion);
841
- }
842
- syncStateFromUpdate(update);
843
- onActiveStateChangeRef.current?.(update.activeState);
844
- onHistoryStateChangeRef.current?.(update.historyState);
845
- emitContentCallbacksForUpdate(update, previousDocumentVersion);
846
- onSelectionChangeRef.current?.(update.selection);
1271
+ syncNativeUpdateFromBridge(nativeUpdate, previousDocumentVersion);
847
1272
  }
848
1273
  catch {
849
1274
  // Invalid JSON from native — skip
850
1275
  }
851
- }, [
852
- applyUpdateToNativeView,
853
- emitContentCallbacksForUpdate,
854
- maybeApplyAutoDetectedLink,
855
- syncStateFromUpdate,
856
- ]);
1276
+ }, [syncNativeUpdateFromBridge]);
857
1277
  const handleSelectionChange = (0, react_1.useCallback)((event) => {
858
- if (!bridgeRef.current || bridgeRef.current.isDestroyed)
1278
+ const bridge = bridgeRef.current;
1279
+ if (!bridge || bridge.isDestroyed)
1280
+ return;
1281
+ if (!isCurrentNativeEditorEvent(event.nativeEvent, bridge))
1282
+ return;
1283
+ const { anchor, head, stateJson, documentVersion } = event.nativeEvent;
1284
+ const currentDocumentVersion = documentVersionRef.current;
1285
+ if (typeof documentVersion === 'number' &&
1286
+ typeof currentDocumentVersion === 'number' &&
1287
+ documentVersion < currentDocumentVersion) {
859
1288
  return;
860
- const { anchor, head, stateJson } = event.nativeEvent;
861
- let selection;
862
- if (anchor === 0 &&
863
- head >= renderedTextLengthRef.current &&
864
- renderedTextLengthRef.current > 0) {
865
- selection = { type: 'all' };
866
- }
867
- else {
868
- selection = { type: 'text', anchor, head };
869
1289
  }
870
- bridgeRef.current.updateSelectionFromNative(anchor, head);
871
1290
  let currentState = null;
872
1291
  if (typeof stateJson === 'string' && stateJson.length > 0) {
1292
+ if (react_native_1.Platform.OS === 'android' &&
1293
+ typeof currentDocumentVersion === 'number') {
1294
+ const stateDocumentVersion = documentVersionFromUpdateJson(stateJson);
1295
+ if (typeof stateDocumentVersion !== 'number' ||
1296
+ stateDocumentVersion < currentDocumentVersion) {
1297
+ return;
1298
+ }
1299
+ }
873
1300
  try {
874
- currentState = bridgeRef.current.parseUpdateJson(stateJson);
1301
+ currentState = bridge.parseUpdateJson(stateJson);
1302
+ if (!currentState)
1303
+ return;
875
1304
  }
876
1305
  catch {
877
- currentState = bridgeRef.current.getSelectionState();
1306
+ currentState = bridge.getSelectionState();
878
1307
  }
879
1308
  }
880
1309
  else {
881
- currentState = bridgeRef.current.getSelectionState();
1310
+ currentState = bridge.getSelectionState();
1311
+ }
1312
+ if (currentState != null &&
1313
+ typeof currentState.documentVersion === 'number' &&
1314
+ typeof currentDocumentVersion === 'number' &&
1315
+ currentState.documentVersion < currentDocumentVersion) {
1316
+ return;
1317
+ }
1318
+ if (currentState != null &&
1319
+ react_native_1.Platform.OS === 'android' &&
1320
+ typeof currentDocumentVersion === 'number' &&
1321
+ typeof currentState.documentVersion !== 'number') {
1322
+ return;
1323
+ }
1324
+ if (currentState == null &&
1325
+ react_native_1.Platform.OS === 'android' &&
1326
+ typeof currentDocumentVersion === 'number') {
1327
+ return;
1328
+ }
1329
+ let selection;
1330
+ const selectionStart = Math.min(anchor, head);
1331
+ const selectionEnd = Math.max(anchor, head);
1332
+ if (selectionStart === 0 &&
1333
+ selectionEnd >= renderedTextLengthRef.current &&
1334
+ renderedTextLengthRef.current > 0) {
1335
+ selection = { type: 'all' };
1336
+ }
1337
+ else {
1338
+ selection = { type: 'text', anchor, head };
882
1339
  }
883
- syncSelectionStateFromUpdate(currentState);
884
1340
  const nextSelection = selection.type === 'all' ? selection : (currentState?.selection ?? selection);
1341
+ if (currentState == null) {
1342
+ bridge.updateSelectionFromNative(anchor, head);
1343
+ }
1344
+ syncSelectionStateFromUpdate(currentState);
885
1345
  selectionRef.current = nextSelection;
886
1346
  if (currentState) {
887
1347
  onActiveStateChangeRef.current?.(currentState.activeState);
@@ -899,6 +1359,8 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
899
1359
  }, 50);
900
1360
  }, []);
901
1361
  const handleFocusChange = (0, react_1.useCallback)((event) => {
1362
+ if (!isCurrentNativeEditorEvent(event.nativeEvent, bridgeRef.current))
1363
+ return;
902
1364
  const { isFocused: focused } = event.nativeEvent;
903
1365
  if (!focused &&
904
1366
  editable &&
@@ -912,7 +1374,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
912
1374
  isFocusedRef.current = focused;
913
1375
  setIsFocused(focused);
914
1376
  if (!focused) {
915
- setMentionQueryEvent(null);
1377
+ setMentionQueryEventState(null);
916
1378
  }
917
1379
  if (focused) {
918
1380
  if (!wasFocused) {
@@ -922,14 +1384,16 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
922
1384
  else if (wasFocused) {
923
1385
  onBlurRef.current?.();
924
1386
  }
925
- }, [editable, refocusAfterToolbarInteraction]);
1387
+ }, [editable, refocusAfterToolbarInteraction, setMentionQueryEventState]);
926
1388
  (0, react_1.useEffect)(() => {
927
1389
  if (addons?.mentions != null) {
928
1390
  return;
929
1391
  }
930
- setMentionQueryEvent(null);
931
- }, [addons?.mentions]);
1392
+ setMentionQueryEventState(null);
1393
+ }, [addons?.mentions, setMentionQueryEventState]);
932
1394
  const handleContentHeightChange = (0, react_1.useCallback)((event) => {
1395
+ if (!isCurrentNativeEditorEvent(event.nativeEvent, bridgeRef.current))
1396
+ return;
933
1397
  if (heightBehavior !== 'autoGrow')
934
1398
  return;
935
1399
  const density = react_native_1.Platform.OS === 'android' ? react_native_1.PixelRatio.get() : 1;
@@ -938,6 +1402,47 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
938
1402
  return;
939
1403
  setAutoGrowHeight((prev) => (prev === nextHeight ? prev : nextHeight));
940
1404
  }, [autoGrowHeight, heightBehavior]);
1405
+ const handleEditorReady = (0, react_1.useCallback)((event) => {
1406
+ if (!isCurrentNativeEditorEvent(event.nativeEvent, bridgeRef.current))
1407
+ return;
1408
+ const editorId = bridgeRef.current?.editorId;
1409
+ const acknowledgedRevision = event.nativeEvent.editorUpdateRevision;
1410
+ const inFlight = pendingNativeUpdateInFlightRef.current;
1411
+ let didClearInFlight = false;
1412
+ if (inFlight != null) {
1413
+ const matchesRevision = typeof acknowledgedRevision === 'number' &&
1414
+ inFlight.editorId === editorId &&
1415
+ inFlight.revision === acknowledgedRevision;
1416
+ if (!matchesRevision) {
1417
+ return;
1418
+ }
1419
+ pendingNativeUpdateInFlightRef.current = null;
1420
+ didClearInFlight = true;
1421
+ setPendingNativeUpdate((current) => {
1422
+ if (current.editorId !== editorId ||
1423
+ current.revision !== acknowledgedRevision ||
1424
+ current.json == null) {
1425
+ return current;
1426
+ }
1427
+ const next = {
1428
+ json: undefined,
1429
+ editorId,
1430
+ revision: current.revision,
1431
+ };
1432
+ pendingNativeUpdateRef.current = next;
1433
+ return next;
1434
+ });
1435
+ }
1436
+ flushBlockedNativeCommandRetry();
1437
+ if (didClearInFlight && pendingControlledSyncAfterNativeUpdateRef.current) {
1438
+ pendingControlledSyncAfterNativeUpdateRef.current = false;
1439
+ setControlledNativeUpdateRetry((revision) => revision + 1);
1440
+ }
1441
+ if (!pendingDetachedControlledSyncRef.current)
1442
+ return;
1443
+ pendingDetachedControlledSyncRef.current = false;
1444
+ setDetachedControlledSyncRetry((revision) => revision + 1);
1445
+ }, [flushBlockedNativeCommandRetry]);
941
1446
  const restoreSelection = (0, react_1.useCallback)((selection) => {
942
1447
  if (selection.type === 'text') {
943
1448
  const { anchor, head } = selection;
@@ -955,6 +1460,134 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
955
1460
  bridgeRef.current?.setSelection(pos, pos);
956
1461
  }
957
1462
  }, []);
1463
+ const isAsyncRequestCurrent = (0, react_1.useCallback)((requestEditorId, requestDocumentVersion) => {
1464
+ const bridge = bridgeRef.current;
1465
+ return (bridge != null &&
1466
+ !bridge.isDestroyed &&
1467
+ bridge.editorId === requestEditorId &&
1468
+ documentVersionRef.current === requestDocumentVersion);
1469
+ }, []);
1470
+ const scalarRangeFromSelection = (0, react_1.useCallback)((selection) => {
1471
+ const bridge = bridgeRef.current;
1472
+ if (!bridge || bridge.isDestroyed)
1473
+ return null;
1474
+ if (selection.type === 'text') {
1475
+ const anchor = selection.anchor ?? 0;
1476
+ const head = selection.head ?? anchor;
1477
+ return {
1478
+ anchor: bridge.docToScalar(anchor),
1479
+ head: bridge.docToScalar(head),
1480
+ };
1481
+ }
1482
+ if (selection.type === 'node' && typeof selection.pos === 'number') {
1483
+ const scalar = bridge.docToScalar(selection.pos);
1484
+ return { anchor: scalar, head: scalar };
1485
+ }
1486
+ return null;
1487
+ }, []);
1488
+ const isCommandRetryScopeCurrent = (0, react_1.useCallback)((scope) => {
1489
+ const bridge = bridgeRef.current;
1490
+ if (!bridge || bridge.isDestroyed || bridge.editorId !== scope.editorId) {
1491
+ return false;
1492
+ }
1493
+ const currentDocumentVersion = documentVersionRef.current;
1494
+ const hasMentionScope = scope.mentionQuery != null ||
1495
+ scope.mentionRange != null ||
1496
+ scope.nativeMentionSelectRequest != null;
1497
+ if (currentDocumentVersion !== scope.documentVersion) {
1498
+ if (!hasMentionScope) {
1499
+ return false;
1500
+ }
1501
+ if (typeof currentDocumentVersion === 'number' &&
1502
+ typeof scope.documentVersion === 'number' &&
1503
+ currentDocumentVersion > scope.documentVersion) {
1504
+ return false;
1505
+ }
1506
+ }
1507
+ if (hasMentionScope &&
1508
+ typeof currentDocumentVersion === 'number' &&
1509
+ scope.documentVersion == null) {
1510
+ return false;
1511
+ }
1512
+ if (typeof scope.scalarAnchor === 'number' &&
1513
+ typeof scope.scalarHead === 'number') {
1514
+ const currentRange = scalarRangeFromSelection(selectionRef.current);
1515
+ if (currentRange?.anchor !== scope.scalarAnchor ||
1516
+ currentRange?.head !== scope.scalarHead) {
1517
+ return false;
1518
+ }
1519
+ }
1520
+ if (scope.mentionQuery) {
1521
+ const mentionQueryEditorId = mentionQueryEditorIdRef.current;
1522
+ if (mentionQueryEditorId != null &&
1523
+ mentionQueryEditorId !== scope.editorId) {
1524
+ return false;
1525
+ }
1526
+ const currentQuery = mentionQueryEventRef.current;
1527
+ if (currentQuery == null ||
1528
+ currentQuery.trigger !== scope.mentionQuery.trigger ||
1529
+ currentQuery.query !== scope.mentionQuery.query ||
1530
+ currentQuery.range.anchor !== scope.mentionQuery.range.anchor ||
1531
+ currentQuery.range.head !== scope.mentionQuery.range.head ||
1532
+ currentQuery.documentVersion !== scope.mentionQuery.documentVersion) {
1533
+ return false;
1534
+ }
1535
+ }
1536
+ if (scope.mentionRange) {
1537
+ const mentionQueryEditorId = mentionQueryEditorIdRef.current;
1538
+ if (mentionQueryEditorId != null &&
1539
+ mentionQueryEditorId !== scope.editorId) {
1540
+ return false;
1541
+ }
1542
+ const currentQuery = mentionQueryEventRef.current;
1543
+ if (currentQuery == null ||
1544
+ currentQuery.range.anchor !== scope.mentionRange.anchor ||
1545
+ currentQuery.range.head !== scope.mentionRange.head ||
1546
+ (currentQuery.documentVersion ?? null) !== scope.documentVersion) {
1547
+ return false;
1548
+ }
1549
+ }
1550
+ if (scope.nativeMentionSelectRequest) {
1551
+ const mentionQueryEditorId = mentionQueryEditorIdRef.current;
1552
+ if (mentionQueryEditorId != null &&
1553
+ mentionQueryEditorId !== scope.editorId) {
1554
+ return false;
1555
+ }
1556
+ if (doesLiveMentionQueryConflictWithNativeSelectRequest(scope.nativeMentionSelectRequest, mentionQueryEventRef.current, scope.documentVersion, currentDocumentVersion)) {
1557
+ return false;
1558
+ }
1559
+ }
1560
+ return true;
1561
+ }, [scalarRangeFromSelection]);
1562
+ const runAndApplyWithCommandRetry = (0, react_1.useCallback)((scope, mutate, options, onApplied) => {
1563
+ let didQueueRetry = false;
1564
+ let run = null;
1565
+ const runIfScopeCurrent = () => {
1566
+ if (!isCommandRetryScopeCurrent(scope))
1567
+ return null;
1568
+ return run?.() ?? null;
1569
+ };
1570
+ const retry = () => {
1571
+ const update = runIfScopeCurrent();
1572
+ if (update) {
1573
+ onApplied?.(update);
1574
+ }
1575
+ return update != null;
1576
+ };
1577
+ run = () => runAndApply(mutate, {
1578
+ ...options,
1579
+ retryBlockedCommand: retry,
1580
+ onBlockedCommandRetryQueued: () => {
1581
+ didQueueRetry = true;
1582
+ options?.onBlockedCommandRetryQueued?.();
1583
+ },
1584
+ });
1585
+ const update = runIfScopeCurrent();
1586
+ if (update) {
1587
+ onApplied?.(update);
1588
+ }
1589
+ return { update, queued: didQueueRetry };
1590
+ }, [isCommandRetryScopeCurrent, runAndApply]);
958
1591
  const insertImage = (0, react_1.useCallback)((src, attrs, selection) => {
959
1592
  const trimmedSrc = src.trim();
960
1593
  if (!trimmedSrc)
@@ -974,38 +1607,116 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
974
1607
  }, [allowBase64Images, restoreSelection, runAndApply]);
975
1608
  const openLinkRequest = (0, react_1.useCallback)(() => {
976
1609
  const requestSelection = selectionRef.current;
1610
+ const requestDocumentVersion = documentVersionRef.current;
1611
+ const requestEditorId = bridgeRef.current?.editorId ?? null;
1612
+ const linkState = activeStateRef.current;
1613
+ const requestLinkHref = typeof linkState.markAttrs?.link?.href === 'string'
1614
+ ? linkState.markAttrs.link.href
1615
+ : undefined;
977
1616
  onRequestLink?.({
978
- href: currentLinkHref,
979
- isActive: activeState.marks.link === true,
1617
+ href: requestLinkHref,
1618
+ isActive: linkState.marks.link === true,
980
1619
  selection: requestSelection,
981
1620
  setLink: (href) => {
982
1621
  const trimmedHref = href.trim();
983
1622
  if (!trimmedHref)
984
1623
  return;
985
- runAndApply(() => {
986
- restoreSelection(requestSelection);
987
- return (bridgeRef.current?.setMark('link', {
988
- href: trimmedHref,
989
- }) ?? null);
990
- }, { skipNativeApplyIfContentUnchanged: true });
1624
+ if (!isAsyncRequestCurrent(requestEditorId, requestDocumentVersion))
1625
+ return;
1626
+ const bridge = bridgeRef.current;
1627
+ if (!bridge || bridge.isDestroyed)
1628
+ return;
1629
+ const scalarSelection = scalarRangeFromSelection(requestSelection);
1630
+ if (!scalarSelection)
1631
+ return;
1632
+ runAndApplyWithCommandRetry({
1633
+ editorId: bridge.editorId,
1634
+ documentVersion: requestDocumentVersion,
1635
+ scalarAnchor: scalarSelection.anchor,
1636
+ scalarHead: scalarSelection.head,
1637
+ }, () => bridgeRef.current?.setMarkAtSelectionScalar(scalarSelection.anchor, scalarSelection.head, 'link', { href: trimmedHref }) ?? null, { skipNativeApplyIfContentUnchanged: true });
991
1638
  },
992
1639
  unsetLink: () => {
993
- runAndApply(() => {
994
- restoreSelection(requestSelection);
995
- return bridgeRef.current?.unsetMark('link') ?? null;
996
- }, { skipNativeApplyIfContentUnchanged: true });
1640
+ if (!isAsyncRequestCurrent(requestEditorId, requestDocumentVersion))
1641
+ return;
1642
+ const bridge = bridgeRef.current;
1643
+ if (!bridge || bridge.isDestroyed)
1644
+ return;
1645
+ const scalarSelection = scalarRangeFromSelection(requestSelection);
1646
+ if (!scalarSelection)
1647
+ return;
1648
+ runAndApplyWithCommandRetry({
1649
+ editorId: bridge.editorId,
1650
+ documentVersion: requestDocumentVersion,
1651
+ scalarAnchor: scalarSelection.anchor,
1652
+ scalarHead: scalarSelection.head,
1653
+ }, () => bridgeRef.current?.unsetMarkAtSelectionScalar(scalarSelection.anchor, scalarSelection.head, 'link') ?? null, { skipNativeApplyIfContentUnchanged: true });
997
1654
  },
998
1655
  });
999
- }, [activeState.marks.link, currentLinkHref, onRequestLink, restoreSelection, runAndApply]);
1656
+ }, [
1657
+ isAsyncRequestCurrent,
1658
+ onRequestLink,
1659
+ runAndApplyWithCommandRetry,
1660
+ scalarRangeFromSelection,
1661
+ ]);
1000
1662
  const openImageRequest = (0, react_1.useCallback)(() => {
1001
1663
  const requestSelection = selectionRef.current;
1664
+ const requestDocumentVersion = documentVersionRef.current;
1665
+ const requestEditorId = bridgeRef.current?.editorId ?? null;
1002
1666
  onRequestImage?.({
1003
1667
  selection: requestSelection,
1004
1668
  allowBase64: allowBase64Images,
1005
- insertImage: (src, attrs) => insertImage(src, attrs, requestSelection),
1669
+ insertImage: (src, attrs) => {
1670
+ const trimmedSrc = src.trim();
1671
+ if (!trimmedSrc)
1672
+ return;
1673
+ if (!allowBase64Images && isImageDataUrl(trimmedSrc))
1674
+ return;
1675
+ if (!isAsyncRequestCurrent(requestEditorId, requestDocumentVersion))
1676
+ return;
1677
+ const bridge = bridgeRef.current;
1678
+ if (!bridge || bridge.isDestroyed)
1679
+ return;
1680
+ const scalarSelection = scalarRangeFromSelection(requestSelection);
1681
+ if (!scalarSelection)
1682
+ return;
1683
+ runAndApplyWithCommandRetry({
1684
+ editorId: bridge.editorId,
1685
+ documentVersion: requestDocumentVersion,
1686
+ scalarAnchor: scalarSelection.anchor,
1687
+ scalarHead: scalarSelection.head,
1688
+ }, () => bridgeRef.current?.insertContentJsonAtSelectionScalar(scalarSelection.anchor, scalarSelection.head, (0, schemas_1.buildImageFragmentJson)({
1689
+ src: trimmedSrc,
1690
+ ...(attrs ?? {}),
1691
+ })) ?? null);
1692
+ },
1006
1693
  });
1007
- }, [allowBase64Images, insertImage, onRequestImage]);
1694
+ }, [
1695
+ allowBase64Images,
1696
+ isAsyncRequestCurrent,
1697
+ onRequestImage,
1698
+ runAndApplyWithCommandRetry,
1699
+ scalarRangeFromSelection,
1700
+ ]);
1008
1701
  const handleToolbarAction = (0, react_1.useCallback)((event) => {
1702
+ if (!isCurrentNativeEditorEvent(event.nativeEvent, bridgeRef.current))
1703
+ return;
1704
+ const currentDocumentVersion = documentVersionRef.current;
1705
+ const eventDocumentVersion = event.nativeEvent.documentVersion;
1706
+ if (typeof eventDocumentVersion === 'number' &&
1707
+ typeof currentDocumentVersion === 'number' &&
1708
+ eventDocumentVersion < currentDocumentVersion) {
1709
+ return;
1710
+ }
1711
+ if (react_native_1.Platform.OS === 'android' &&
1712
+ typeof currentDocumentVersion === 'number' &&
1713
+ typeof eventDocumentVersion !== 'number' &&
1714
+ typeof event.nativeEvent.updateJson !== 'string') {
1715
+ return;
1716
+ }
1717
+ if (!syncPreflightUpdateFromNativeEvent(event.nativeEvent.updateJson)) {
1718
+ return;
1719
+ }
1009
1720
  if (event.nativeEvent.key === LINK_TOOLBAR_ACTION_KEY) {
1010
1721
  openLinkRequest();
1011
1722
  return;
@@ -1015,7 +1726,12 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
1015
1726
  return;
1016
1727
  }
1017
1728
  onToolbarAction?.(event.nativeEvent.key);
1018
- }, [onToolbarAction, openImageRequest, openLinkRequest]);
1729
+ }, [
1730
+ onToolbarAction,
1731
+ openImageRequest,
1732
+ openLinkRequest,
1733
+ syncPreflightUpdateFromNativeEvent,
1734
+ ]);
1019
1735
  const resolveMentionSelectionAttrs = (0, react_1.useCallback)((selectionEvent) => {
1020
1736
  let resolvedAttrs;
1021
1737
  try {
@@ -1056,23 +1772,88 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
1056
1772
  bridgeRef.current.isDestroyed) {
1057
1773
  return;
1058
1774
  }
1059
- const attrs = resolveMentionInsertionAttrs({
1060
- trigger: mentionQuery.trigger,
1061
- suggestion,
1062
- attrs: resolveMentionSuggestionAttrs(suggestion, mentionQuery.trigger),
1063
- range: mentionQuery.range,
1064
- });
1065
- const update = runAndApply(() => bridgeRef.current?.insertContentJsonAtSelectionScalar(mentionQuery.range.anchor, mentionQuery.range.head, (0, addons_1.buildMentionFragmentJson)(attrs)) ?? null);
1066
- if (update) {
1067
- setMentionQueryEvent(null);
1775
+ const currentDocumentVersion = documentVersionRef.current;
1776
+ if (typeof mentionQuery.documentVersion === 'number' &&
1777
+ typeof currentDocumentVersion === 'number' &&
1778
+ mentionQuery.documentVersion < currentDocumentVersion) {
1779
+ setMentionQueryEventState(null);
1780
+ return;
1781
+ }
1782
+ let attrs = null;
1783
+ const requestDocumentVersion = typeof mentionQuery.documentVersion === 'number'
1784
+ ? mentionQuery.documentVersion
1785
+ : currentDocumentVersion;
1786
+ const retryScope = {
1787
+ editorId: bridgeRef.current.editorId,
1788
+ documentVersion: requestDocumentVersion,
1789
+ mentionQuery,
1790
+ };
1791
+ let queuedRetry = false;
1792
+ let retry = null;
1793
+ const finishSelection = (selectedAttrs) => {
1794
+ setMentionQueryEventState(null);
1068
1795
  mentions.onSelect?.({
1069
1796
  trigger: mentionQuery.trigger,
1070
1797
  suggestion,
1071
- attrs,
1798
+ attrs: selectedAttrs,
1799
+ ...(typeof mentionQuery.documentVersion === 'number'
1800
+ ? { documentVersion: mentionQuery.documentVersion }
1801
+ : {}),
1802
+ });
1803
+ };
1804
+ const attemptInsertion = () => {
1805
+ if (!isCommandRetryScopeCurrent(retryScope))
1806
+ return false;
1807
+ attrs = null;
1808
+ const update = runAndApply(() => bridgeRef.current?.insertContentJsonAtSelectionScalarLazy(mentionQuery.range.anchor, mentionQuery.range.head, () => {
1809
+ attrs = resolveMentionInsertionAttrs({
1810
+ trigger: mentionQuery.trigger,
1811
+ suggestion,
1812
+ attrs: resolveMentionSuggestionAttrs(suggestion, mentionQuery.trigger),
1813
+ range: mentionQuery.range,
1814
+ ...(typeof mentionQuery.documentVersion === 'number'
1815
+ ? { documentVersion: mentionQuery.documentVersion }
1816
+ : {}),
1817
+ });
1818
+ return (0, addons_1.buildMentionFragmentJson)(attrs);
1819
+ }) ?? null, {
1820
+ retryBlockedCommand: () => retry?.() === true,
1821
+ onBlockedCommandRetryQueued: () => {
1822
+ queuedRetry = true;
1823
+ },
1072
1824
  });
1825
+ if (update && attrs) {
1826
+ finishSelection(attrs);
1827
+ return true;
1828
+ }
1829
+ return false;
1830
+ };
1831
+ retry = attemptInsertion;
1832
+ if (attemptInsertion()) {
1833
+ return;
1834
+ }
1835
+ if (queuedRetry) {
1836
+ return;
1073
1837
  }
1074
- }, [resolveMentionInsertionAttrs, runAndApply]);
1838
+ const latestMentionQuery = mentionQueryEventRef.current;
1839
+ if (latestMentionQuery == null ||
1840
+ (latestMentionQuery.trigger === mentionQuery.trigger &&
1841
+ latestMentionQuery.query === mentionQuery.query &&
1842
+ latestMentionQuery.range.anchor === mentionQuery.range.anchor &&
1843
+ latestMentionQuery.range.head === mentionQuery.range.head &&
1844
+ latestMentionQuery.documentVersion === mentionQuery.documentVersion)) {
1845
+ setMentionQueryEventState(null);
1846
+ }
1847
+ }, [
1848
+ isCommandRetryScopeCurrent,
1849
+ resolveMentionInsertionAttrs,
1850
+ runAndApply,
1851
+ setMentionQueryEventState,
1852
+ ]);
1075
1853
  const handleAddonEvent = (0, react_1.useCallback)((event) => {
1854
+ const bridge = bridgeRef.current;
1855
+ if (!isCurrentNativeEditorEvent(event.nativeEvent, bridge))
1856
+ return;
1076
1857
  let parsed = null;
1077
1858
  try {
1078
1859
  parsed = JSON.parse(event.nativeEvent.eventJson);
@@ -1082,44 +1863,105 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
1082
1863
  }
1083
1864
  if (!parsed)
1084
1865
  return;
1866
+ let parsedDocumentVersion = typeof parsed.documentVersion === 'number' ? parsed.documentVersion : undefined;
1867
+ if (typeof parsedDocumentVersion !== 'number' &&
1868
+ parsed.type === 'mentionsSelectRequest' &&
1869
+ typeof parsed.updateJson === 'string') {
1870
+ try {
1871
+ const parsedUpdate = JSON.parse(parsed.updateJson);
1872
+ if (typeof parsedUpdate.documentVersion === 'number') {
1873
+ parsedDocumentVersion = parsedUpdate.documentVersion;
1874
+ }
1875
+ }
1876
+ catch {
1877
+ return;
1878
+ }
1879
+ }
1880
+ const currentDocumentVersion = documentVersionRef.current;
1881
+ const isStaleAddonEvent = typeof parsedDocumentVersion === 'number' &&
1882
+ typeof currentDocumentVersion === 'number' &&
1883
+ parsedDocumentVersion < currentDocumentVersion;
1884
+ const isVersionlessAndroidAddonEvent = react_native_1.Platform.OS === 'android' &&
1885
+ typeof currentDocumentVersion === 'number' &&
1886
+ typeof parsedDocumentVersion !== 'number';
1085
1887
  if (parsed.type === 'mentionsQueryChange') {
1888
+ if (isStaleAddonEvent || isVersionlessAndroidAddonEvent)
1889
+ return;
1086
1890
  const nextEvent = {
1087
1891
  query: parsed.query,
1088
1892
  trigger: parsed.trigger,
1089
1893
  range: parsed.range,
1090
1894
  isActive: parsed.isActive,
1895
+ documentVersion: parsedDocumentVersion,
1091
1896
  };
1092
- setMentionQueryEvent(parsed.isActive ? nextEvent : null);
1897
+ setMentionQueryEventState(parsed.isActive ? nextEvent : null, event.nativeEvent.editorId ?? bridgeRef.current?.editorId ?? null);
1093
1898
  addonsRef.current?.mentions?.onQueryChange?.({
1094
1899
  query: nextEvent.query,
1095
1900
  trigger: nextEvent.trigger,
1096
1901
  range: nextEvent.range,
1097
1902
  isActive: nextEvent.isActive,
1903
+ ...(typeof nextEvent.documentVersion === 'number'
1904
+ ? { documentVersion: nextEvent.documentVersion }
1905
+ : {}),
1098
1906
  });
1099
1907
  return;
1100
1908
  }
1101
1909
  if (parsed.type === 'mentionsSelectRequest') {
1102
- const suggestion = mentionSuggestionsByKeyRef.current.get(parsed.suggestionKey);
1103
- if (!suggestion || !bridgeRef.current || bridgeRef.current.isDestroyed)
1910
+ if (isStaleAddonEvent || isVersionlessAndroidAddonEvent)
1104
1911
  return;
1105
- const selectionEvent = {
1912
+ const requestDocumentVersion = typeof parsedDocumentVersion === 'number'
1913
+ ? parsedDocumentVersion
1914
+ : currentDocumentVersion;
1915
+ const nativeMentionSelectRequest = {
1106
1916
  trigger: parsed.trigger,
1107
- suggestion,
1108
- attrs: parsed.attrs,
1917
+ suggestionKey: parsed.suggestionKey,
1109
1918
  range: parsed.range,
1110
1919
  };
1111
- const finalAttrs = resolveMentionInsertionAttrs(selectionEvent);
1112
- const update = runAndApply(() => bridgeRef.current?.insertContentJsonAtSelectionScalar(parsed.range.anchor, parsed.range.head, (0, addons_1.buildMentionFragmentJson)(finalAttrs)) ?? null);
1113
- if (update) {
1920
+ if (doesLiveMentionQueryConflictWithNativeSelectRequest(nativeMentionSelectRequest, mentionQueryEventRef.current, requestDocumentVersion, currentDocumentVersion)) {
1921
+ return;
1922
+ }
1923
+ if (!syncPreflightUpdateFromNativeEvent(parsed.updateJson))
1924
+ return;
1925
+ const suggestion = mentionSuggestionsByKeyRef.current.get(parsed.suggestionKey);
1926
+ if (!suggestion || !bridgeRef.current || bridgeRef.current.isDestroyed)
1927
+ return;
1928
+ let finalAttrs = null;
1929
+ runAndApplyWithCommandRetry({
1930
+ editorId: bridgeRef.current.editorId,
1931
+ documentVersion: requestDocumentVersion,
1932
+ nativeMentionSelectRequest,
1933
+ }, () => {
1934
+ return (bridgeRef.current?.insertContentJsonAtSelectionScalarLazy(parsed.range.anchor, parsed.range.head, () => {
1935
+ const selectionEvent = {
1936
+ trigger: parsed.trigger,
1937
+ suggestion,
1938
+ attrs: parsed.attrs,
1939
+ range: parsed.range,
1940
+ ...(typeof parsedDocumentVersion === 'number'
1941
+ ? { documentVersion: parsedDocumentVersion }
1942
+ : {}),
1943
+ };
1944
+ const attrs = resolveMentionInsertionAttrs(selectionEvent);
1945
+ finalAttrs = attrs;
1946
+ return (0, addons_1.buildMentionFragmentJson)(attrs);
1947
+ }) ?? null);
1948
+ }, undefined, () => {
1949
+ if (!finalAttrs)
1950
+ return;
1114
1951
  addonsRef.current?.mentions?.onSelect?.({
1115
1952
  trigger: parsed.trigger,
1116
1953
  suggestion,
1117
1954
  attrs: finalAttrs,
1955
+ ...(typeof parsedDocumentVersion === 'number'
1956
+ ? { documentVersion: parsedDocumentVersion }
1957
+ : {}),
1118
1958
  });
1119
- }
1959
+ });
1120
1960
  return;
1121
1961
  }
1122
1962
  if (parsed.type === 'mentionsSelect') {
1963
+ if (isStaleAddonEvent || isVersionlessAndroidAddonEvent)
1964
+ return;
1123
1965
  const suggestion = mentionSuggestionsByKeyRef.current.get(parsed.suggestionKey);
1124
1966
  if (!suggestion)
1125
1967
  return;
@@ -1127,9 +1969,17 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
1127
1969
  trigger: parsed.trigger,
1128
1970
  suggestion,
1129
1971
  attrs: parsed.attrs,
1972
+ ...(typeof parsedDocumentVersion === 'number'
1973
+ ? { documentVersion: parsedDocumentVersion }
1974
+ : {}),
1130
1975
  });
1131
1976
  }
1132
- }, [resolveMentionInsertionAttrs, runAndApply]);
1977
+ }, [
1978
+ resolveMentionInsertionAttrs,
1979
+ runAndApplyWithCommandRetry,
1980
+ setMentionQueryEventState,
1981
+ syncPreflightUpdateFromNativeEvent,
1982
+ ]);
1133
1983
  (0, react_1.useImperativeHandle)(ref, () => ({
1134
1984
  focus() {
1135
1985
  nativeViewRef.current?.focus?.();
@@ -1192,16 +2042,25 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
1192
2042
  getContent() {
1193
2043
  if (!bridgeRef.current || bridgeRef.current.isDestroyed)
1194
2044
  return '';
2045
+ if (!prepareBridgeForExternalContentRead()) {
2046
+ return bridgeRef.current.getCachedHtml() ?? '';
2047
+ }
1195
2048
  return bridgeRef.current.getHtml();
1196
2049
  },
1197
2050
  getContentJson() {
1198
2051
  if (!bridgeRef.current || bridgeRef.current.isDestroyed)
1199
2052
  return {};
2053
+ if (!prepareBridgeForExternalContentRead()) {
2054
+ return bridgeRef.current.getCachedJson() ?? {};
2055
+ }
1200
2056
  return bridgeRef.current.getJson();
1201
2057
  },
1202
2058
  getTextContent() {
1203
2059
  if (!bridgeRef.current || bridgeRef.current.isDestroyed)
1204
2060
  return '';
2061
+ if (!prepareBridgeForExternalContentRead()) {
2062
+ return (bridgeRef.current.getCachedHtml() ?? '').replace(/<[^>]+>/g, '');
2063
+ }
1205
2064
  return bridgeRef.current.getHtml().replace(/<[^>]+>/g, '');
1206
2065
  },
1207
2066
  async getCaretRect() {
@@ -1227,7 +2086,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
1227
2086
  return false;
1228
2087
  return bridgeRef.current.canRedo();
1229
2088
  },
1230
- }), [insertImage, runAndApply]);
2089
+ }), [insertImage, prepareBridgeForExternalContentRead, runAndApply]);
1231
2090
  const activeMentionTrigger = mentionQueryEvent?.trigger || resolveMentionTrigger(addons);
1232
2091
  const activeMentionSuggestions = (0, react_1.useMemo)(() => isFocused && mentionQueryEvent != null && addons?.mentions != null
1233
2092
  ? filterMentionSuggestions(addons.mentions.suggestions ?? [], mentionQueryEvent.query, activeMentionTrigger)
@@ -1247,6 +2106,9 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
1247
2106
  suggestion,
1248
2107
  attrs: resolveMentionSuggestionAttrs(suggestion, activeMentionTrigger),
1249
2108
  range: mentionQueryEvent.range,
2109
+ ...(typeof mentionQueryEvent.documentVersion === 'number'
2110
+ ? { documentVersion: mentionQueryEvent.documentVersion }
2111
+ : {}),
1250
2112
  };
1251
2113
  const attrs = resolveMentionSelectionAttrs(selectionEvent);
1252
2114
  let resolvedTheme;
@@ -1414,8 +2276,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
1414
2276
  'rgba(0, 122, 255, 0.12)')
1415
2277
  : (suggestionTheme?.backgroundColor ??
1416
2278
  '#F2F2F7'),
1417
- borderColor: suggestionTheme?.borderColor ??
1418
- 'transparent',
2279
+ borderColor: suggestionTheme?.borderColor ?? 'transparent',
1419
2280
  borderWidth: suggestionTheme?.borderWidth ?? 0,
1420
2281
  borderRadius: suggestionTheme?.borderRadius ?? 12,
1421
2282
  },
@@ -1465,7 +2326,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
1465
2326
  }), onToggleStrike: () => runAndApply(() => bridgeRef.current?.toggleMark('strike') ?? null, {
1466
2327
  skipNativeApplyIfContentUnchanged: true,
1467
2328
  }), onToggleBulletList: () => runAndApply(() => bridgeRef.current?.toggleList('bulletList') ?? null), onToggleOrderedList: () => runAndApply(() => bridgeRef.current?.toggleList('orderedList') ?? null), onIndentList: () => runAndApply(() => bridgeRef.current?.indentListItem() ?? null), onOutdentList: () => runAndApply(() => bridgeRef.current?.outdentListItem() ?? null), onInsertHorizontalRule: () => runAndApply(() => bridgeRef.current?.insertNode('horizontalRule') ?? null), onInsertLineBreak: () => runAndApply(() => bridgeRef.current?.insertNode('hardBreak') ?? null), onUndo: () => runAndApply(() => bridgeRef.current?.undo() ?? null), onRedo: () => runAndApply(() => bridgeRef.current?.redo() ?? null) })) }));
1468
- return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.container, containerStyle], children: [(0, jsx_runtime_1.jsx)(NativeEditorView, { ref: nativeViewRef, style: nativeViewStyle, editorId: editorInstanceId, placeholder: placeholder, editable: editable, autoFocus: autoFocus, autoCapitalize: autoCapitalize, autoCorrect: autoCorrect, keyboardType: keyboardType, showToolbar: showToolbar, toolbarPlacement: toolbarPlacement, heightBehavior: heightBehavior, allowImageResizing: allowImageResizing, themeJson: themeJson, addonsJson: addonsJson, toolbarItemsJson: toolbarItemsJson, remoteSelectionsJson: remoteSelectionsJson, toolbarFrameJson: toolbarFrameJson, editorUpdateJson: pendingNativeUpdate.json, editorUpdateRevision: pendingNativeUpdate.revision, onEditorUpdate: handleUpdate, onSelectionChange: handleSelectionChange, onFocusChange: handleFocusChange, onContentHeightChange: handleContentHeightChange, onToolbarAction: handleToolbarAction, onAddonEvent: handleAddonEvent }, DEV_NATIVE_VIEW_KEY), shouldRenderJsToolbar && jsToolbar] }));
2329
+ return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.container, containerStyle], children: [(0, jsx_runtime_1.jsx)(NativeEditorView, { ref: nativeViewRef, style: nativeViewStyle, editorId: editorInstanceId, placeholder: placeholder, editable: editable, autoFocus: autoFocus, autoCapitalize: autoCapitalize, autoCorrect: autoCorrect, keyboardType: keyboardType, showToolbar: showToolbar, toolbarPlacement: toolbarPlacement, heightBehavior: heightBehavior, allowImageResizing: allowImageResizing, themeJson: themeJson, addonsJson: addonsJson, toolbarItemsJson: toolbarItemsJson, remoteSelectionsJson: remoteSelectionsJson, toolbarFrameJson: toolbarFrameJson, editorUpdateJson: pendingNativeUpdate.json, editorUpdateEditorId: pendingNativeUpdate.editorId, editorUpdateRevision: pendingNativeUpdate.revision, onEditorUpdate: handleUpdate, onSelectionChange: handleSelectionChange, onFocusChange: handleFocusChange, onContentHeightChange: handleContentHeightChange, ...(react_native_1.Platform.OS === 'android' ? { onEditorReady: handleEditorReady } : {}), onToolbarAction: handleToolbarAction, onAddonEvent: handleAddonEvent }, DEV_NATIVE_VIEW_KEY), shouldRenderJsToolbar && jsToolbar] }));
1469
2330
  });
1470
2331
  const styles = react_native_1.StyleSheet.create({
1471
2332
  container: {