From 278035a487a294a3b9fe1e7360ee8780a321c948 Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Fri, 17 Apr 2026 21:10:29 -0700 Subject: [PATCH 01/10] feat: custom selection color --- .../enriched/markdown/EnrichedMarkdown.kt | 21 +++++ .../markdown/EnrichedMarkdownManager.kt | 14 ++++ .../enriched/markdown/EnrichedMarkdownText.kt | 16 ++++ .../markdown/EnrichedMarkdownTextManager.kt | 14 ++++ .../utils/text/view/TextSelectionColors.kt | 47 +++++++++++ apps/example/src/App.tsx | 2 + ios/EnrichedMarkdown.mm | 28 +++++++ ios/EnrichedMarkdownText.mm | 12 +++ src/EnrichedMarkdownNativeComponent.ts | 24 ++++++ src/EnrichedMarkdownTextNativeComponent.ts | 24 ++++++ src/native/EnrichedMarkdownText.tsx | 4 + src/types/MarkdownTextProps.ts | 26 ++++++- src/types/MarkdownTextProps.web.ts | 12 +++ src/web/EnrichedMarkdownText.tsx | 78 ++++++++++++++----- 14 files changed, 300 insertions(+), 22 deletions(-) create mode 100644 android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt diff --git a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt index dceb56a7..40f7ec4d 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt @@ -18,6 +18,7 @@ import com.swmansion.enriched.markdown.utils.common.FeatureFlags import com.swmansion.enriched.markdown.utils.common.MarkdownSegmentRenderer import com.swmansion.enriched.markdown.utils.common.RenderedSegment import com.swmansion.enriched.markdown.utils.common.splitASTIntoSegments +import com.swmansion.enriched.markdown.utils.text.view.applyMarkdownSelectionColors import com.swmansion.enriched.markdown.utils.text.view.emitLinkLongPressEvent import com.swmansion.enriched.markdown.utils.text.view.emitLinkPressEvent import com.swmansion.enriched.markdown.views.BlockSegmentView @@ -54,6 +55,8 @@ class EnrichedMarkdown private var maxFontSizeMultiplier: Float = 0f private var allowTrailingMargin: Boolean = false private var selectable: Boolean = true + private var propSelectionColor: Int? = null + private var propSelectionHandleColor: Int? = null private var onLinkPressCallback: ((String) -> Unit)? = null private var onLinkLongPressCallback: ((String) -> Unit)? = null @@ -128,6 +131,22 @@ class EnrichedMarkdown } } + fun setSelectionColorFromProps(color: Int?) { + propSelectionColor = color + applySelectionColorsToSegments() + } + + fun setSelectionHandleColorFromProps(color: Int?) { + propSelectionHandleColor = color + applySelectionColorsToSegments() + } + + private fun applySelectionColorsToSegments() { + segmentViews.filterIsInstance().forEach { + it.applyMarkdownSelectionColors(propSelectionColor, propSelectionHandleColor) + } + } + fun setOnLinkPressCallback(callback: (String) -> Unit) { onLinkPressCallback = callback } @@ -234,6 +253,8 @@ class EnrichedMarkdown if (contextMenuItemTexts.isNotEmpty()) { setContextMenuItems(contextMenuItemTexts, ::forwardContextMenuItemPress) } + + applyMarkdownSelectionColors(propSelectionColor, propSelectionHandleColor) } private fun createTableView( diff --git a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt index 89a31534..841a494d 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt @@ -80,6 +80,20 @@ class EnrichedMarkdownManager : view?.setIsSelectable(selectable) } + override fun setSelectionColor( + view: EnrichedMarkdown?, + value: Int?, + ) { + view?.setSelectionColorFromProps(value) + } + + override fun setSelectionHandleColor( + view: EnrichedMarkdown?, + value: Int?, + ) { + view?.setSelectionHandleColorFromProps(value) + } + @ReactProp(name = "md4cFlags") override fun setMd4cFlags( view: EnrichedMarkdown?, diff --git a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt index aa9614e4..0afa3a38 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt @@ -22,6 +22,7 @@ import com.swmansion.enriched.markdown.styles.StyleConfig import com.swmansion.enriched.markdown.utils.text.TailFadeInAnimator import com.swmansion.enriched.markdown.utils.text.interaction.CheckboxTouchHelper import com.swmansion.enriched.markdown.utils.text.view.LinkLongPressMovementMethod +import com.swmansion.enriched.markdown.utils.text.view.applyMarkdownSelectionColors import com.swmansion.enriched.markdown.utils.text.view.applySelectableState import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForCheckboxTap import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForLinkTap @@ -81,6 +82,9 @@ class EnrichedMarkdownText private set var spoilerOverlay: SpoilerOverlay = SpoilerOverlay.PARTICLES + private var propSelectionColor: Int? = null + private var propSelectionHandleColor: Int? = null + init { setupAsMarkdownTextView() customSelectionActionModeCallback = @@ -248,6 +252,8 @@ class EnrichedMarkdownText fadeAnimator?.animate(tailStart, styledText.length) previousTextLength = styledText.length } + + applyMarkdownSelectionColors(propSelectionColor, propSelectionHandleColor) } fun setContextMenuItems(items: List) { @@ -258,6 +264,16 @@ class EnrichedMarkdownText applySelectableState(selectable) } + fun setSelectionColorFromProps(color: Int?) { + propSelectionColor = color + applyMarkdownSelectionColors(propSelectionColor, propSelectionHandleColor) + } + + fun setSelectionHandleColorFromProps(color: Int?) { + propSelectionHandleColor = color + applyMarkdownSelectionColors(propSelectionColor, propSelectionHandleColor) + } + fun emitOnLinkPress(url: String) { emitLinkPressEvent(url) } diff --git a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt index 4cce5323..0f2993f5 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt @@ -100,6 +100,20 @@ class EnrichedMarkdownTextManager : view?.setIsSelectable(selectable) } + override fun setSelectionColor( + view: EnrichedMarkdownText?, + value: Int?, + ) { + view?.setSelectionColorFromProps(value) + } + + override fun setSelectionHandleColor( + view: EnrichedMarkdownText?, + value: Int?, + ) { + view?.setSelectionHandleColorFromProps(value) + } + @ReactProp(name = "md4cFlags") override fun setMd4cFlags( view: EnrichedMarkdownText?, diff --git a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt new file mode 100644 index 00000000..260a81c2 --- /dev/null +++ b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt @@ -0,0 +1,47 @@ +package com.swmansion.enriched.markdown.utils.text.view + +import android.os.Build +import android.widget.TextView +import androidx.annotation.ColorInt +import androidx.core.graphics.drawable.DrawableCompat + +/** + * Applies selection highlight and (where supported) handle tinting to a [TextView]. + * + * Handle drawables are only tinted on API 29+ where the framework exposes getters; + * on older versions the handle theme defaults remain unchanged. + */ +fun TextView.applyMarkdownSelectionColors( + selectionColor: Int?, + selectionHandleColor: Int?, +) { + selectionColor?.let { highlightColor = it } + selectionHandleColor?.let { applySelectionHandleTint(it) } +} + +private fun TextView.applySelectionHandleTint( + @ColorInt color: Int, +) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + return + } + try { + tintHandle(textSelectHandleLeft, color)?.let { setTextSelectHandleLeft(it) } + tintHandle(textSelectHandle, color)?.let { setTextSelectHandle(it) } + tintHandle(textSelectHandleRight, color)?.let { setTextSelectHandleRight(it) } + } catch (_: Exception) { + // Defensive: OEM TextView variants may not support all handle accessors. + } +} + +private fun tintHandle( + drawable: android.graphics.drawable.Drawable?, + @ColorInt color: Int, +): android.graphics.drawable.Drawable? { + if (drawable == null) { + return null + } + val mutated = drawable.mutate() + DrawableCompat.setTint(mutated, color) + return mutated +} diff --git a/apps/example/src/App.tsx b/apps/example/src/App.tsx index e4c44a7a..6de1b119 100644 --- a/apps/example/src/App.tsx +++ b/apps/example/src/App.tsx @@ -93,6 +93,8 @@ export default function App() { onLinkPress={handleLinkPress} markdownStyle={markdownStyle} contextMenuItems={contextMenuItems} + selectionColor={Platform.OS === 'ios' ? '#5A52FA' : '#DCDDFE'} + selectionHandleColor="#5A52FA" /> )} diff --git a/ios/EnrichedMarkdown.mm b/ios/EnrichedMarkdown.mm index 9227b789..7bdb8cdd 100644 --- a/ios/EnrichedMarkdown.mm +++ b/ios/EnrichedMarkdown.mm @@ -7,6 +7,7 @@ #import "EditMenuUtils.h" #import "ENRMFeatureFlags.h" +#import "ENRMUIKit.h" #if ENRICHED_MARKDOWN_MATH #import "ENRMMathContainerView.h" @@ -90,6 +91,7 @@ + (instancetype)segmentWithLatex:(NSString *)latex #endif @interface EnrichedMarkdown () +- (void)applySelectionTintFromProps:(const EnrichedMarkdownProps &)props toTextView:(ENRMPlatformTextView *)textView; @end @implementation EnrichedMarkdown { @@ -493,6 +495,9 @@ - (EnrichedMarkdownInternalText *)createTextViewForRenderedSegment:(ENRMRenderRe view.textView.selectable = _selectable; [view applyAttributedText:segment.attributedText context:segment.context]; + const auto &selectionProps = *std::static_pointer_cast(self->_props); + [self applySelectionTintFromProps:selectionProps toTextView:view.textView]; + ENRMTapRecognizer *tapRecognizer = [[ENRMTapRecognizer alloc] initWithTarget:self action:@selector(textTapped:)]; [view.textView addGestureRecognizer:tapRecognizer]; @@ -657,6 +662,18 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & } } + if (newViewProps.selectionHandleColor != oldViewProps.selectionHandleColor || + newViewProps.selectionColor != oldViewProps.selectionColor) { +#if !TARGET_OS_OSX + for (RCTUIView *segment in _segmentViews) { + if ([segment isKindOfClass:[EnrichedMarkdownInternalText class]]) { + ENRMPlatformTextView *tv = ((EnrichedMarkdownInternalText *)segment).textView; + [self applySelectionTintFromProps:newViewProps toTextView:tv]; + } + } +#endif + } + if (markdownChanged || stylePropChanged || md4cFlagsChanged || allowTrailingMarginChanged) { NSString *markdownString = [[NSString alloc] initWithUTF8String:newViewProps.markdown.c_str()]; [self renderMarkdownContent:markdownString]; @@ -880,4 +897,15 @@ - (NSInteger)indexOfAccessibilityElement:(id)element } #endif +- (void)applySelectionTintFromProps:(const EnrichedMarkdownProps &)props toTextView:(ENRMPlatformTextView *)textView +{ +#if !TARGET_OS_OSX + if (isColorMeaningful(props.selectionHandleColor)) { + ENRMSetSelectionColor(textView, RCTUIColorFromSharedColor(props.selectionHandleColor)); + } else if (isColorMeaningful(props.selectionColor)) { + ENRMSetSelectionColor(textView, RCTUIColorFromSharedColor(props.selectionColor)); + } +#endif +} + @end diff --git a/ios/EnrichedMarkdownText.mm b/ios/EnrichedMarkdownText.mm index 239e435a..012aa0c0 100644 --- a/ios/EnrichedMarkdownText.mm +++ b/ios/EnrichedMarkdownText.mm @@ -9,6 +9,7 @@ #import "ENRMTailFadeInAnimator.h" #import "ENRMTextRenderer.h" #import "ENRMTextViewSetup.h" +#import "ENRMUIKit.h" #import "EditMenuUtils.h" #import "FontScaleObserver.h" #import "FontUtils.h" @@ -411,6 +412,17 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & _textView.selectable = newViewProps.selectable; } + if (newViewProps.selectionHandleColor != oldViewProps.selectionHandleColor || + newViewProps.selectionColor != oldViewProps.selectionColor) { +#if !TARGET_OS_OSX + if (isColorMeaningful(newViewProps.selectionHandleColor)) { + ENRMSetSelectionColor(_textView, RCTUIColorFromSharedColor(newViewProps.selectionHandleColor)); + } else if (isColorMeaningful(newViewProps.selectionColor)) { + ENRMSetSelectionColor(_textView, RCTUIColorFromSharedColor(newViewProps.selectionColor)); + } +#endif + } + if (newViewProps.allowFontScaling != oldViewProps.allowFontScaling) { _fontScaleObserver.allowFontScaling = newViewProps.allowFontScaling; diff --git a/src/EnrichedMarkdownNativeComponent.ts b/src/EnrichedMarkdownNativeComponent.ts index 06024b47..4d661917 100644 --- a/src/EnrichedMarkdownNativeComponent.ts +++ b/src/EnrichedMarkdownNativeComponent.ts @@ -273,6 +273,30 @@ export interface NativeProps extends ViewProps { * @default true */ selectable?: boolean; + /** + * Color of the text selection highlight (selected text background). + * + * - **Android**: maps to `TextView.highlightColor`. + * - **iOS**: `UITextView.tintColor` drives the selection highlight, caret, and + * selection handles together. When `selectionHandleColor` is also set, it + * takes precedence for `tintColor` (see that prop). + * - **Web**: maps to `::selection` background via a CSS variable on the root. + * + * @platform ios, android, web + */ + selectionColor?: ColorValue; + /** + * Color of the selection handles (drag anchors). + * + * - **Android**: tints the left, mid, and right handle drawables (API 29+; + * older API levels leave handles at the default theme color). + * - **iOS**: merged with selection via `tintColor` — when set, it overrides + * `selectionColor` for the shared `tintColor` (highlight + handles + caret). + * - **Web**: best-effort via `accent-color` on the root; browser support varies. + * + * @platform ios, android, web + */ + selectionHandleColor?: ColorValue; /** * MD4C parser flags configuration. * Controls how the markdown parser interprets certain syntax. diff --git a/src/EnrichedMarkdownTextNativeComponent.ts b/src/EnrichedMarkdownTextNativeComponent.ts index e238ecfc..24773d91 100644 --- a/src/EnrichedMarkdownTextNativeComponent.ts +++ b/src/EnrichedMarkdownTextNativeComponent.ts @@ -273,6 +273,30 @@ export interface NativeProps extends ViewProps { * @default true */ selectable?: boolean; + /** + * Color of the text selection highlight (selected text background). + * + * - **Android**: maps to `TextView.highlightColor`. + * - **iOS**: `UITextView.tintColor` drives the selection highlight, caret, and + * selection handles together. When `selectionHandleColor` is also set, it + * takes precedence for `tintColor` (see that prop). + * - **Web**: maps to `::selection` background via a CSS variable on the root. + * + * @platform ios, android, web + */ + selectionColor?: ColorValue; + /** + * Color of the selection handles (drag anchors). + * + * - **Android**: tints the left, mid, and right handle drawables (API 29+; + * older API levels leave handles at the default theme color). + * - **iOS**: merged with selection via `tintColor` — when set, it overrides + * `selectionColor` for the shared `tintColor` (highlight + handles + caret). + * - **Web**: best-effort via `accent-color` on the root; browser support varies. + * + * @platform ios, android, web + */ + selectionHandleColor?: ColorValue; /** * MD4C parser flags configuration. * Controls how the markdown parser interprets certain syntax. diff --git a/src/native/EnrichedMarkdownText.tsx b/src/native/EnrichedMarkdownText.tsx index 236c2b7c..9d43fb9f 100644 --- a/src/native/EnrichedMarkdownText.tsx +++ b/src/native/EnrichedMarkdownText.tsx @@ -42,6 +42,8 @@ export const EnrichedMarkdownText = ({ streamingAnimation = false, spoilerOverlay = 'particles', contextMenuItems, + selectionColor, + selectionHandleColor, ...rest }: EnrichedMarkdownTextProps) => { const normalizedStyleRef = useRef(null); @@ -137,6 +139,8 @@ export const EnrichedMarkdownText = ({ style: containerStyle, contextMenuItems: nativeContextMenuItems, onContextMenuItemPress: handleContextMenuItemPress, + selectionColor, + selectionHandleColor, ...rest, }; diff --git a/src/types/MarkdownTextProps.ts b/src/types/MarkdownTextProps.ts index d450699e..00795c49 100644 --- a/src/types/MarkdownTextProps.ts +++ b/src/types/MarkdownTextProps.ts @@ -1,4 +1,4 @@ -import type { ViewProps, ViewStyle, TextStyle } from 'react-native'; +import type { ColorValue, ViewProps, ViewStyle, TextStyle } from 'react-native'; import type { MarkdownStyle, Md4cFlags } from './MarkdownStyle'; import type { LinkPressEvent, @@ -91,6 +91,30 @@ export interface EnrichedMarkdownTextProps extends Omit { * @platform ios, android, web */ selectable?: boolean; + /** + * Color of the text selection highlight (selected text background). + * + * - **Android**: maps to `TextView.highlightColor`. + * - **iOS**: `UITextView.tintColor` drives the selection highlight, caret, and + * selection handles together. When `selectionHandleColor` is also set, it + * takes precedence for `tintColor` (see that prop). + * - **Web**: maps to `::selection` background via a CSS variable on the root. + * + * @platform ios, android, web + */ + selectionColor?: ColorValue; + /** + * Color of the selection handles (drag anchors). + * + * - **Android**: tints the left, mid, and right handle drawables (API 29+; + * older API levels leave handles at the default theme color). + * - **iOS**: merged with selection via `tintColor` — when set, it overrides + * `selectionColor` for the shared `tintColor` (highlight + handles + caret). + * - **Web**: best-effort via `accent-color` on the root; browser support varies. + * + * @platform ios, android, web + */ + selectionHandleColor?: ColorValue; /** * Specifies whether fonts should scale to respect Text Size accessibility settings. * When false, text will not scale with the user's accessibility settings. diff --git a/src/types/MarkdownTextProps.web.ts b/src/types/MarkdownTextProps.web.ts index fa9d4014..0a9a0955 100644 --- a/src/types/MarkdownTextProps.web.ts +++ b/src/types/MarkdownTextProps.web.ts @@ -1,3 +1,4 @@ +import type { ColorValue } from 'react-native'; import type { CSSProperties, HTMLAttributes } from 'react'; import type { MarkdownStyle, Md4cFlags } from './MarkdownStyle'; import type { @@ -65,6 +66,17 @@ export interface EnrichedMarkdownTextProps * @platform ios, android, web */ selectable?: boolean; + /** + * Color of the text selection highlight (`::selection` background). + * @platform web + */ + selectionColor?: ColorValue; + /** + * Best-effort tint for selection affordances (`accent-color` on the root). + * Selection handle appearance is largely browser-controlled. + * @platform web + */ + selectionHandleColor?: ColorValue; /** * When false (default), removes trailing margin from the last element to * eliminate bottom spacing. diff --git a/src/web/EnrichedMarkdownText.tsx b/src/web/EnrichedMarkdownText.tsx index 70eb3a4c..9d53bf76 100644 --- a/src/web/EnrichedMarkdownText.tsx +++ b/src/web/EnrichedMarkdownText.tsx @@ -1,4 +1,10 @@ -import { useState, useEffect, useMemo, type CSSProperties } from 'react'; +import { + useState, + useEffect, + useMemo, + Fragment, + type CSSProperties, +} from 'react'; import type { EnrichedMarkdownTextProps } from '../types/MarkdownTextProps.web'; import { normalizeMarkdownStyle } from '../normalizeMarkdownStyle.web'; import { @@ -24,6 +30,8 @@ export const EnrichedMarkdownText = ({ containerStyle, selectable = true, dir, + selectionColor, + selectionHandleColor, ...rest }: EnrichedMarkdownTextProps) => { const normalizedStyle = useMemo( @@ -95,21 +103,46 @@ export const EnrichedMarkdownText = ({ [lastChildStyle] ); - const wrapperStyle = useMemo( - () => ({ + const wrapperStyle = useMemo(() => { + const selectionBgVar = + selectionColor != null && selectionColor !== undefined + ? String(selectionColor) + : undefined; + + return { display: 'flex', flexDirection: 'column', ...(containerStyle as CSSProperties), ...(selectable ? undefined : { userSelect: 'none' }), - }), - [containerStyle, selectable] - ); + ...(selectionBgVar != null + ? ({ ['--enrm-selection-bg']: selectionBgVar } as CSSProperties) + : null), + ...(selectionHandleColor != null && selectionHandleColor !== undefined + ? { accentColor: String(selectionHandleColor) } + : null), + }; + }, [containerStyle, selectable, selectionColor, selectionHandleColor]); + + const selectionStyle = + selectionColor != null && selectionColor !== undefined ? ( + + ) : null; if (parseError) { return ( -
-
{markdown}
-
+ + {selectionStyle} +
+
{markdown}
+
+
); } @@ -119,18 +152,21 @@ export const EnrichedMarkdownText = ({ const lastIdx = children.length - 1; return ( -
- {children.map((child, index) => ( - - ))} -
+ + {selectionStyle} +
+ {children.map((child, index) => ( + + ))} +
+
); }; From 1f75881ce202918b3b2e4f46080ff1b812330aad Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Fri, 17 Apr 2026 22:07:33 -0700 Subject: [PATCH 02/10] add example color --- apps/web-example/src/App.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/web-example/src/App.tsx b/apps/web-example/src/App.tsx index 7547f280..0564d438 100644 --- a/apps/web-example/src/App.tsx +++ b/apps/web-example/src/App.tsx @@ -153,6 +153,8 @@ export default function App() { onLinkPress={onLinkPress} onLinkLongPress={onLinkLongPress} onTaskListItemPress={onTaskListItemPress} + selectionColor="#DCDDFE" + selectionHandleColor="#5A52FA" /> From 3f9f1c5453b234e69311e979a83967d4605d18be Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Mon, 20 Apr 2026 10:10:50 -0700 Subject: [PATCH 03/10] feedback: only use selectionHandleColor in android --- ios/EnrichedMarkdown.mm | 7 ++----- ios/EnrichedMarkdownText.mm | 7 ++----- src/EnrichedMarkdownNativeComponent.ts | 9 ++------- src/EnrichedMarkdownTextNativeComponent.ts | 9 ++------- src/types/MarkdownTextProps.ts | 8 ++------ src/types/MarkdownTextProps.web.ts | 6 ------ src/web/EnrichedMarkdownText.tsx | 6 +----- 7 files changed, 11 insertions(+), 41 deletions(-) diff --git a/ios/EnrichedMarkdown.mm b/ios/EnrichedMarkdown.mm index 7bdb8cdd..8777b1b2 100644 --- a/ios/EnrichedMarkdown.mm +++ b/ios/EnrichedMarkdown.mm @@ -662,8 +662,7 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & } } - if (newViewProps.selectionHandleColor != oldViewProps.selectionHandleColor || - newViewProps.selectionColor != oldViewProps.selectionColor) { + if (newViewProps.selectionColor != oldViewProps.selectionColor) { #if !TARGET_OS_OSX for (RCTUIView *segment in _segmentViews) { if ([segment isKindOfClass:[EnrichedMarkdownInternalText class]]) { @@ -900,9 +899,7 @@ - (NSInteger)indexOfAccessibilityElement:(id)element - (void)applySelectionTintFromProps:(const EnrichedMarkdownProps &)props toTextView:(ENRMPlatformTextView *)textView { #if !TARGET_OS_OSX - if (isColorMeaningful(props.selectionHandleColor)) { - ENRMSetSelectionColor(textView, RCTUIColorFromSharedColor(props.selectionHandleColor)); - } else if (isColorMeaningful(props.selectionColor)) { + if (isColorMeaningful(props.selectionColor)) { ENRMSetSelectionColor(textView, RCTUIColorFromSharedColor(props.selectionColor)); } #endif diff --git a/ios/EnrichedMarkdownText.mm b/ios/EnrichedMarkdownText.mm index 012aa0c0..f83cf83d 100644 --- a/ios/EnrichedMarkdownText.mm +++ b/ios/EnrichedMarkdownText.mm @@ -412,12 +412,9 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & _textView.selectable = newViewProps.selectable; } - if (newViewProps.selectionHandleColor != oldViewProps.selectionHandleColor || - newViewProps.selectionColor != oldViewProps.selectionColor) { + if (newViewProps.selectionColor != oldViewProps.selectionColor) { #if !TARGET_OS_OSX - if (isColorMeaningful(newViewProps.selectionHandleColor)) { - ENRMSetSelectionColor(_textView, RCTUIColorFromSharedColor(newViewProps.selectionHandleColor)); - } else if (isColorMeaningful(newViewProps.selectionColor)) { + if (isColorMeaningful(newViewProps.selectionColor)) { ENRMSetSelectionColor(_textView, RCTUIColorFromSharedColor(newViewProps.selectionColor)); } #endif diff --git a/src/EnrichedMarkdownNativeComponent.ts b/src/EnrichedMarkdownNativeComponent.ts index 4d661917..da629967 100644 --- a/src/EnrichedMarkdownNativeComponent.ts +++ b/src/EnrichedMarkdownNativeComponent.ts @@ -278,8 +278,7 @@ export interface NativeProps extends ViewProps { * * - **Android**: maps to `TextView.highlightColor`. * - **iOS**: `UITextView.tintColor` drives the selection highlight, caret, and - * selection handles together. When `selectionHandleColor` is also set, it - * takes precedence for `tintColor` (see that prop). + * selection handles together. * - **Web**: maps to `::selection` background via a CSS variable on the root. * * @platform ios, android, web @@ -290,11 +289,7 @@ export interface NativeProps extends ViewProps { * * - **Android**: tints the left, mid, and right handle drawables (API 29+; * older API levels leave handles at the default theme color). - * - **iOS**: merged with selection via `tintColor` — when set, it overrides - * `selectionColor` for the shared `tintColor` (highlight + handles + caret). - * - **Web**: best-effort via `accent-color` on the root; browser support varies. - * - * @platform ios, android, web + * @platform android */ selectionHandleColor?: ColorValue; /** diff --git a/src/EnrichedMarkdownTextNativeComponent.ts b/src/EnrichedMarkdownTextNativeComponent.ts index 24773d91..baf66f80 100644 --- a/src/EnrichedMarkdownTextNativeComponent.ts +++ b/src/EnrichedMarkdownTextNativeComponent.ts @@ -278,8 +278,7 @@ export interface NativeProps extends ViewProps { * * - **Android**: maps to `TextView.highlightColor`. * - **iOS**: `UITextView.tintColor` drives the selection highlight, caret, and - * selection handles together. When `selectionHandleColor` is also set, it - * takes precedence for `tintColor` (see that prop). + * selection handles together. * - **Web**: maps to `::selection` background via a CSS variable on the root. * * @platform ios, android, web @@ -290,11 +289,7 @@ export interface NativeProps extends ViewProps { * * - **Android**: tints the left, mid, and right handle drawables (API 29+; * older API levels leave handles at the default theme color). - * - **iOS**: merged with selection via `tintColor` — when set, it overrides - * `selectionColor` for the shared `tintColor` (highlight + handles + caret). - * - **Web**: best-effort via `accent-color` on the root; browser support varies. - * - * @platform ios, android, web + * @platform android */ selectionHandleColor?: ColorValue; /** diff --git a/src/types/MarkdownTextProps.ts b/src/types/MarkdownTextProps.ts index 00795c49..f8b11aee 100644 --- a/src/types/MarkdownTextProps.ts +++ b/src/types/MarkdownTextProps.ts @@ -96,8 +96,7 @@ export interface EnrichedMarkdownTextProps extends Omit { * * - **Android**: maps to `TextView.highlightColor`. * - **iOS**: `UITextView.tintColor` drives the selection highlight, caret, and - * selection handles together. When `selectionHandleColor` is also set, it - * takes precedence for `tintColor` (see that prop). + * selection handles together. * - **Web**: maps to `::selection` background via a CSS variable on the root. * * @platform ios, android, web @@ -108,11 +107,8 @@ export interface EnrichedMarkdownTextProps extends Omit { * * - **Android**: tints the left, mid, and right handle drawables (API 29+; * older API levels leave handles at the default theme color). - * - **iOS**: merged with selection via `tintColor` — when set, it overrides - * `selectionColor` for the shared `tintColor` (highlight + handles + caret). - * - **Web**: best-effort via `accent-color` on the root; browser support varies. * - * @platform ios, android, web + * @platform android */ selectionHandleColor?: ColorValue; /** diff --git a/src/types/MarkdownTextProps.web.ts b/src/types/MarkdownTextProps.web.ts index 0a9a0955..d82a3d20 100644 --- a/src/types/MarkdownTextProps.web.ts +++ b/src/types/MarkdownTextProps.web.ts @@ -71,12 +71,6 @@ export interface EnrichedMarkdownTextProps * @platform web */ selectionColor?: ColorValue; - /** - * Best-effort tint for selection affordances (`accent-color` on the root). - * Selection handle appearance is largely browser-controlled. - * @platform web - */ - selectionHandleColor?: ColorValue; /** * When false (default), removes trailing margin from the last element to * eliminate bottom spacing. diff --git a/src/web/EnrichedMarkdownText.tsx b/src/web/EnrichedMarkdownText.tsx index 9d53bf76..67faa472 100644 --- a/src/web/EnrichedMarkdownText.tsx +++ b/src/web/EnrichedMarkdownText.tsx @@ -31,7 +31,6 @@ export const EnrichedMarkdownText = ({ selectable = true, dir, selectionColor, - selectionHandleColor, ...rest }: EnrichedMarkdownTextProps) => { const normalizedStyle = useMemo( @@ -117,11 +116,8 @@ export const EnrichedMarkdownText = ({ ...(selectionBgVar != null ? ({ ['--enrm-selection-bg']: selectionBgVar } as CSSProperties) : null), - ...(selectionHandleColor != null && selectionHandleColor !== undefined - ? { accentColor: String(selectionHandleColor) } - : null), }; - }, [containerStyle, selectable, selectionColor, selectionHandleColor]); + }, [containerStyle, selectable, selectionColor]); const selectionStyle = selectionColor != null && selectionColor !== undefined ? ( From 7438221b5f525adcead1c81a13e209298194353c Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Tue, 21 Apr 2026 10:06:41 -0700 Subject: [PATCH 04/10] rename --- .../enriched/markdown/EnrichedMarkdown.kt | 18 ++++++++--------- .../markdown/EnrichedMarkdownManager.kt | 4 ++-- .../enriched/markdown/EnrichedMarkdownText.kt | 20 +++++++++---------- .../markdown/EnrichedMarkdownTextManager.kt | 4 ++-- .../utils/text/view/TextSelectionColors.kt | 2 +- ios/EnrichedMarkdown.mm | 8 ++++---- src/web/EnrichedMarkdownText.tsx | 15 +++++++------- 7 files changed, 35 insertions(+), 36 deletions(-) diff --git a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt index 40f7ec4d..c52877f3 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt @@ -18,7 +18,7 @@ import com.swmansion.enriched.markdown.utils.common.FeatureFlags import com.swmansion.enriched.markdown.utils.common.MarkdownSegmentRenderer import com.swmansion.enriched.markdown.utils.common.RenderedSegment import com.swmansion.enriched.markdown.utils.common.splitASTIntoSegments -import com.swmansion.enriched.markdown.utils.text.view.applyMarkdownSelectionColors +import com.swmansion.enriched.markdown.utils.text.view.applySelectionColors import com.swmansion.enriched.markdown.utils.text.view.emitLinkLongPressEvent import com.swmansion.enriched.markdown.utils.text.view.emitLinkPressEvent import com.swmansion.enriched.markdown.views.BlockSegmentView @@ -55,8 +55,8 @@ class EnrichedMarkdown private var maxFontSizeMultiplier: Float = 0f private var allowTrailingMargin: Boolean = false private var selectable: Boolean = true - private var propSelectionColor: Int? = null - private var propSelectionHandleColor: Int? = null + private var selectionColor: Int? = null + private var selectionHandleColor: Int? = null private var onLinkPressCallback: ((String) -> Unit)? = null private var onLinkLongPressCallback: ((String) -> Unit)? = null @@ -131,19 +131,19 @@ class EnrichedMarkdown } } - fun setSelectionColorFromProps(color: Int?) { - propSelectionColor = color + fun setSelectionColor(color: Int?) { + selectionColor = color applySelectionColorsToSegments() } - fun setSelectionHandleColorFromProps(color: Int?) { - propSelectionHandleColor = color + fun setSelectionHandleColor(color: Int?) { + selectionHandleColor = color applySelectionColorsToSegments() } private fun applySelectionColorsToSegments() { segmentViews.filterIsInstance().forEach { - it.applyMarkdownSelectionColors(propSelectionColor, propSelectionHandleColor) + it.applySelectionColors(selectionColor, selectionHandleColor) } } @@ -254,7 +254,7 @@ class EnrichedMarkdown setContextMenuItems(contextMenuItemTexts, ::forwardContextMenuItemPress) } - applyMarkdownSelectionColors(propSelectionColor, propSelectionHandleColor) + applySelectionColors(selectionColor, selectionHandleColor) } private fun createTableView( diff --git a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt index 841a494d..2684a129 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt @@ -84,14 +84,14 @@ class EnrichedMarkdownManager : view: EnrichedMarkdown?, value: Int?, ) { - view?.setSelectionColorFromProps(value) + view?.setSelectionColor(value) } override fun setSelectionHandleColor( view: EnrichedMarkdown?, value: Int?, ) { - view?.setSelectionHandleColorFromProps(value) + view?.setSelectionHandleColor(value) } @ReactProp(name = "md4cFlags") diff --git a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt index 0afa3a38..891c86e8 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt @@ -22,8 +22,8 @@ import com.swmansion.enriched.markdown.styles.StyleConfig import com.swmansion.enriched.markdown.utils.text.TailFadeInAnimator import com.swmansion.enriched.markdown.utils.text.interaction.CheckboxTouchHelper import com.swmansion.enriched.markdown.utils.text.view.LinkLongPressMovementMethod -import com.swmansion.enriched.markdown.utils.text.view.applyMarkdownSelectionColors import com.swmansion.enriched.markdown.utils.text.view.applySelectableState +import com.swmansion.enriched.markdown.utils.text.view.applySelectionColors import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForCheckboxTap import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForLinkTap import com.swmansion.enriched.markdown.utils.text.view.createSelectionActionModeCallback @@ -82,8 +82,8 @@ class EnrichedMarkdownText private set var spoilerOverlay: SpoilerOverlay = SpoilerOverlay.PARTICLES - private var propSelectionColor: Int? = null - private var propSelectionHandleColor: Int? = null + private var selectionColor: Int? = null + private var selectionHandleColor: Int? = null init { setupAsMarkdownTextView() @@ -253,7 +253,7 @@ class EnrichedMarkdownText previousTextLength = styledText.length } - applyMarkdownSelectionColors(propSelectionColor, propSelectionHandleColor) + applySelectionColors(selectionColor, selectionHandleColor) } fun setContextMenuItems(items: List) { @@ -264,14 +264,14 @@ class EnrichedMarkdownText applySelectableState(selectable) } - fun setSelectionColorFromProps(color: Int?) { - propSelectionColor = color - applyMarkdownSelectionColors(propSelectionColor, propSelectionHandleColor) + fun setSelectionColor(color: Int?) { + selectionColor = color + applySelectionColors(selectionColor, selectionHandleColor) } - fun setSelectionHandleColorFromProps(color: Int?) { - propSelectionHandleColor = color - applyMarkdownSelectionColors(propSelectionColor, propSelectionHandleColor) + fun setSelectionHandleColor(color: Int?) { + selectionHandleColor = color + applySelectionColors(selectionColor, selectionHandleColor) } fun emitOnLinkPress(url: String) { diff --git a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt index 0f2993f5..ed860bc8 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt @@ -104,14 +104,14 @@ class EnrichedMarkdownTextManager : view: EnrichedMarkdownText?, value: Int?, ) { - view?.setSelectionColorFromProps(value) + view?.setSelectionColor(value) } override fun setSelectionHandleColor( view: EnrichedMarkdownText?, value: Int?, ) { - view?.setSelectionHandleColorFromProps(value) + view?.setSelectionHandleColor(value) } @ReactProp(name = "md4cFlags") diff --git a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt index 260a81c2..70dccfe8 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt @@ -11,7 +11,7 @@ import androidx.core.graphics.drawable.DrawableCompat * Handle drawables are only tinted on API 29+ where the framework exposes getters; * on older versions the handle theme defaults remain unchanged. */ -fun TextView.applyMarkdownSelectionColors( +fun TextView.applySelectionColors( selectionColor: Int?, selectionHandleColor: Int?, ) { diff --git a/ios/EnrichedMarkdown.mm b/ios/EnrichedMarkdown.mm index 8777b1b2..0d4e98f2 100644 --- a/ios/EnrichedMarkdown.mm +++ b/ios/EnrichedMarkdown.mm @@ -91,7 +91,7 @@ + (instancetype)segmentWithLatex:(NSString *)latex #endif @interface EnrichedMarkdown () -- (void)applySelectionTintFromProps:(const EnrichedMarkdownProps &)props toTextView:(ENRMPlatformTextView *)textView; +- (void)applySelectionColor:(const EnrichedMarkdownProps &)props toTextView:(ENRMPlatformTextView *)textView; @end @implementation EnrichedMarkdown { @@ -496,7 +496,7 @@ - (EnrichedMarkdownInternalText *)createTextViewForRenderedSegment:(ENRMRenderRe [view applyAttributedText:segment.attributedText context:segment.context]; const auto &selectionProps = *std::static_pointer_cast(self->_props); - [self applySelectionTintFromProps:selectionProps toTextView:view.textView]; + [self applySelectionColor:selectionProps toTextView:view.textView]; ENRMTapRecognizer *tapRecognizer = [[ENRMTapRecognizer alloc] initWithTarget:self action:@selector(textTapped:)]; [view.textView addGestureRecognizer:tapRecognizer]; @@ -667,7 +667,7 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & for (RCTUIView *segment in _segmentViews) { if ([segment isKindOfClass:[EnrichedMarkdownInternalText class]]) { ENRMPlatformTextView *tv = ((EnrichedMarkdownInternalText *)segment).textView; - [self applySelectionTintFromProps:newViewProps toTextView:tv]; + [self applySelectionColor:newViewProps toTextView:tv]; } } #endif @@ -896,7 +896,7 @@ - (NSInteger)indexOfAccessibilityElement:(id)element } #endif -- (void)applySelectionTintFromProps:(const EnrichedMarkdownProps &)props toTextView:(ENRMPlatformTextView *)textView +- (void)applySelectionColor:(const EnrichedMarkdownProps &)props toTextView:(ENRMPlatformTextView *)textView { #if !TARGET_OS_OSX if (isColorMeaningful(props.selectionColor)) { diff --git a/src/web/EnrichedMarkdownText.tsx b/src/web/EnrichedMarkdownText.tsx index 67faa472..5bc574b5 100644 --- a/src/web/EnrichedMarkdownText.tsx +++ b/src/web/EnrichedMarkdownText.tsx @@ -18,6 +18,7 @@ import type { ASTNode, RendererCallbacks, RenderCapabilities } from './types'; import { indexTaskItems, markInlineImages } from './utils'; import { loadKaTeX } from './katex'; import type { KaTeXInstance } from './katex'; +import { normalizeColor } from '../styleUtils'; export const EnrichedMarkdownText = ({ markdown, @@ -103,10 +104,9 @@ export const EnrichedMarkdownText = ({ ); const wrapperStyle = useMemo(() => { - const selectionBgVar = - selectionColor != null && selectionColor !== undefined - ? String(selectionColor) - : undefined; + const selectionBgVar = selectionColor + ? normalizeColor(String(selectionColor)) + : undefined; return { display: 'flex', @@ -119,12 +119,11 @@ export const EnrichedMarkdownText = ({ }; }, [containerStyle, selectable, selectionColor]); - const selectionStyle = - selectionColor != null && selectionColor !== undefined ? ( - - ) : null; + ) : null; if (parseError) { return ( From 9ff8b6abcf95c04419ff76922c418868fa3f82f9 Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Tue, 21 Apr 2026 10:14:33 -0700 Subject: [PATCH 05/10] clean up jsdoc --- src/EnrichedMarkdownNativeComponent.ts | 11 ++++------- src/EnrichedMarkdownTextNativeComponent.ts | 11 ++++------- src/types/MarkdownTextProps.ts | 12 ++++-------- src/types/MarkdownTextProps.web.ts | 2 +- 4 files changed, 13 insertions(+), 23 deletions(-) diff --git a/src/EnrichedMarkdownNativeComponent.ts b/src/EnrichedMarkdownNativeComponent.ts index da629967..71b3d8ac 100644 --- a/src/EnrichedMarkdownNativeComponent.ts +++ b/src/EnrichedMarkdownNativeComponent.ts @@ -274,21 +274,18 @@ export interface NativeProps extends ViewProps { */ selectable?: boolean; /** - * Color of the text selection highlight (selected text background). + * Color of the text selection highlight. * - * - **Android**: maps to `TextView.highlightColor`. - * - **iOS**: `UITextView.tintColor` drives the selection highlight, caret, and - * selection handles together. - * - **Web**: maps to `::selection` background via a CSS variable on the root. + * On iOS, this also affects the caret and selection handle colors + * (they share a single tint). * * @platform ios, android, web */ selectionColor?: ColorValue; /** * Color of the selection handles (drag anchors). + * No-op on API levels below 29. * - * - **Android**: tints the left, mid, and right handle drawables (API 29+; - * older API levels leave handles at the default theme color). * @platform android */ selectionHandleColor?: ColorValue; diff --git a/src/EnrichedMarkdownTextNativeComponent.ts b/src/EnrichedMarkdownTextNativeComponent.ts index baf66f80..13add176 100644 --- a/src/EnrichedMarkdownTextNativeComponent.ts +++ b/src/EnrichedMarkdownTextNativeComponent.ts @@ -274,21 +274,18 @@ export interface NativeProps extends ViewProps { */ selectable?: boolean; /** - * Color of the text selection highlight (selected text background). + * Color of the text selection highlight. * - * - **Android**: maps to `TextView.highlightColor`. - * - **iOS**: `UITextView.tintColor` drives the selection highlight, caret, and - * selection handles together. - * - **Web**: maps to `::selection` background via a CSS variable on the root. + * On iOS, this also affects the caret and selection handle colors + * (they share a single tint). * * @platform ios, android, web */ selectionColor?: ColorValue; /** * Color of the selection handles (drag anchors). + * No-op on API levels below 29. * - * - **Android**: tints the left, mid, and right handle drawables (API 29+; - * older API levels leave handles at the default theme color). * @platform android */ selectionHandleColor?: ColorValue; diff --git a/src/types/MarkdownTextProps.ts b/src/types/MarkdownTextProps.ts index f8b11aee..bade9723 100644 --- a/src/types/MarkdownTextProps.ts +++ b/src/types/MarkdownTextProps.ts @@ -92,21 +92,17 @@ export interface EnrichedMarkdownTextProps extends Omit { */ selectable?: boolean; /** - * Color of the text selection highlight (selected text background). + * Color of the text selection highlight. * - * - **Android**: maps to `TextView.highlightColor`. - * - **iOS**: `UITextView.tintColor` drives the selection highlight, caret, and - * selection handles together. - * - **Web**: maps to `::selection` background via a CSS variable on the root. + * On iOS, this also affects the caret and selection handle colors + * (they share a single tint). * * @platform ios, android, web */ selectionColor?: ColorValue; /** * Color of the selection handles (drag anchors). - * - * - **Android**: tints the left, mid, and right handle drawables (API 29+; - * older API levels leave handles at the default theme color). + * No-op on API levels below 29. * * @platform android */ diff --git a/src/types/MarkdownTextProps.web.ts b/src/types/MarkdownTextProps.web.ts index d82a3d20..91034b54 100644 --- a/src/types/MarkdownTextProps.web.ts +++ b/src/types/MarkdownTextProps.web.ts @@ -67,7 +67,7 @@ export interface EnrichedMarkdownTextProps */ selectable?: boolean; /** - * Color of the text selection highlight (`::selection` background). + * Color of the text selection highlight. * @platform web */ selectionColor?: ColorValue; From 2752a6ea7251e083cebcb5b35155819e132af1cc Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Tue, 21 Apr 2026 10:26:28 -0700 Subject: [PATCH 06/10] android: wrap each handle in try catch --- .../utils/text/view/TextSelectionColors.kt | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt index 70dccfe8..ed7423f5 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt @@ -1,10 +1,13 @@ package com.swmansion.enriched.markdown.utils.text.view import android.os.Build +import android.util.Log import android.widget.TextView import androidx.annotation.ColorInt import androidx.core.graphics.drawable.DrawableCompat +private const val TAG = "TextSelectionColors" + /** * Applies selection highlight and (where supported) handle tinting to a [TextView]. * @@ -25,23 +28,20 @@ private fun TextView.applySelectionHandleTint( if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { return } - try { - tintHandle(textSelectHandleLeft, color)?.let { setTextSelectHandleLeft(it) } - tintHandle(textSelectHandle, color)?.let { setTextSelectHandle(it) } - tintHandle(textSelectHandleRight, color)?.let { setTextSelectHandleRight(it) } - } catch (_: Exception) { - // Defensive: OEM TextView variants may not support all handle accessors. - } -} -private fun tintHandle( - drawable: android.graphics.drawable.Drawable?, - @ColorInt color: Int, -): android.graphics.drawable.Drawable? { - if (drawable == null) { - return null + val handles = + listOf( + this::getTextSelectHandleLeft to this::setTextSelectHandleLeft, + this::getTextSelectHandle to this::setTextSelectHandle, + this::getTextSelectHandleRight to this::setTextSelectHandleRight, + ) + + handles.forEach { (getter, setter) -> + try { + getter()?.mutate()?.also { DrawableCompat.setTint(it, color) }?.let(setter) + } catch (e: LinkageError) { + // Defensive: OEM TextView variants may strip individual handle accessors. + Log.w(TAG, "Selection handle tint skipped: ${e.message}") + } } - val mutated = drawable.mutate() - DrawableCompat.setTint(mutated, color) - return mutated } From 1a9caa3649aba036580d2a0afd5d74573c3f9160 Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Tue, 21 Apr 2026 10:27:07 -0700 Subject: [PATCH 07/10] ios: handle resetting color --- ios/EnrichedMarkdown.mm | 2 ++ ios/EnrichedMarkdownText.mm | 2 ++ ios/input/EnrichedMarkdownTextInput.mm | 2 ++ 3 files changed, 6 insertions(+) diff --git a/ios/EnrichedMarkdown.mm b/ios/EnrichedMarkdown.mm index 0d4e98f2..0595356b 100644 --- a/ios/EnrichedMarkdown.mm +++ b/ios/EnrichedMarkdown.mm @@ -901,6 +901,8 @@ - (void)applySelectionColor:(const EnrichedMarkdownProps &)props toTextView:(ENR #if !TARGET_OS_OSX if (isColorMeaningful(props.selectionColor)) { ENRMSetSelectionColor(textView, RCTUIColorFromSharedColor(props.selectionColor)); + } else { + ENRMSetSelectionColor(textView, nil); // resets to inherited / system tint } #endif } diff --git a/ios/EnrichedMarkdownText.mm b/ios/EnrichedMarkdownText.mm index f83cf83d..e1d0c40b 100644 --- a/ios/EnrichedMarkdownText.mm +++ b/ios/EnrichedMarkdownText.mm @@ -416,6 +416,8 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & #if !TARGET_OS_OSX if (isColorMeaningful(newViewProps.selectionColor)) { ENRMSetSelectionColor(_textView, RCTUIColorFromSharedColor(newViewProps.selectionColor)); + } else { + ENRMSetSelectionColor(_textView, nil); // resets to inherited / system tint } #endif } diff --git a/ios/input/EnrichedMarkdownTextInput.mm b/ios/input/EnrichedMarkdownTextInput.mm index 016d341e..b175f7c7 100644 --- a/ios/input/EnrichedMarkdownTextInput.mm +++ b/ios/input/EnrichedMarkdownTextInput.mm @@ -292,6 +292,8 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & if (newViewProps.selectionColor != oldViewProps.selectionColor) { if (isColorMeaningful(newViewProps.selectionColor)) { ENRMSetSelectionColor(_textView, RCTUIColorFromSharedColor(newViewProps.selectionColor)); + } else { + ENRMSetSelectionColor(_textView, nil); // resets to inherited / system tint } } From 90edd78ce1ca06060ffddda8de56a75f42431ff9 Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Tue, 21 Apr 2026 11:09:41 -0700 Subject: [PATCH 08/10] android: fix --- .../utils/text/view/TextSelectionColors.kt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt index ed7423f5..4b8e700a 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt @@ -1,5 +1,6 @@ package com.swmansion.enriched.markdown.utils.text.view +import android.graphics.drawable.Drawable import android.os.Build import android.util.Log import android.widget.TextView @@ -8,6 +9,9 @@ import androidx.core.graphics.drawable.DrawableCompat private const val TAG = "TextSelectionColors" +private typealias HandleGetter = (TextView) -> Drawable? +private typealias HandleSetter = (TextView, Drawable) -> Unit + /** * Applies selection highlight and (where supported) handle tinting to a [TextView]. * @@ -29,16 +33,16 @@ private fun TextView.applySelectionHandleTint( return } - val handles = + val handles: List> = listOf( - this::getTextSelectHandleLeft to this::setTextSelectHandleLeft, - this::getTextSelectHandle to this::setTextSelectHandle, - this::getTextSelectHandleRight to this::setTextSelectHandleRight, + TextView::getTextSelectHandleLeft to { tv, d -> tv.setTextSelectHandleLeft(d) }, + TextView::getTextSelectHandle to { tv, d -> tv.setTextSelectHandle(d) }, + TextView::getTextSelectHandleRight to { tv, d -> tv.setTextSelectHandleRight(d) }, ) handles.forEach { (getter, setter) -> try { - getter()?.mutate()?.also { DrawableCompat.setTint(it, color) }?.let(setter) + getter(this)?.mutate()?.also { DrawableCompat.setTint(it, color) }?.let { setter(this, it) } } catch (e: LinkageError) { // Defensive: OEM TextView variants may strip individual handle accessors. Log.w(TAG, "Selection handle tint skipped: ${e.message}") From 79f23a7f162038c50ded3e3f5374f45118f08c12 Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Wed, 22 Apr 2026 11:53:18 -0700 Subject: [PATCH 09/10] feedback: minor changes --- ios/EnrichedMarkdown.mm | 2 +- ios/EnrichedMarkdownText.mm | 2 +- ios/input/EnrichedMarkdownTextInput.mm | 2 +- src/web/EnrichedMarkdownText.tsx | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ios/EnrichedMarkdown.mm b/ios/EnrichedMarkdown.mm index 0595356b..92c73f07 100644 --- a/ios/EnrichedMarkdown.mm +++ b/ios/EnrichedMarkdown.mm @@ -902,7 +902,7 @@ - (void)applySelectionColor:(const EnrichedMarkdownProps &)props toTextView:(ENR if (isColorMeaningful(props.selectionColor)) { ENRMSetSelectionColor(textView, RCTUIColorFromSharedColor(props.selectionColor)); } else { - ENRMSetSelectionColor(textView, nil); // resets to inherited / system tint + ENRMSetSelectionColor(textView, nil); } #endif } diff --git a/ios/EnrichedMarkdownText.mm b/ios/EnrichedMarkdownText.mm index e1d0c40b..47bb9970 100644 --- a/ios/EnrichedMarkdownText.mm +++ b/ios/EnrichedMarkdownText.mm @@ -417,7 +417,7 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & if (isColorMeaningful(newViewProps.selectionColor)) { ENRMSetSelectionColor(_textView, RCTUIColorFromSharedColor(newViewProps.selectionColor)); } else { - ENRMSetSelectionColor(_textView, nil); // resets to inherited / system tint + ENRMSetSelectionColor(_textView, nil); } #endif } diff --git a/ios/input/EnrichedMarkdownTextInput.mm b/ios/input/EnrichedMarkdownTextInput.mm index b175f7c7..a01345cc 100644 --- a/ios/input/EnrichedMarkdownTextInput.mm +++ b/ios/input/EnrichedMarkdownTextInput.mm @@ -293,7 +293,7 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & if (isColorMeaningful(newViewProps.selectionColor)) { ENRMSetSelectionColor(_textView, RCTUIColorFromSharedColor(newViewProps.selectionColor)); } else { - ENRMSetSelectionColor(_textView, nil); // resets to inherited / system tint + ENRMSetSelectionColor(_textView, nil); } } diff --git a/src/web/EnrichedMarkdownText.tsx b/src/web/EnrichedMarkdownText.tsx index 5bc574b5..e26ee273 100644 --- a/src/web/EnrichedMarkdownText.tsx +++ b/src/web/EnrichedMarkdownText.tsx @@ -104,7 +104,7 @@ export const EnrichedMarkdownText = ({ ); const wrapperStyle = useMemo(() => { - const selectionBgVar = selectionColor + const selectionColorCss = selectionColor ? normalizeColor(String(selectionColor)) : undefined; @@ -113,8 +113,8 @@ export const EnrichedMarkdownText = ({ flexDirection: 'column', ...(containerStyle as CSSProperties), ...(selectable ? undefined : { userSelect: 'none' }), - ...(selectionBgVar != null - ? ({ ['--enrm-selection-bg']: selectionBgVar } as CSSProperties) + ...(selectionColorCss != null + ? ({ ['--enrm-selection-bg']: selectionColorCss } as CSSProperties) : null), }; }, [containerStyle, selectable, selectionColor]); From 6972ae2ee57c557e9cf5eb52a4ae4a2c172352c3 Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Wed, 22 Apr 2026 11:56:24 -0700 Subject: [PATCH 10/10] feedback: refactor applySelectionHandleTint --- .../enriched/markdown/utils/text/view/TextSelectionColors.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt index 4b8e700a..1a6d7242 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt @@ -29,9 +29,7 @@ fun TextView.applySelectionColors( private fun TextView.applySelectionHandleTint( @ColorInt color: Int, ) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - return - } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return val handles: List> = listOf( @@ -44,7 +42,6 @@ private fun TextView.applySelectionHandleTint( try { getter(this)?.mutate()?.also { DrawableCompat.setTint(it, color) }?.let { setter(this, it) } } catch (e: LinkageError) { - // Defensive: OEM TextView variants may strip individual handle accessors. Log.w(TAG, "Selection handle tint skipped: ${e.message}") } }