@apollohg/react-native-prose-editor 0.5.18 → 0.5.20

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.
@@ -3779,8 +3779,13 @@ class EditorEditText @JvmOverloads constructor(
3779
3779
  *
3780
3780
  * @param updateJSON The JSON string from editor_insert_text, etc.
3781
3781
  */
3782
- fun applyUpdateJSON(updateJSON: String, notifyListener: Boolean = true) {
3782
+ fun applyUpdateJSON(
3783
+ updateJSON: String,
3784
+ notifyListener: Boolean = true,
3785
+ refreshInputConnectionForExternalUpdate: Boolean = false
3786
+ ) {
3783
3787
  val totalStartedAt = System.nanoTime()
3788
+ val previousVisibleText = text?.toString().orEmpty()
3784
3789
  val parseStartedAt = totalStartedAt
3785
3790
  val update = try {
3786
3791
  org.json.JSONObject(updateJSON)
@@ -3891,6 +3896,10 @@ class EditorEditText @JvmOverloads constructor(
3891
3896
  } else {
3892
3897
  preserveScrollPosition(previousScrollX, previousScrollY)
3893
3898
  }
3899
+ refreshInputConnectionAfterExternalTextReplacementIfNeeded(
3900
+ enabled = refreshInputConnectionForExternalUpdate,
3901
+ previousVisibleText = previousVisibleText
3902
+ )
3894
3903
  val postApplyNanos = System.nanoTime() - postApplyStartedAt
3895
3904
 
3896
3905
  val totalNanos = System.nanoTime() - totalStartedAt
@@ -3916,6 +3925,17 @@ class EditorEditText @JvmOverloads constructor(
3916
3925
  }
3917
3926
  }
3918
3927
 
3928
+ private fun refreshInputConnectionAfterExternalTextReplacementIfNeeded(
3929
+ enabled: Boolean,
3930
+ previousVisibleText: String
3931
+ ) {
3932
+ if (!enabled || !hasFocus()) return
3933
+ val currentVisibleText = text?.toString().orEmpty()
3934
+ if (currentVisibleText == previousVisibleText) return
3935
+ retireInputConnectionForEditor()
3936
+ restartInputForEditor("externalUpdate")
3937
+ }
3938
+
3919
3939
  /**
3920
3940
  * Apply a render JSON string (just render elements, no update wrapper).
3921
3941
  *
@@ -1744,7 +1744,10 @@ class NativeEditorExpoView(
1744
1744
  }
1745
1745
  isApplyingJSUpdate = true
1746
1746
  return try {
1747
- richTextView.editorEditText.applyUpdateJSON(updateJson)
1747
+ richTextView.editorEditText.applyUpdateJSON(
1748
+ updateJson,
1749
+ refreshInputConnectionForExternalUpdate = true
1750
+ )
1748
1751
  clearPendingEditorUpdateDispatchQueue("jsUpdate")
1749
1752
  true
1750
1753
  } catch (error: Throwable) {
@@ -209,13 +209,21 @@ class RichTextEditorView @JvmOverloads constructor(
209
209
  fun setContent(html: String) {
210
210
  if (editorId == 0L) return
211
211
  editorSetHtml(editorId.toULong(), html)
212
- editorEditText.applyUpdateJSON(editorGetCurrentState(editorId.toULong()), notifyListener = false)
212
+ editorEditText.applyUpdateJSON(
213
+ editorGetCurrentState(editorId.toULong()),
214
+ notifyListener = false,
215
+ refreshInputConnectionForExternalUpdate = true
216
+ )
213
217
  }
214
218
 
215
219
  fun setContent(json: org.json.JSONObject) {
216
220
  if (editorId == 0L) return
217
221
  editorSetJson(editorId.toULong(), json.toString())
218
- editorEditText.applyUpdateJSON(editorGetCurrentState(editorId.toULong()), notifyListener = false)
222
+ editorEditText.applyUpdateJSON(
223
+ editorGetCurrentState(editorId.toULong()),
224
+ notifyListener = false,
225
+ refreshInputConnectionForExternalUpdate = true
226
+ )
219
227
  }
220
228
 
221
229
  internal fun rebindEditorIfNeeded(notifyListener: Boolean = true) {
@@ -567,6 +567,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
567
567
  const pendingControlledSyncAfterNativeUpdateRef = (0, react_1.useRef)(false);
568
568
  const pendingBlockedNativeCommandRetryRef = (0, react_1.useRef)(false);
569
569
  const pendingNativeCommandRetryRef = (0, react_1.useRef)(null);
570
+ const pendingBridgeRecreationContentRef = (0, react_1.useRef)(null);
570
571
  const blockedNativeCommandRetryTimerRef = (0, react_1.useRef)(null);
571
572
  const [autoGrowHeight, setAutoGrowHeight] = (0, react_1.useState)(null);
572
573
  // Toolbar state from EditorUpdate events
@@ -1039,6 +1040,10 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
1039
1040
  }
1040
1041
  }, [syncNativeUpdateFromBridge]);
1041
1042
  (0, react_1.useEffect)(() => {
1043
+ const preservedUncontrolledContent = value == null && serializedValueJson == null
1044
+ ? pendingBridgeRecreationContentRef.current
1045
+ : null;
1046
+ pendingBridgeRecreationContentRef.current = null;
1042
1047
  const bridgeConfig = maxLength != null || serializedSchemaJson || allowBase64Images
1043
1048
  ? {
1044
1049
  maxLength,
@@ -1056,18 +1061,48 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
1056
1061
  else if (serializedValueJson != null) {
1057
1062
  bridge.setJsonString(serializedValueJson);
1058
1063
  }
1064
+ else if (preservedUncontrolledContent != null) {
1065
+ bridge.setJsonString(preservedUncontrolledContent.jsonString);
1066
+ }
1059
1067
  else if (serializedInitialJson != null) {
1060
1068
  bridge.setJsonString(serializedInitialJson);
1061
1069
  }
1062
1070
  else if (initialContent) {
1063
1071
  bridge.setHtml(initialContent);
1064
1072
  }
1073
+ if (preservedUncontrolledContent != null) {
1074
+ const preservedSelection = preservedUncontrolledContent.selection;
1075
+ if (preservedSelection.type === 'text') {
1076
+ const anchor = preservedSelection.anchor ?? 0;
1077
+ const head = preservedSelection.head ?? anchor;
1078
+ bridge.setSelection(anchor, head);
1079
+ }
1080
+ else if (preservedSelection.type === 'node' &&
1081
+ typeof preservedSelection.pos === 'number') {
1082
+ bridge.setSelection(preservedSelection.pos, preservedSelection.pos);
1083
+ }
1084
+ }
1065
1085
  syncStateFromUpdate(bridge.getCurrentState());
1066
1086
  setIsReady(true);
1067
1087
  return () => {
1088
+ if (bridgeRef.current === bridge &&
1089
+ value == null &&
1090
+ serializedValueJson == null &&
1091
+ !bridge.isDestroyed) {
1092
+ try {
1093
+ pendingBridgeRecreationContentRef.current = {
1094
+ jsonString: bridge.getJsonString(),
1095
+ selection: selectionRef.current,
1096
+ };
1097
+ }
1098
+ catch {
1099
+ pendingBridgeRecreationContentRef.current = null;
1100
+ }
1101
+ }
1068
1102
  bridge.destroy();
1069
- bridgeRef.current = null;
1070
- nativeViewRef.current = null;
1103
+ if (bridgeRef.current === bridge) {
1104
+ bridgeRef.current = null;
1105
+ }
1071
1106
  pendingNativeUpdateInFlightRef.current = null;
1072
1107
  pendingDetachedControlledSyncRef.current = false;
1073
1108
  pendingControlledSyncAfterNativeUpdateRef.current = false;
@@ -131,6 +131,9 @@ func resolveMentionQueryState(
131
131
  }
132
132
 
133
133
  final class MentionSuggestionChipButton: UIButton {
134
+ private static let horizontalContentInset: CGFloat = 8
135
+ private static let verticalContentInset: CGFloat = 8
136
+
134
137
  private let titleLabelView = UILabel()
135
138
  private let subtitleLabelView = UILabel()
136
139
  private let stackView = UIStackView()
@@ -180,10 +183,10 @@ final class MentionSuggestionChipButton: UIButton {
180
183
  addSubview(stackView)
181
184
 
182
185
  NSLayoutConstraint.activate([
183
- stackView.topAnchor.constraint(equalTo: topAnchor, constant: 8),
184
- stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12),
185
- stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12),
186
- stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8),
186
+ stackView.topAnchor.constraint(equalTo: topAnchor, constant: Self.verticalContentInset),
187
+ stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: Self.horizontalContentInset),
188
+ stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -Self.horizontalContentInset),
189
+ stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -Self.verticalContentInset),
187
190
  heightAnchor.constraint(greaterThanOrEqualToConstant: 40),
188
191
  ])
189
192
 
@@ -213,6 +216,13 @@ final class MentionSuggestionChipButton: UIButton {
213
216
  }
214
217
  }
215
218
 
219
+ override func tintColorDidChange() {
220
+ super.tintColorDidChange()
221
+ if toolbarAppearance == .native {
222
+ updateAppearance(highlighted: isHighlighted)
223
+ }
224
+ }
225
+
216
226
  @objc private func handleTouchDown() {
217
227
  updateAppearance(highlighted: true)
218
228
  }
@@ -226,14 +236,46 @@ final class MentionSuggestionChipButton: UIButton {
226
236
  layer.cornerRadius = 18
227
237
  layer.borderColor = UIColor.clear.cgColor
228
238
  layer.borderWidth = 0
239
+ #if compiler(>=6.2)
240
+ if #available(iOS 26.0, *) {
241
+ stackView.isHidden = true
242
+ backgroundColor = .clear
243
+ var configuration = highlighted
244
+ ? UIButton.Configuration.prominentGlass()
245
+ : UIButton.Configuration.glass()
246
+ configuration.cornerStyle = .capsule
247
+ configuration.contentInsets = NSDirectionalEdgeInsets(
248
+ top: Self.verticalContentInset,
249
+ leading: Self.horizontalContentInset,
250
+ bottom: Self.verticalContentInset,
251
+ trailing: Self.horizontalContentInset
252
+ )
253
+ configuration.title = suggestion.label
254
+ configuration.subtitle = suggestion.subtitle
255
+ configuration.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { incoming in
256
+ var outgoing = incoming
257
+ outgoing.font = .systemFont(ofSize: 14, weight: .semibold)
258
+ return outgoing
259
+ }
260
+ self.configuration = configuration
261
+ return
262
+ }
263
+ #endif
264
+ stackView.isHidden = false
229
265
  backgroundColor = highlighted
230
266
  ? UIColor.white.withAlphaComponent(0.18)
231
267
  : .clear
232
- titleLabelView.textColor = .label
233
- subtitleLabelView.textColor = .secondaryLabel
268
+ titleLabelView.textColor = tintColor
269
+ subtitleLabelView.textColor = tintColor.withAlphaComponent(0.72)
234
270
  return
235
271
  }
236
272
 
273
+ stackView.isHidden = false
274
+ if #available(iOS 15.0, *) {
275
+ var configuration = UIButton.Configuration.plain()
276
+ configuration.contentInsets = .zero
277
+ self.configuration = configuration
278
+ }
237
279
  backgroundColor = highlighted
238
280
  ? (theme?.optionHighlightedBackgroundColor ?? UIColor.systemBlue.withAlphaComponent(0.12))
239
281
  : (theme?.backgroundColor ?? UIColor.secondarySystemBackground)
@@ -252,4 +294,34 @@ final class MentionSuggestionChipButton: UIButton {
252
294
  func usesNativeAppearanceForTesting() -> Bool {
253
295
  toolbarAppearance == .native
254
296
  }
297
+
298
+ func titleTextColorForTesting() -> UIColor? {
299
+ titleLabelView.textColor
300
+ }
301
+
302
+ func subtitleTextColorForTesting() -> UIColor? {
303
+ subtitleLabelView.textColor
304
+ }
305
+
306
+ func usesNativeGlassTextRenderingForTesting() -> Bool {
307
+ #if compiler(>=6.2)
308
+ if #available(iOS 26.0, *) {
309
+ return toolbarAppearance == .native
310
+ && stackView.isHidden
311
+ && configuration?.title == suggestion.label
312
+ }
313
+ #endif
314
+ return false
315
+ }
316
+
317
+ func usesNativeGlassSemiboldTitleForTesting() -> Bool {
318
+ #if compiler(>=6.2)
319
+ if #available(iOS 26.0, *) {
320
+ return toolbarAppearance == .native
321
+ && stackView.isHidden
322
+ && configuration?.titleTextAttributesTransformer != nil
323
+ }
324
+ #endif
325
+ return false
326
+ }
255
327
  }
@@ -691,6 +691,8 @@ final class EditorAccessoryToolbarView: UIInputView {
691
691
  private static let contentSpacing: CGFloat = 6
692
692
  private static let defaultHorizontalInset: CGFloat = 0
693
693
  private static let defaultKeyboardOffset: CGFloat = 0
694
+ private static let chromeTransitionDuration: TimeInterval = 0.18
695
+ private static let nativeDisabledButtonOpacity: CGFloat = 0.46
694
696
 
695
697
  private struct ButtonBinding {
696
698
  let item: NativeToolbarItem
@@ -727,6 +729,7 @@ final class EditorAccessoryToolbarView: UIInputView {
727
729
  private var currentState = NativeToolbarState.empty
728
730
  private var theme: EditorToolbarTheme?
729
731
  private var mentionTheme: EditorMentionTheme?
732
+ private var didAnimateChromeTransition = false
730
733
  fileprivate var onPressItem: ((NativeToolbarItem) -> Void)?
731
734
  var onSelectMentionSuggestion: ((NativeMentionSuggestion) -> Void)?
732
735
  var isShowingMentionSuggestions: Bool {
@@ -746,6 +749,16 @@ final class EditorAccessoryToolbarView: UIInputView {
746
749
  var chromeBorderWidthForTesting: CGFloat {
747
750
  chromeView.layer.borderWidth
748
751
  }
752
+ var nativeChromeIsTransparentForTesting: Bool {
753
+ blurView.isHidden
754
+ && glassTintView.isHidden
755
+ && chromeView.layer.borderWidth == 0
756
+ && chromeView.layer.shadowOpacity == 0
757
+ && (chromeView.backgroundColor ?? .clear) == .clear
758
+ }
759
+ var didAnimateChromeTransitionForTesting: Bool {
760
+ didAnimateChromeTransition
761
+ }
749
762
  var nativeToolbarVisibleWidthForTesting: CGFloat {
750
763
  activeNativeToolbarScrollViewForTesting.bounds.width
751
764
  }
@@ -849,15 +862,42 @@ final class EditorAccessoryToolbarView: UIInputView {
849
862
  func apply(mentionTheme: EditorMentionTheme?) {
850
863
  self.mentionTheme = mentionTheme
851
864
  for button in mentionButtons {
852
- button.apply(theme: mentionTheme)
865
+ button.apply(theme: mentionTheme, toolbarAppearance: resolvedAppearance)
853
866
  }
854
867
  }
855
868
 
856
869
  func apply(theme: EditorToolbarTheme?) {
870
+ apply(theme: theme, animateChrome: false)
871
+ }
872
+
873
+ private func apply(theme: EditorToolbarTheme?, animateChrome: Bool) {
857
874
  self.theme = theme
858
875
  let usesNativeAppearance = resolvedAppearance == .native
876
+ let usesTransparentMentionChrome = self.usesTransparentMentionChrome
859
877
  let hasFloatingGlassButtons = self.usesFloatingGlassButtons
860
878
  let usesBarToolbar = usesNativeBarToolbar
879
+ let targetBlurHidden = usesTransparentMentionChrome || usesBarToolbar || !usesNativeAppearance
880
+ let targetBlurAlpha: CGFloat = usesNativeAppearance && !usesTransparentMentionChrome ? resolvedEffectAlpha : 0
881
+ let targetBlurEffect = usesNativeAppearance && !usesTransparentMentionChrome ? resolvedBlurEffect() : nil
882
+ let targetGlassHidden = usesTransparentMentionChrome || usesBarToolbar || !usesNativeAppearance
883
+ let targetGlassBackground = usesNativeAppearance && !usesTransparentMentionChrome
884
+ ? UIColor.systemBackground.withAlphaComponent(resolvedGlassTintAlpha)
885
+ : .clear
886
+ let targetGlassAlpha: CGFloat = targetGlassHidden ? 0 : 1
887
+ let targetBorderColor = usesTransparentMentionChrome ? UIColor.clear : resolvedBorderColor
888
+ let targetBorderWidth: CGFloat = usesTransparentMentionChrome || usesBarToolbar
889
+ ? 0
890
+ : (usesNativeAppearance
891
+ ? (1 / UIScreen.main.scale)
892
+ : resolvedBorderWidth)
893
+ let targetClipsToBounds =
894
+ !usesTransparentMentionChrome
895
+ && ((usesNativeAppearance && !hasFloatingGlassButtons && !usesBarToolbar) || resolvedBorderRadius > 0)
896
+ let targetShadowOpacity: Float =
897
+ usesNativeAppearance && !usesTransparentMentionChrome && !hasFloatingGlassButtons && !usesBarToolbar ? 0.08 : 0
898
+ let targetShadowRadius: CGFloat =
899
+ usesNativeAppearance && !usesTransparentMentionChrome && !hasFloatingGlassButtons && !usesBarToolbar ? 10 : 0
900
+
861
901
  chromeView.backgroundColor = usesNativeAppearance
862
902
  ? .clear
863
903
  : (theme?.backgroundColor ?? .systemBackground)
@@ -865,19 +905,6 @@ final class EditorAccessoryToolbarView: UIInputView {
865
905
  ? nil
866
906
  : (theme?.buttonColor ?? tintColor)
867
907
  chromeView.isOpaque = false
868
- blurView.isHidden = usesBarToolbar || !usesNativeAppearance
869
- blurView.effect = usesNativeAppearance ? resolvedBlurEffect() : nil
870
- blurView.alpha = usesNativeAppearance ? resolvedEffectAlpha : 1
871
- glassTintView.isHidden = usesBarToolbar || !usesNativeAppearance
872
- glassTintView.backgroundColor = usesNativeAppearance
873
- ? UIColor.systemBackground.withAlphaComponent(resolvedGlassTintAlpha)
874
- : .clear
875
- chromeView.layer.borderColor = resolvedBorderColor.cgColor
876
- chromeView.layer.borderWidth = usesBarToolbar
877
- ? 0
878
- : (usesNativeAppearance
879
- ? (1 / UIScreen.main.scale)
880
- : resolvedBorderWidth)
881
908
  chromeView.layer.cornerRadius = resolvedBorderRadius
882
909
  if #available(iOS 13.0, *) {
883
910
  chromeView.layer.cornerCurve = .continuous
@@ -892,11 +919,68 @@ final class EditorAccessoryToolbarView: UIInputView {
892
919
  glassTintView.cornerConfiguration = cornerConfig
893
920
  }
894
921
  #endif
895
- chromeView.clipsToBounds = (usesNativeAppearance && !hasFloatingGlassButtons && !usesBarToolbar) || resolvedBorderRadius > 0
896
- chromeView.layer.shadowOpacity = usesNativeAppearance && !hasFloatingGlassButtons && !usesBarToolbar ? 0.08 : 0
897
- chromeView.layer.shadowRadius = usesNativeAppearance && !hasFloatingGlassButtons && !usesBarToolbar ? 10 : 0
898
922
  chromeView.layer.shadowOffset = CGSize(width: 0, height: 2)
899
923
  chromeView.layer.shadowColor = UIColor.black.cgColor
924
+
925
+ let applyChromeProperties = {
926
+ self.blurView.alpha = targetBlurAlpha
927
+ self.glassTintView.alpha = targetGlassAlpha
928
+ self.chromeView.layer.borderColor = targetBorderColor.cgColor
929
+ self.chromeView.layer.borderWidth = targetBorderWidth
930
+ self.chromeView.layer.shadowOpacity = targetShadowOpacity
931
+ self.chromeView.layer.shadowRadius = targetShadowRadius
932
+ }
933
+ let finishChromeProperties = {
934
+ self.blurView.isHidden = targetBlurHidden
935
+ self.blurView.effect = targetBlurEffect
936
+ self.blurView.alpha = targetBlurAlpha
937
+ self.glassTintView.isHidden = targetGlassHidden
938
+ self.glassTintView.backgroundColor = targetGlassHidden ? .clear : targetGlassBackground
939
+ self.glassTintView.alpha = targetGlassAlpha
940
+ self.chromeView.layer.borderColor = targetBorderColor.cgColor
941
+ self.chromeView.layer.borderWidth = targetBorderWidth
942
+ self.chromeView.layer.shadowOpacity = targetShadowOpacity
943
+ self.chromeView.layer.shadowRadius = targetShadowRadius
944
+ self.chromeView.clipsToBounds = targetClipsToBounds
945
+ }
946
+
947
+ let shouldAnimateChrome = animateChrome && UIView.areAnimationsEnabled && window != nil
948
+ didAnimateChromeTransition = shouldAnimateChrome
949
+ if shouldAnimateChrome {
950
+ let blurWasHidden = blurView.isHidden
951
+ let glassWasHidden = glassTintView.isHidden
952
+ if !targetBlurHidden {
953
+ blurView.effect = targetBlurEffect
954
+ }
955
+ if !targetBlurHidden || !blurWasHidden {
956
+ blurView.isHidden = false
957
+ }
958
+ if blurWasHidden && !targetBlurHidden {
959
+ blurView.alpha = 0
960
+ }
961
+ if !targetGlassHidden {
962
+ glassTintView.backgroundColor = targetGlassBackground
963
+ }
964
+ if !targetGlassHidden || !glassWasHidden {
965
+ glassTintView.isHidden = false
966
+ }
967
+ if glassWasHidden && !targetGlassHidden {
968
+ glassTintView.alpha = 0
969
+ }
970
+ chromeView.clipsToBounds = targetClipsToBounds
971
+ UIView.animate(
972
+ withDuration: Self.chromeTransitionDuration,
973
+ delay: 0,
974
+ options: [.beginFromCurrentState, .allowUserInteraction, .curveEaseOut],
975
+ animations: applyChromeProperties,
976
+ completion: { _ in
977
+ finishChromeProperties()
978
+ }
979
+ )
980
+ } else {
981
+ finishChromeProperties()
982
+ }
983
+
900
984
  chromeLeadingConstraint?.constant = resolvedHorizontalInset
901
985
  chromeTrailingConstraint?.constant = -resolvedHorizontalInset
902
986
  chromeBottomConstraint?.constant = -resolvedKeyboardOffset
@@ -949,6 +1033,7 @@ final class EditorAccessoryToolbarView: UIInputView {
949
1033
  mentionScrollView.isHidden = !hasSuggestions
950
1034
  scrollView.isHidden = hasSuggestions
951
1035
  mentionRowHeightConstraint?.constant = hasSuggestions ? Self.mentionRowHeight : 0
1036
+ apply(theme: theme, animateChrome: hadSuggestions != hasSuggestions)
952
1037
  invalidateIntrinsicContentSize()
953
1038
  setNeedsLayout()
954
1039
  return hadSuggestions != hasSuggestions
@@ -988,6 +1073,9 @@ final class EditorAccessoryToolbarView: UIInputView {
988
1073
  var firstButtonTintColorForTesting: UIColor? {
989
1074
  buttonBindings.first?.button.tintColor
990
1075
  }
1076
+ func firstButtonTitleColorForTesting(_ state: UIControl.State) -> UIColor? {
1077
+ buttonBindings.first?.button.titleColor(for: state)
1078
+ }
991
1079
  var firstButtonTintAdjustmentModeForTesting: UIView.TintAdjustmentMode {
992
1080
  buttonBindings.first?.button.tintAdjustmentMode ?? .automatic
993
1081
  }
@@ -1552,7 +1640,10 @@ final class EditorAccessoryToolbarView: UIInputView {
1552
1640
  #endif
1553
1641
 
1554
1642
  if resolvedAppearance == .native {
1555
- button.tintColor = enabled ? nil : .systemGray
1643
+ let tintColor = enabled ? theme?.buttonColor : resolvedNativeDisabledButtonTintColor()
1644
+ button.tintColor = tintColor
1645
+ button.setTitleColor(tintColor, for: .normal)
1646
+ button.setTitleColor(resolvedNativeDisabledButtonTintColor(), for: .disabled)
1556
1647
  button.tintAdjustmentMode = enabled ? .automatic : .normal
1557
1648
  button.alpha = 1
1558
1649
  button.backgroundColor = active
@@ -1602,10 +1693,28 @@ final class EditorAccessoryToolbarView: UIInputView {
1602
1693
  theme?.resolvedButtonBorderRadius ?? 8
1603
1694
  }
1604
1695
 
1696
+ private func resolvedNativeDisabledButtonTintColor() -> UIColor {
1697
+ theme?.buttonDisabledColor ?? resolvedNativeButtonTintColor.withAlphaComponent(Self.nativeDisabledButtonOpacity)
1698
+ }
1699
+
1700
+ private var resolvedNativeButtonTintColor: UIColor {
1701
+ theme?.buttonColor ?? tintColor ?? .label
1702
+ }
1703
+
1605
1704
  private var usesFloatingGlassButtons: Bool {
1606
1705
  return false
1607
1706
  }
1608
1707
 
1708
+ private var usesTransparentMentionChrome: Bool {
1709
+ guard resolvedAppearance == .native, !mentionButtons.isEmpty else { return false }
1710
+ #if compiler(>=6.2)
1711
+ if #available(iOS 26.0, *) {
1712
+ return true
1713
+ }
1714
+ #endif
1715
+ return false
1716
+ }
1717
+
1609
1718
  private var usesNativeBarToolbar: Bool {
1610
1719
  return false
1611
1720
  }
@@ -1104,10 +1104,12 @@ final class RenderBridge {
1104
1104
  }
1105
1105
 
1106
1106
  let tagged = NSMutableAttributedString(attributedString: attributedString)
1107
+ let firstComposedCharacterRange = (tagged.string as NSString)
1108
+ .rangeOfComposedCharacterSequence(at: 0)
1107
1109
  tagged.addAttribute(
1108
1110
  RenderBridgeAttributes.topLevelChildIndex,
1109
1111
  value: NSNumber(value: topLevelChildIndex),
1110
- range: NSRange(location: 0, length: 1)
1112
+ range: firstComposedCharacterRange
1111
1113
  )
1112
1114
  return tagged
1113
1115
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apollohg/react-native-prose-editor",
3
- "version": "0.5.18",
3
+ "version": "0.5.20",
4
4
  "description": "Native rich text editor with Rust core for React Native",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/apollohg/react-native-prose-editor",