react-native-enriched-markdown 0.5.0 → 0.6.0-nightly-20260424-4080a3393

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerDelegate.java +7 -0
  2. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerInterface.java +2 -0
  3. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +7 -0
  4. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +2 -0
  5. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +20 -0
  6. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +4 -0
  7. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +21 -0
  8. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +14 -0
  9. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +16 -0
  10. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +14 -0
  11. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockquoteRenderer.kt +5 -2
  12. package/android/src/main/java/com/swmansion/enriched/markdown/spans/BlockquoteSpan.kt +23 -5
  13. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/span/SpanFlags.kt +12 -0
  14. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt +48 -0
  15. package/ios/EnrichedMarkdown.mm +14 -0
  16. package/ios/EnrichedMarkdownText.mm +6 -0
  17. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp +20 -0
  18. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +4 -0
  19. package/ios/input/EnrichedMarkdownTextInput.mm +2 -3
  20. package/ios/utils/ENRMUIKit.h +6 -2
  21. package/ios/utils/SelectionColorUtils.h +14 -0
  22. package/lib/module/EnrichedMarkdownNativeComponent.ts +17 -0
  23. package/lib/module/EnrichedMarkdownTextNativeComponent.ts +17 -0
  24. package/lib/module/native/EnrichedMarkdownText.js +4 -0
  25. package/lib/module/native/EnrichedMarkdownText.js.map +1 -1
  26. package/lib/module/web/EnrichedMarkdownText.js +9 -2
  27. package/lib/module/web/EnrichedMarkdownText.js.map +1 -1
  28. package/lib/module/web/globalStyles.js +10 -0
  29. package/lib/module/web/globalStyles.js.map +1 -0
  30. package/lib/module/web/injectStyle.js +20 -0
  31. package/lib/module/web/injectStyle.js.map +1 -0
  32. package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +17 -0
  33. package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
  34. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +17 -0
  35. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
  36. package/lib/typescript/src/native/EnrichedMarkdownText.d.ts +1 -1
  37. package/lib/typescript/src/native/EnrichedMarkdownText.d.ts.map +1 -1
  38. package/lib/typescript/src/types/MarkdownTextProps.d.ts +18 -1
  39. package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
  40. package/lib/typescript/src/types/MarkdownTextProps.web.d.ts +5 -0
  41. package/lib/typescript/src/types/MarkdownTextProps.web.d.ts.map +1 -1
  42. package/lib/typescript/src/web/EnrichedMarkdownText.d.ts +1 -1
  43. package/lib/typescript/src/web/EnrichedMarkdownText.d.ts.map +1 -1
  44. package/lib/typescript/src/web/globalStyles.d.ts +3 -0
  45. package/lib/typescript/src/web/globalStyles.d.ts.map +1 -0
  46. package/lib/typescript/src/web/injectStyle.d.ts +9 -0
  47. package/lib/typescript/src/web/injectStyle.d.ts.map +1 -0
  48. package/package.json +1 -1
  49. package/src/EnrichedMarkdownNativeComponent.ts +17 -0
  50. package/src/EnrichedMarkdownTextNativeComponent.ts +17 -0
  51. package/src/native/EnrichedMarkdownText.tsx +4 -0
  52. package/src/types/MarkdownTextProps.ts +18 -1
  53. package/src/types/MarkdownTextProps.web.ts +5 -0
  54. package/src/web/EnrichedMarkdownText.tsx +8 -3
  55. package/src/web/globalStyles.ts +15 -0
  56. package/src/web/injectStyle.ts +17 -0
@@ -11,6 +11,7 @@ package com.facebook.react.viewmanagers;
11
11
 
12
12
  import android.view.View;
13
13
  import androidx.annotation.Nullable;
14
+ import com.facebook.react.bridge.ColorPropConverter;
14
15
  import com.facebook.react.bridge.ReadableArray;
15
16
  import com.facebook.react.bridge.ReadableMap;
16
17
  import com.facebook.react.uimanager.BaseViewManager;
@@ -37,6 +38,12 @@ public class EnrichedMarkdownManagerDelegate<T extends View, U extends BaseViewM
37
38
  case "selectable":
38
39
  mViewManager.setSelectable(view, value == null ? false : (boolean) value);
39
40
  break;
41
+ case "selectionColor":
42
+ mViewManager.setSelectionColor(view, ColorPropConverter.getColor(value, view.getContext()));
43
+ break;
44
+ case "selectionHandleColor":
45
+ mViewManager.setSelectionHandleColor(view, ColorPropConverter.getColor(value, view.getContext()));
46
+ break;
40
47
  case "md4cFlags":
41
48
  mViewManager.setMd4cFlags(view, (ReadableMap) value);
42
49
  break;
@@ -20,6 +20,8 @@ public interface EnrichedMarkdownManagerInterface<T extends View> extends ViewMa
20
20
  void setMarkdownStyle(T view, @Nullable ReadableMap value);
21
21
  void setEnableLinkPreview(T view, boolean value);
22
22
  void setSelectable(T view, boolean value);
23
+ void setSelectionColor(T view, @Nullable Integer value);
24
+ void setSelectionHandleColor(T view, @Nullable Integer value);
23
25
  void setMd4cFlags(T view, @Nullable ReadableMap value);
24
26
  void setAllowFontScaling(T view, boolean value);
25
27
  void setMaxFontSizeMultiplier(T view, float value);
@@ -11,6 +11,7 @@ package com.facebook.react.viewmanagers;
11
11
 
12
12
  import android.view.View;
13
13
  import androidx.annotation.Nullable;
14
+ import com.facebook.react.bridge.ColorPropConverter;
14
15
  import com.facebook.react.bridge.ReadableArray;
15
16
  import com.facebook.react.bridge.ReadableMap;
16
17
  import com.facebook.react.uimanager.BaseViewManager;
@@ -37,6 +38,12 @@ public class EnrichedMarkdownTextManagerDelegate<T extends View, U extends BaseV
37
38
  case "selectable":
38
39
  mViewManager.setSelectable(view, value == null ? false : (boolean) value);
39
40
  break;
41
+ case "selectionColor":
42
+ mViewManager.setSelectionColor(view, ColorPropConverter.getColor(value, view.getContext()));
43
+ break;
44
+ case "selectionHandleColor":
45
+ mViewManager.setSelectionHandleColor(view, ColorPropConverter.getColor(value, view.getContext()));
46
+ break;
40
47
  case "md4cFlags":
41
48
  mViewManager.setMd4cFlags(view, (ReadableMap) value);
42
49
  break;
@@ -20,6 +20,8 @@ public interface EnrichedMarkdownTextManagerInterface<T extends View> extends Vi
20
20
  void setMarkdownStyle(T view, @Nullable ReadableMap value);
21
21
  void setEnableLinkPreview(T view, boolean value);
22
22
  void setSelectable(T view, boolean value);
23
+ void setSelectionColor(T view, @Nullable Integer value);
24
+ void setSelectionHandleColor(T view, @Nullable Integer value);
23
25
  void setMd4cFlags(T view, @Nullable ReadableMap value);
24
26
  void setAllowFontScaling(T view, boolean value);
25
27
  void setMaxFontSizeMultiplier(T view, float value);
@@ -23,6 +23,8 @@ EnrichedMarkdownProps::EnrichedMarkdownProps(
23
23
  markdownStyle(convertRawProp(context, rawProps, "markdownStyle", sourceProps.markdownStyle, {})),
24
24
  enableLinkPreview(convertRawProp(context, rawProps, "enableLinkPreview", sourceProps.enableLinkPreview, {true})),
25
25
  selectable(convertRawProp(context, rawProps, "selectable", sourceProps.selectable, {false})),
26
+ selectionColor(convertRawProp(context, rawProps, "selectionColor", sourceProps.selectionColor, {})),
27
+ selectionHandleColor(convertRawProp(context, rawProps, "selectionHandleColor", sourceProps.selectionHandleColor, {})),
26
28
  md4cFlags(convertRawProp(context, rawProps, "md4cFlags", sourceProps.md4cFlags, {})),
27
29
  allowFontScaling(convertRawProp(context, rawProps, "allowFontScaling", sourceProps.allowFontScaling, {true})),
28
30
  maxFontSizeMultiplier(convertRawProp(context, rawProps, "maxFontSizeMultiplier", sourceProps.maxFontSizeMultiplier, {0.0})),
@@ -63,6 +65,14 @@ folly::dynamic EnrichedMarkdownProps::getDiffProps(
63
65
  result["selectable"] = selectable;
64
66
  }
65
67
 
68
+ if (selectionColor != oldProps->selectionColor) {
69
+ result["selectionColor"] = *selectionColor;
70
+ }
71
+
72
+ if (selectionHandleColor != oldProps->selectionHandleColor) {
73
+ result["selectionHandleColor"] = *selectionHandleColor;
74
+ }
75
+
66
76
  if (md4cFlags != oldProps->md4cFlags) {
67
77
  result["md4cFlags"] = toDynamic(md4cFlags);
68
78
  }
@@ -221,6 +231,8 @@ EnrichedMarkdownTextProps::EnrichedMarkdownTextProps(
221
231
  markdownStyle(convertRawProp(context, rawProps, "markdownStyle", sourceProps.markdownStyle, {})),
222
232
  enableLinkPreview(convertRawProp(context, rawProps, "enableLinkPreview", sourceProps.enableLinkPreview, {true})),
223
233
  selectable(convertRawProp(context, rawProps, "selectable", sourceProps.selectable, {false})),
234
+ selectionColor(convertRawProp(context, rawProps, "selectionColor", sourceProps.selectionColor, {})),
235
+ selectionHandleColor(convertRawProp(context, rawProps, "selectionHandleColor", sourceProps.selectionHandleColor, {})),
224
236
  md4cFlags(convertRawProp(context, rawProps, "md4cFlags", sourceProps.md4cFlags, {})),
225
237
  allowFontScaling(convertRawProp(context, rawProps, "allowFontScaling", sourceProps.allowFontScaling, {true})),
226
238
  maxFontSizeMultiplier(convertRawProp(context, rawProps, "maxFontSizeMultiplier", sourceProps.maxFontSizeMultiplier, {0.0})),
@@ -261,6 +273,14 @@ folly::dynamic EnrichedMarkdownTextProps::getDiffProps(
261
273
  result["selectable"] = selectable;
262
274
  }
263
275
 
276
+ if (selectionColor != oldProps->selectionColor) {
277
+ result["selectionColor"] = *selectionColor;
278
+ }
279
+
280
+ if (selectionHandleColor != oldProps->selectionHandleColor) {
281
+ result["selectionHandleColor"] = *selectionHandleColor;
282
+ }
283
+
264
284
  if (md4cFlags != oldProps->md4cFlags) {
265
285
  result["md4cFlags"] = toDynamic(md4cFlags);
266
286
  }
@@ -1954,6 +1954,8 @@ class EnrichedMarkdownProps final : public ViewProps {
1954
1954
  EnrichedMarkdownMarkdownStyleStruct markdownStyle{};
1955
1955
  bool enableLinkPreview{true};
1956
1956
  bool selectable{false};
1957
+ SharedColor selectionColor{};
1958
+ SharedColor selectionHandleColor{};
1957
1959
  EnrichedMarkdownMd4cFlagsStruct md4cFlags{};
1958
1960
  bool allowFontScaling{true};
1959
1961
  Float maxFontSizeMultiplier{0.0};
@@ -4250,6 +4252,8 @@ class EnrichedMarkdownTextProps final : public ViewProps {
4250
4252
  EnrichedMarkdownTextMarkdownStyleStruct markdownStyle{};
4251
4253
  bool enableLinkPreview{true};
4252
4254
  bool selectable{false};
4255
+ SharedColor selectionColor{};
4256
+ SharedColor selectionHandleColor{};
4253
4257
  EnrichedMarkdownTextMd4cFlagsStruct md4cFlags{};
4254
4258
  bool allowFontScaling{true};
4255
4259
  Float maxFontSizeMultiplier{0.0};
@@ -18,6 +18,7 @@ import com.swmansion.enriched.markdown.utils.common.FeatureFlags
18
18
  import com.swmansion.enriched.markdown.utils.common.MarkdownSegmentRenderer
19
19
  import com.swmansion.enriched.markdown.utils.common.RenderedSegment
20
20
  import com.swmansion.enriched.markdown.utils.common.splitASTIntoSegments
21
+ import com.swmansion.enriched.markdown.utils.text.view.applySelectionColors
21
22
  import com.swmansion.enriched.markdown.utils.text.view.emitLinkLongPressEvent
22
23
  import com.swmansion.enriched.markdown.utils.text.view.emitLinkPressEvent
23
24
  import com.swmansion.enriched.markdown.views.BlockSegmentView
@@ -54,6 +55,8 @@ class EnrichedMarkdown
54
55
  private var maxFontSizeMultiplier: Float = 0f
55
56
  private var allowTrailingMargin: Boolean = false
56
57
  private var selectable: Boolean = true
58
+ private var selectionColor: Int? = null
59
+ private var selectionHandleColor: Int? = null
57
60
 
58
61
  private var onLinkPressCallback: ((String) -> Unit)? = null
59
62
  private var onLinkLongPressCallback: ((String) -> Unit)? = null
@@ -128,6 +131,22 @@ class EnrichedMarkdown
128
131
  }
129
132
  }
130
133
 
134
+ fun setSelectionColor(color: Int?) {
135
+ selectionColor = color
136
+ applySelectionColorsToSegments()
137
+ }
138
+
139
+ fun setSelectionHandleColor(color: Int?) {
140
+ selectionHandleColor = color
141
+ applySelectionColorsToSegments()
142
+ }
143
+
144
+ private fun applySelectionColorsToSegments() {
145
+ segmentViews.filterIsInstance<EnrichedMarkdownInternalText>().forEach {
146
+ it.applySelectionColors(selectionColor, selectionHandleColor)
147
+ }
148
+ }
149
+
131
150
  fun setOnLinkPressCallback(callback: (String) -> Unit) {
132
151
  onLinkPressCallback = callback
133
152
  }
@@ -234,6 +253,8 @@ class EnrichedMarkdown
234
253
  if (contextMenuItemTexts.isNotEmpty()) {
235
254
  setContextMenuItems(contextMenuItemTexts, ::forwardContextMenuItemPress)
236
255
  }
256
+
257
+ applySelectionColors(selectionColor, selectionHandleColor)
237
258
  }
238
259
 
239
260
  private fun createTableView(
@@ -80,6 +80,20 @@ class EnrichedMarkdownManager :
80
80
  view?.setIsSelectable(selectable)
81
81
  }
82
82
 
83
+ override fun setSelectionColor(
84
+ view: EnrichedMarkdown?,
85
+ value: Int?,
86
+ ) {
87
+ view?.setSelectionColor(value)
88
+ }
89
+
90
+ override fun setSelectionHandleColor(
91
+ view: EnrichedMarkdown?,
92
+ value: Int?,
93
+ ) {
94
+ view?.setSelectionHandleColor(value)
95
+ }
96
+
83
97
  @ReactProp(name = "md4cFlags")
84
98
  override fun setMd4cFlags(
85
99
  view: EnrichedMarkdown?,
@@ -23,6 +23,7 @@ import com.swmansion.enriched.markdown.utils.text.TailFadeInAnimator
23
23
  import com.swmansion.enriched.markdown.utils.text.interaction.CheckboxTouchHelper
24
24
  import com.swmansion.enriched.markdown.utils.text.view.LinkLongPressMovementMethod
25
25
  import com.swmansion.enriched.markdown.utils.text.view.applySelectableState
26
+ import com.swmansion.enriched.markdown.utils.text.view.applySelectionColors
26
27
  import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForCheckboxTap
27
28
  import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForLinkTap
28
29
  import com.swmansion.enriched.markdown.utils.text.view.createSelectionActionModeCallback
@@ -81,6 +82,9 @@ class EnrichedMarkdownText
81
82
  private set
82
83
  var spoilerOverlay: SpoilerOverlay = SpoilerOverlay.PARTICLES
83
84
 
85
+ private var selectionColor: Int? = null
86
+ private var selectionHandleColor: Int? = null
87
+
84
88
  init {
85
89
  setupAsMarkdownTextView()
86
90
  customSelectionActionModeCallback =
@@ -248,6 +252,8 @@ class EnrichedMarkdownText
248
252
  fadeAnimator?.animate(tailStart, styledText.length)
249
253
  previousTextLength = styledText.length
250
254
  }
255
+
256
+ applySelectionColors(selectionColor, selectionHandleColor)
251
257
  }
252
258
 
253
259
  fun setContextMenuItems(items: List<String>) {
@@ -258,6 +264,16 @@ class EnrichedMarkdownText
258
264
  applySelectableState(selectable)
259
265
  }
260
266
 
267
+ fun setSelectionColor(color: Int?) {
268
+ selectionColor = color
269
+ applySelectionColors(selectionColor, selectionHandleColor)
270
+ }
271
+
272
+ fun setSelectionHandleColor(color: Int?) {
273
+ selectionHandleColor = color
274
+ applySelectionColors(selectionColor, selectionHandleColor)
275
+ }
276
+
261
277
  fun emitOnLinkPress(url: String) {
262
278
  emitLinkPressEvent(url)
263
279
  }
@@ -100,6 +100,20 @@ class EnrichedMarkdownTextManager :
100
100
  view?.setIsSelectable(selectable)
101
101
  }
102
102
 
103
+ override fun setSelectionColor(
104
+ view: EnrichedMarkdownText?,
105
+ value: Int?,
106
+ ) {
107
+ view?.setSelectionColor(value)
108
+ }
109
+
110
+ override fun setSelectionHandleColor(
111
+ view: EnrichedMarkdownText?,
112
+ value: Int?,
113
+ ) {
114
+ view?.setSelectionHandleColor(value)
115
+ }
116
+
103
117
  @ReactProp(name = "md4cFlags")
104
118
  override fun setMd4cFlags(
105
119
  view: EnrichedMarkdownText?,
@@ -3,6 +3,7 @@ package com.swmansion.enriched.markdown.renderer
3
3
  import android.text.SpannableStringBuilder
4
4
  import com.swmansion.enriched.markdown.parser.MarkdownASTNode
5
5
  import com.swmansion.enriched.markdown.spans.BlockquoteSpan
6
+ import com.swmansion.enriched.markdown.utils.text.span.SPAN_FLAGS_CONTAINER_BACKGROUND
6
7
  import com.swmansion.enriched.markdown.utils.text.span.SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE
7
8
  import com.swmansion.enriched.markdown.utils.text.span.applyMarginBottom
8
9
  import com.swmansion.enriched.markdown.utils.text.span.applyMarginTop
@@ -45,12 +46,14 @@ class BlockquoteRenderer(
45
46
  .map { builder.getSpanStart(it) to builder.getSpanEnd(it) }
46
47
  .sortedBy { it.first }
47
48
 
48
- // The Accent Bar Span covers the full range for visual continuity
49
+ // The accent bar span covers the full range for visual continuity.
50
+ // SPAN_FLAGS_CONTAINER_BACKGROUND keeps the blockquote fill under any
51
+ // inline chip/pill backgrounds on the same line.
49
52
  builder.setSpan(
50
53
  BlockquoteSpan(style, depth, factory.context, factory.styleCache),
51
54
  start,
52
55
  end,
53
- SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
56
+ SPAN_FLAGS_CONTAINER_BACKGROUND,
54
57
  )
55
58
 
56
59
  // Apply styling only to segments that are NOT nested quotes
@@ -10,6 +10,7 @@ import android.text.Layout
10
10
  import android.text.Spanned
11
11
  import android.text.TextPaint
12
12
  import android.text.style.LeadingMarginSpan
13
+ import android.text.style.LineBackgroundSpan
13
14
  import android.text.style.MetricAffectingSpan
14
15
  import com.swmansion.enriched.markdown.renderer.BlockStyle
15
16
  import com.swmansion.enriched.markdown.renderer.SpanStyleCache
@@ -23,7 +24,8 @@ class BlockquoteSpan(
23
24
  private val context: Context,
24
25
  private val styleCache: SpanStyleCache,
25
26
  ) : MetricAffectingSpan(),
26
- LeadingMarginSpan {
27
+ LeadingMarginSpan,
28
+ LineBackgroundSpan {
27
29
  private val levelSpacing: Float = blockquoteStyle.borderWidth + blockquoteStyle.gapWidth
28
30
  private val blockStyle =
29
31
  BlockStyle(
@@ -60,8 +62,6 @@ class BlockquoteSpan(
60
62
  // Essential check from original: only the deepest span draws to prevent over-rendering background
61
63
  if (shouldSkipDrawing(text, start)) return
62
64
 
63
- drawBackground(c, top, bottom, layout)
64
-
65
65
  val borderPaint = configureBorderPaint()
66
66
  val borderTop = top.toFloat()
67
67
  val borderBottom = bottom.toFloat()
@@ -73,6 +73,23 @@ class BlockquoteSpan(
73
73
  }
74
74
  }
75
75
 
76
+ override fun drawBackground(
77
+ canvas: Canvas,
78
+ paint: Paint,
79
+ left: Int,
80
+ right: Int,
81
+ top: Int,
82
+ baseline: Int,
83
+ bottom: Int,
84
+ text: CharSequence,
85
+ start: Int,
86
+ end: Int,
87
+ lineNum: Int,
88
+ ) {
89
+ if (shouldSkipDrawing(text, start)) return
90
+ drawBackground(canvas, left, top, bottom, right)
91
+ }
92
+
76
93
  @SuppressLint("WrongConstant") // Result of mask is always valid: 0, 1, 2, or 3
77
94
  private fun applyTextStyle(tp: TextPaint) {
78
95
  tp.textSize = blockStyle.fontSize
@@ -123,12 +140,13 @@ class BlockquoteSpan(
123
140
 
124
141
  private fun drawBackground(
125
142
  c: Canvas,
143
+ left: Int,
126
144
  top: Int,
127
145
  bottom: Int,
128
- layout: Layout?,
146
+ right: Int,
129
147
  ) {
130
148
  val bgColor = blockquoteStyle.backgroundColor?.takeIf { it != Color.TRANSPARENT } ?: return
131
149
  val backgroundPaint = configureBackgroundPaint(bgColor)
132
- c.drawRect(0f, top.toFloat(), layout?.width?.toFloat() ?: 0f, bottom.toFloat(), backgroundPaint)
150
+ c.drawRect(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat(), backgroundPaint)
133
151
  }
134
152
  }
@@ -1,5 +1,17 @@
1
1
  package com.swmansion.enriched.markdown.utils.text.span
2
2
 
3
3
  import android.text.SpannableString
4
+ import android.text.Spanned
4
5
 
5
6
  const val SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE = SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE
7
+
8
+ /**
9
+ * `SPAN_EXCLUSIVE_EXCLUSIVE` with the maximum span priority.
10
+ *
11
+ * Higher-priority spans are iterated — and therefore drawn — first, so any
12
+ * lower-priority span on the same line ends up painted *on top* visually.
13
+ * Use this for full-width container backgrounds (e.g. blockquote fill in
14
+ * [BlockquoteSpan]) that must sit under inline pill/chip backgrounds.
15
+ */
16
+ const val SPAN_FLAGS_CONTAINER_BACKGROUND =
17
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE or Spanned.SPAN_PRIORITY
@@ -0,0 +1,48 @@
1
+ package com.swmansion.enriched.markdown.utils.text.view
2
+
3
+ import android.graphics.drawable.Drawable
4
+ import android.os.Build
5
+ import android.util.Log
6
+ import android.widget.TextView
7
+ import androidx.annotation.ColorInt
8
+ import androidx.core.graphics.drawable.DrawableCompat
9
+
10
+ private const val TAG = "TextSelectionColors"
11
+
12
+ private typealias HandleGetter = (TextView) -> Drawable?
13
+ private typealias HandleSetter = (TextView, Drawable) -> Unit
14
+
15
+ /**
16
+ * Applies selection highlight and (where supported) handle tinting to a [TextView].
17
+ *
18
+ * Handle drawables are only tinted on API 29+ where the framework exposes getters;
19
+ * on older versions the handle theme defaults remain unchanged.
20
+ */
21
+ fun TextView.applySelectionColors(
22
+ selectionColor: Int?,
23
+ selectionHandleColor: Int?,
24
+ ) {
25
+ selectionColor?.let { highlightColor = it }
26
+ selectionHandleColor?.let { applySelectionHandleTint(it) }
27
+ }
28
+
29
+ private fun TextView.applySelectionHandleTint(
30
+ @ColorInt color: Int,
31
+ ) {
32
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return
33
+
34
+ val handles: List<Pair<HandleGetter, HandleSetter>> =
35
+ listOf(
36
+ TextView::getTextSelectHandleLeft to { tv, d -> tv.setTextSelectHandleLeft(d) },
37
+ TextView::getTextSelectHandle to { tv, d -> tv.setTextSelectHandle(d) },
38
+ TextView::getTextSelectHandleRight to { tv, d -> tv.setTextSelectHandleRight(d) },
39
+ )
40
+
41
+ handles.forEach { (getter, setter) ->
42
+ try {
43
+ getter(this)?.mutate()?.also { DrawableCompat.setTint(it, color) }?.let { setter(this, it) }
44
+ } catch (e: LinkageError) {
45
+ Log.w(TAG, "Selection handle tint skipped: ${e.message}")
46
+ }
47
+ }
48
+ }
@@ -7,6 +7,7 @@
7
7
  #import "EditMenuUtils.h"
8
8
 
9
9
  #import "ENRMFeatureFlags.h"
10
+ #import "ENRMUIKit.h"
10
11
 
11
12
  #if ENRICHED_MARKDOWN_MATH
12
13
  #import "ENRMMathContainerView.h"
@@ -25,6 +26,7 @@
25
26
  #import "MarkdownExtractor.h"
26
27
  #import "ParagraphStyleUtils.h"
27
28
  #import "RuntimeKeys.h"
29
+ #import "SelectionColorUtils.h"
28
30
  #import "StyleConfig.h"
29
31
  #import "StylePropsUtils.h"
30
32
  #import "TableContainerView.h"
@@ -493,6 +495,9 @@ using namespace facebook::react;
493
495
  view.textView.selectable = _selectable;
494
496
  [view applyAttributedText:segment.attributedText context:segment.context];
495
497
 
498
+ const auto &selectionProps = *std::static_pointer_cast<EnrichedMarkdownProps const>(self->_props);
499
+ ENRMApplySelectionColor(view.textView, selectionProps.selectionColor);
500
+
496
501
  ENRMTapRecognizer *tapRecognizer = [[ENRMTapRecognizer alloc] initWithTarget:self action:@selector(textTapped:)];
497
502
  [view.textView addGestureRecognizer:tapRecognizer];
498
503
 
@@ -657,6 +662,15 @@ using namespace facebook::react;
657
662
  }
658
663
  }
659
664
 
665
+ if (newViewProps.selectionColor != oldViewProps.selectionColor) {
666
+ for (RCTUIView *segment in _segmentViews) {
667
+ if ([segment isKindOfClass:[EnrichedMarkdownInternalText class]]) {
668
+ ENRMPlatformTextView *tv = ((EnrichedMarkdownInternalText *)segment).textView;
669
+ ENRMApplySelectionColor(tv, newViewProps.selectionColor);
670
+ }
671
+ }
672
+ }
673
+
660
674
  if (markdownChanged || stylePropChanged || md4cFlagsChanged || allowTrailingMarginChanged) {
661
675
  NSString *markdownString = [[NSString alloc] initWithUTF8String:newViewProps.markdown.c_str()];
662
676
  [self renderMarkdownContent:markdownString];
@@ -9,6 +9,7 @@
9
9
  #import "ENRMTailFadeInAnimator.h"
10
10
  #import "ENRMTextRenderer.h"
11
11
  #import "ENRMTextViewSetup.h"
12
+ #import "ENRMUIKit.h"
12
13
  #import "EditMenuUtils.h"
13
14
  #import "FontScaleObserver.h"
14
15
  #import "FontUtils.h"
@@ -19,6 +20,7 @@
19
20
  #import "MarkdownExtractor.h"
20
21
  #import "ParagraphStyleUtils.h"
21
22
  #import "RuntimeKeys.h"
23
+ #import "SelectionColorUtils.h"
22
24
  #import "StylePropsUtils.h"
23
25
  #import "TaskListTapUtils.h"
24
26
 
@@ -411,6 +413,10 @@ using namespace facebook::react;
411
413
  _textView.selectable = newViewProps.selectable;
412
414
  }
413
415
 
416
+ if (newViewProps.selectionColor != oldViewProps.selectionColor) {
417
+ ENRMApplySelectionColor(_textView, newViewProps.selectionColor);
418
+ }
419
+
414
420
  if (newViewProps.allowFontScaling != oldViewProps.allowFontScaling) {
415
421
  _fontScaleObserver.allowFontScaling = newViewProps.allowFontScaling;
416
422
 
@@ -23,6 +23,8 @@ EnrichedMarkdownProps::EnrichedMarkdownProps(
23
23
  markdownStyle(convertRawProp(context, rawProps, "markdownStyle", sourceProps.markdownStyle, {})),
24
24
  enableLinkPreview(convertRawProp(context, rawProps, "enableLinkPreview", sourceProps.enableLinkPreview, {true})),
25
25
  selectable(convertRawProp(context, rawProps, "selectable", sourceProps.selectable, {false})),
26
+ selectionColor(convertRawProp(context, rawProps, "selectionColor", sourceProps.selectionColor, {})),
27
+ selectionHandleColor(convertRawProp(context, rawProps, "selectionHandleColor", sourceProps.selectionHandleColor, {})),
26
28
  md4cFlags(convertRawProp(context, rawProps, "md4cFlags", sourceProps.md4cFlags, {})),
27
29
  allowFontScaling(convertRawProp(context, rawProps, "allowFontScaling", sourceProps.allowFontScaling, {true})),
28
30
  maxFontSizeMultiplier(convertRawProp(context, rawProps, "maxFontSizeMultiplier", sourceProps.maxFontSizeMultiplier, {0.0})),
@@ -63,6 +65,14 @@ folly::dynamic EnrichedMarkdownProps::getDiffProps(
63
65
  result["selectable"] = selectable;
64
66
  }
65
67
 
68
+ if (selectionColor != oldProps->selectionColor) {
69
+ result["selectionColor"] = *selectionColor;
70
+ }
71
+
72
+ if (selectionHandleColor != oldProps->selectionHandleColor) {
73
+ result["selectionHandleColor"] = *selectionHandleColor;
74
+ }
75
+
66
76
  if (md4cFlags != oldProps->md4cFlags) {
67
77
  result["md4cFlags"] = toDynamic(md4cFlags);
68
78
  }
@@ -221,6 +231,8 @@ EnrichedMarkdownTextProps::EnrichedMarkdownTextProps(
221
231
  markdownStyle(convertRawProp(context, rawProps, "markdownStyle", sourceProps.markdownStyle, {})),
222
232
  enableLinkPreview(convertRawProp(context, rawProps, "enableLinkPreview", sourceProps.enableLinkPreview, {true})),
223
233
  selectable(convertRawProp(context, rawProps, "selectable", sourceProps.selectable, {false})),
234
+ selectionColor(convertRawProp(context, rawProps, "selectionColor", sourceProps.selectionColor, {})),
235
+ selectionHandleColor(convertRawProp(context, rawProps, "selectionHandleColor", sourceProps.selectionHandleColor, {})),
224
236
  md4cFlags(convertRawProp(context, rawProps, "md4cFlags", sourceProps.md4cFlags, {})),
225
237
  allowFontScaling(convertRawProp(context, rawProps, "allowFontScaling", sourceProps.allowFontScaling, {true})),
226
238
  maxFontSizeMultiplier(convertRawProp(context, rawProps, "maxFontSizeMultiplier", sourceProps.maxFontSizeMultiplier, {0.0})),
@@ -261,6 +273,14 @@ folly::dynamic EnrichedMarkdownTextProps::getDiffProps(
261
273
  result["selectable"] = selectable;
262
274
  }
263
275
 
276
+ if (selectionColor != oldProps->selectionColor) {
277
+ result["selectionColor"] = *selectionColor;
278
+ }
279
+
280
+ if (selectionHandleColor != oldProps->selectionHandleColor) {
281
+ result["selectionHandleColor"] = *selectionHandleColor;
282
+ }
283
+
264
284
  if (md4cFlags != oldProps->md4cFlags) {
265
285
  result["md4cFlags"] = toDynamic(md4cFlags);
266
286
  }
@@ -1954,6 +1954,8 @@ class EnrichedMarkdownProps final : public ViewProps {
1954
1954
  EnrichedMarkdownMarkdownStyleStruct markdownStyle{};
1955
1955
  bool enableLinkPreview{true};
1956
1956
  bool selectable{false};
1957
+ SharedColor selectionColor{};
1958
+ SharedColor selectionHandleColor{};
1957
1959
  EnrichedMarkdownMd4cFlagsStruct md4cFlags{};
1958
1960
  bool allowFontScaling{true};
1959
1961
  Float maxFontSizeMultiplier{0.0};
@@ -4250,6 +4252,8 @@ class EnrichedMarkdownTextProps final : public ViewProps {
4250
4252
  EnrichedMarkdownTextMarkdownStyleStruct markdownStyle{};
4251
4253
  bool enableLinkPreview{true};
4252
4254
  bool selectable{false};
4255
+ SharedColor selectionColor{};
4256
+ SharedColor selectionHandleColor{};
4253
4257
  EnrichedMarkdownTextMd4cFlagsStruct md4cFlags{};
4254
4258
  bool allowFontScaling{true};
4255
4259
  Float maxFontSizeMultiplier{0.0};
@@ -15,6 +15,7 @@
15
15
  #import "ENRMStyleMergingConfig.h"
16
16
  #import "ENRMUIKit.h"
17
17
  #import "InputStylePropsUtils.h"
18
+ #import "SelectionColorUtils.h"
18
19
  #if TARGET_OS_OSX
19
20
  #import <React/RCTBackedTextInputDelegate.h>
20
21
  #endif
@@ -290,9 +291,7 @@ using namespace facebook::react;
290
291
  }
291
292
 
292
293
  if (newViewProps.selectionColor != oldViewProps.selectionColor) {
293
- if (isColorMeaningful(newViewProps.selectionColor)) {
294
- ENRMSetSelectionColor(_textView, RCTUIColorFromSharedColor(newViewProps.selectionColor));
295
- }
294
+ ENRMApplySelectionColor(_textView, newViewProps.selectionColor);
296
295
  }
297
296
 
298
297
  _emitMarkdown = newViewProps.isOnChangeMarkdownSet;
@@ -203,12 +203,16 @@ static inline void ENRMSetCursorColor(ENRMPlatformTextView *textView, RCTUIColor
203
203
  #endif
204
204
  }
205
205
 
206
- /// Cross-platform selection color: iOS uses tintColor (affects both cursor and selection);
207
- /// macOS selection highlight is managed by the system and not directly settable.
206
+ /// Cross-platform selection color: iOS uses tintColor (also affects the caret
207
+ /// and selection handles); macOS sets the selection background via
208
+ /// `selectedTextAttributes`. Pass `nil` to restore the system default.
208
209
  static inline void ENRMSetSelectionColor(ENRMPlatformTextView *textView, RCTUIColor *color)
209
210
  {
210
211
  #if !TARGET_OS_OSX
211
212
  textView.tintColor = color;
213
+ #else
214
+ RCTUIColor *resolved = color ?: [NSColor selectedTextBackgroundColor];
215
+ textView.selectedTextAttributes = @{NSBackgroundColorAttributeName : resolved};
212
216
  #endif
213
217
  }
214
218
 
@@ -0,0 +1,14 @@
1
+ #pragma once
2
+
3
+ #import "ENRMUIKit.h"
4
+ #import <React/RCTConversions.h>
5
+
6
+ /// Applies a codegen `selectionColor` to `textView`, falling back to the
7
+ /// system tint when the color is unset. Obj-C++ only — `.mm` consumers only.
8
+ static inline void ENRMApplySelectionColor(ENRMPlatformTextView *textView, const facebook::react::SharedColor &color) {
9
+ if (isColorMeaningful(color)) {
10
+ ENRMSetSelectionColor(textView, RCTUIColorFromSharedColor(color));
11
+ } else {
12
+ ENRMSetSelectionColor(textView, nil);
13
+ }
14
+ }
@@ -273,6 +273,23 @@ export interface NativeProps extends ViewProps {
273
273
  * @default true
274
274
  */
275
275
  selectable?: boolean;
276
+ /**
277
+ * Color of the text selection highlight.
278
+ *
279
+ * On iOS, this also affects the caret and selection handle colors
280
+ * (they share a single tint). On macOS, only the selection background
281
+ * is affected.
282
+ *
283
+ * @platform ios, android, macos, web
284
+ */
285
+ selectionColor?: ColorValue;
286
+ /**
287
+ * Color of the selection handles (drag anchors).
288
+ * No-op on API levels below 29.
289
+ *
290
+ * @platform android
291
+ */
292
+ selectionHandleColor?: ColorValue;
276
293
  /**
277
294
  * MD4C parser flags configuration.
278
295
  * Controls how the markdown parser interprets certain syntax.
@@ -273,6 +273,23 @@ export interface NativeProps extends ViewProps {
273
273
  * @default true
274
274
  */
275
275
  selectable?: boolean;
276
+ /**
277
+ * Color of the text selection highlight.
278
+ *
279
+ * On iOS, this also affects the caret and selection handle colors
280
+ * (they share a single tint). On macOS, only the selection background
281
+ * is affected.
282
+ *
283
+ * @platform ios, android, macos, web
284
+ */
285
+ selectionColor?: ColorValue;
286
+ /**
287
+ * Color of the selection handles (drag anchors).
288
+ * No-op on API levels below 29.
289
+ *
290
+ * @platform android
291
+ */
292
+ selectionHandleColor?: ColorValue;
276
293
  /**
277
294
  * MD4C parser flags configuration.
278
295
  * Controls how the markdown parser interprets certain syntax.