1- import React , { useMemo , useCallback } from 'react' ;
2- import {
3- Text ,
4- StyleSheet ,
5- type StyleProp ,
6- type TextStyle ,
7- TouchableWithoutFeedback ,
8- } from 'react-native' ;
1+ import React from 'react' ;
92import type {
103 TextSegmentProps ,
114 HighlightedTextProps ,
125 HighlightedSegmentProps ,
136} from '../types' ;
7+ import { Text , StyleSheet } from 'react-native' ;
148
159const HighlightedText : React . FC < HighlightedTextProps > = ( {
1610 text,
@@ -20,85 +14,97 @@ const HighlightedText: React.FC<HighlightedTextProps> = ({
2014 onHighlightedPress,
2115 ...rest
2216} ) => {
23- const segments = useMemo ( ( ) => {
24- const sorted = [ ...highlights ] . sort ( ( a , b ) => a . start - b . start ) ;
17+ const baseStyle = [ style , highlightedStyle ?? styles . isHighlighted ] ;
18+
19+ const segments = React . useMemo ( ( ) => {
20+ if ( ! text || ! highlights . length ) {
21+ return [ ] ;
22+ }
23+ let cursor = 0 ;
24+ let isSorted = true ;
2525
2626 const parts : TextSegmentProps [ ] = [ ] ;
27- let currentIndex = 0 ;
2827
29- for ( let i = 0 ; i < sorted . length ; ++ i ) {
30- const currentSegment = sorted [ i ] as HighlightedSegmentProps ;
31- const { start, end} = currentSegment ;
28+ for ( let i = 1 ; i < highlights . length ; i ++ ) {
29+ if ( highlights [ i - 1 ] ! . start > highlights [ i ] ! . start ) {
30+ isSorted = false ;
31+ break ;
32+ }
33+ }
34+ const ordered = isSorted
35+ ? highlights
36+ : [ ...highlights ] . sort ( ( a , b ) => a . start - b . start ) ;
37+
38+ for ( let i = 0 ; i < ordered . length ; i ++ ) {
39+ const {
40+ end,
41+ start,
42+ style : segmentStyle ,
43+ } = ordered [ i ] as HighlightedSegmentProps ;
44+
45+ if ( start >= text . length || end <= cursor ) continue ;
3246
33- if ( start > currentIndex ) {
47+ const clampedStart = Math . max ( cursor , start ) ;
48+ const clampedEnd = Math . min ( end , text . length ) ;
49+
50+ if ( clampedStart > cursor ) {
3451 parts . push ( {
3552 isHighlighted : false ,
36- text : text . slice ( currentIndex , start ) ,
53+ text : text . slice ( cursor , clampedStart ) ,
3754 } ) ;
3855 }
3956 parts . push ( {
40- ...currentSegment ,
57+ end : clampedEnd ,
58+ start : clampedStart ,
59+ style : segmentStyle ,
4160 isHighlighted : true ,
42- text : text . slice ( start , end ) ,
61+ text : text . slice ( clampedStart , clampedEnd ) ,
4362 } ) ;
44- currentIndex = end ;
63+
64+ cursor = clampedEnd ;
4565 }
46- if ( currentIndex < text . length ) {
66+ if ( cursor < text . length ) {
4767 parts . push ( {
4868 isHighlighted : false ,
49- text : text . slice ( currentIndex ) ,
69+ text : text . slice ( cursor ) ,
5070 } ) ;
5171 }
5272 return parts ;
5373 } , [ highlights , text ] ) ;
5474
55- const getHighlightedSegmentStyle = useCallback (
56- ( isHighlighted = false , segmentStyle ?: StyleProp < TextStyle > ) => {
57- return ! isHighlighted
58- ? style
59- : StyleSheet . flatten ( [
60- style ,
61- highlightedStyle ?? styles . isHighlighted ,
62- segmentStyle ,
63- ] ) ;
75+ const onHighlightedSegmentPress = React . useCallback (
76+ ( segment : TextSegmentProps ) => {
77+ if ( ! segment . isHighlighted ) return ;
78+ onHighlightedPress ?.( {
79+ end : segment . end ! ,
80+ start : segment . start ! ,
81+ text : segment . text ,
82+ } ) ;
6483 } ,
65- [ highlightedStyle , style ] ,
84+ [ onHighlightedPress ] ,
6685 ) ;
6786
68- const renderText = useCallback (
69- ( segment : TextSegmentProps , index : number ) => {
70- return ! segment . isHighlighted ? (
71- < Text
72- key = { index }
73- style = { getHighlightedSegmentStyle ( segment . isHighlighted ) } >
74- { segment . text }
75- </ Text >
76- ) : (
77- < TouchableWithoutFeedback
78- key = { index }
79- onPress = { ( ) =>
80- onHighlightedPress ?.( {
81- end : segment . end ! ,
82- text : segment . text ,
83- start : segment . start ! ,
84- } )
85- } >
86- < Text
87- style = { getHighlightedSegmentStyle (
88- segment . isHighlighted ,
89- segment . style ,
90- ) } >
91- { segment . text }
92- </ Text >
93- </ TouchableWithoutFeedback >
94- ) ;
95- } ,
96- [ getHighlightedSegmentStyle , onHighlightedPress ] ,
97- ) ;
87+ const renderText = ( segment : TextSegmentProps , index : number ) => {
88+ const segmentStyle = segment . isHighlighted
89+ ? segment . style
90+ ? [ baseStyle , segment . style ]
91+ : baseStyle
92+ : style ;
93+
94+ return (
95+ < Text
96+ style = { segmentStyle }
97+ suppressHighlighting
98+ key = { `segment-${ index } ` }
99+ onPress = { ( ) => onHighlightedSegmentPress ( segment ) } >
100+ { segment . text }
101+ </ Text >
102+ ) ;
103+ } ;
98104
99105 return (
100106 < Text style = { style } { ...rest } >
101- { segments . map ( renderText ) }
107+ { segments . length ? segments . map ( renderText ) : text }
102108 </ Text >
103109 ) ;
104110} ;
0 commit comments