react-native-advanced-text 0.1.34 → 0.1.41
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.
- package/README.md +7 -2
- package/android/src/main/java/com/advancedtext/AdvancedTextView.kt +110 -32
- package/android/src/main/java/com/advancedtext/AdvancedTextViewManager.kt +20 -8
- package/ios/AdvancedTextView.mm +115 -47
- package/lib/module/AdvancedText.js +1 -3
- package/lib/module/AdvancedText.js.map +1 -1
- package/lib/module/AdvancedTextViewNativeComponent.ts +3 -1
- package/lib/typescript/src/AdvancedText.d.ts.map +1 -1
- package/lib/typescript/src/AdvancedTextViewNativeComponent.d.ts +3 -1
- package/lib/typescript/src/AdvancedTextViewNativeComponent.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/AdvancedText.tsx +0 -2
- package/src/AdvancedTextViewNativeComponent.ts +3 -1
package/README.md
CHANGED
|
@@ -14,12 +14,11 @@ npm install react-native-advanced-text
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
```js
|
|
17
|
-
import {
|
|
17
|
+
import { AdvancedText } from "react-native-advanced-text";
|
|
18
18
|
|
|
19
19
|
<AdvancedText
|
|
20
20
|
text={'This is an example of AdvancedText component. Tap on any word to see the event in action.'}
|
|
21
21
|
style={[styles.AdvancedText, { minHeight }]}
|
|
22
|
-
indicatorWordIndex={2}
|
|
23
22
|
onWordPress={(event) => {
|
|
24
23
|
console.log({event})
|
|
25
24
|
}}
|
|
@@ -28,9 +27,15 @@ import { AdvancedTextView } from "react-native-advanced-text";
|
|
|
28
27
|
console.log({event})
|
|
29
28
|
}}
|
|
30
29
|
highlightedWords={[
|
|
30
|
+
{
|
|
31
|
+
index: 2,
|
|
32
|
+
highlightColor: '#FFD60A',
|
|
33
|
+
borderRadius: 10,
|
|
34
|
+
},
|
|
31
35
|
{
|
|
32
36
|
index: 4,
|
|
33
37
|
highlightColor: '#6baeffb5',
|
|
38
|
+
borderRadius: 6,
|
|
34
39
|
},
|
|
35
40
|
]}
|
|
36
41
|
fontSize={24}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
package com.advancedtext
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
|
+
import android.graphics.Canvas
|
|
4
5
|
import android.graphics.Color
|
|
5
6
|
import android.graphics.Point
|
|
7
|
+
import android.graphics.RectF
|
|
6
8
|
import android.text.SpannableString
|
|
7
9
|
import android.text.Spannable
|
|
8
10
|
import android.text.Spanned
|
|
9
11
|
import android.text.TextPaint
|
|
10
12
|
import android.text.method.ArrowKeyMovementMethod
|
|
11
13
|
import android.text.style.ClickableSpan
|
|
12
|
-
import android.text.style.BackgroundColorSpan
|
|
13
|
-
import android.text.style.ForegroundColorSpan
|
|
14
14
|
import android.util.AttributeSet
|
|
15
15
|
import android.util.Log
|
|
16
16
|
import android.view.ActionMode
|
|
@@ -32,7 +32,6 @@ class AdvancedTextView : TextView {
|
|
|
32
32
|
|
|
33
33
|
private var highlightedWords: List<HighlightedWord> = emptyList()
|
|
34
34
|
private var menuOptions: List<String> = emptyList()
|
|
35
|
-
private var indicatorWordIndex: Int = -1
|
|
36
35
|
private var lastSelectedText: String = ""
|
|
37
36
|
private var customActionMode: ActionMode? = null
|
|
38
37
|
private var currentText: String = ""
|
|
@@ -43,6 +42,9 @@ class AdvancedTextView : TextView {
|
|
|
43
42
|
private var fontFamily: String = "sans-serif"
|
|
44
43
|
private var lineHeightMultiplier: Float = 1.0f
|
|
45
44
|
|
|
45
|
+
private var indicatorWordIndex: Int = -1
|
|
46
|
+
private var indicatorColor: String = ""
|
|
47
|
+
|
|
46
48
|
private var wordPositions: List<WordPosition> = emptyList()
|
|
47
49
|
|
|
48
50
|
constructor(context: Context?) : super(context) { init() }
|
|
@@ -157,18 +159,24 @@ class AdvancedTextView : TextView {
|
|
|
157
159
|
this.menuOptions = menuOptions
|
|
158
160
|
}
|
|
159
161
|
|
|
162
|
+
fun setIndicatorWordIndex(index: Int) {
|
|
163
|
+
if (indicatorWordIndex == index) return
|
|
164
|
+
indicatorWordIndex = index
|
|
165
|
+
invalidate()
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
fun setIndicatorColor(color: String) {
|
|
169
|
+
if (indicatorColor == color) return
|
|
170
|
+
indicatorColor = color
|
|
171
|
+
invalidate()
|
|
172
|
+
}
|
|
173
|
+
|
|
160
174
|
fun setHighlightedWords(highlightedWords: List<HighlightedWord>) {
|
|
161
175
|
if (this.highlightedWords == highlightedWords) return
|
|
162
176
|
this.highlightedWords = highlightedWords
|
|
163
177
|
updateTextWithHighlights()
|
|
164
178
|
}
|
|
165
179
|
|
|
166
|
-
fun setIndicatorWordIndex(index: Int) {
|
|
167
|
-
if (this.indicatorWordIndex == index) return
|
|
168
|
-
this.indicatorWordIndex = index
|
|
169
|
-
updateTextWithHighlights()
|
|
170
|
-
}
|
|
171
|
-
|
|
172
180
|
private fun calculateWordPositions(text: String) {
|
|
173
181
|
if (text.isEmpty()) {
|
|
174
182
|
wordPositions = emptyList()
|
|
@@ -206,27 +214,12 @@ class AdvancedTextView : TextView {
|
|
|
206
214
|
val spannableString = SpannableString(currentText)
|
|
207
215
|
|
|
208
216
|
wordPositions.forEach { wordPos ->
|
|
209
|
-
highlightedWords.find { it.index == wordPos.index }?.let { highlightedWord ->
|
|
210
|
-
val color = parseColor(highlightedWord.highlightColor)
|
|
211
|
-
spannableString.setSpan(
|
|
212
|
-
BackgroundColorSpan(color),
|
|
213
|
-
wordPos.start,
|
|
214
|
-
wordPos.extendedEnd,
|
|
215
|
-
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
216
|
-
)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (wordPos.index == indicatorWordIndex) {
|
|
220
|
-
spannableString.setSpan(
|
|
221
|
-
ForegroundColorSpan(Color.parseColor(textColor)),
|
|
222
|
-
wordPos.start,
|
|
223
|
-
wordPos.end,
|
|
224
|
-
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
225
|
-
)
|
|
226
|
-
}
|
|
227
|
-
|
|
228
217
|
spannableString.setSpan(
|
|
229
|
-
WordClickableSpan(
|
|
218
|
+
WordClickableSpan(
|
|
219
|
+
wordIndex = wordPos.index,
|
|
220
|
+
word = wordPos.word,
|
|
221
|
+
wordColor = parseColor(textColor)
|
|
222
|
+
),
|
|
230
223
|
wordPos.start,
|
|
231
224
|
wordPos.end,
|
|
232
225
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
@@ -258,6 +251,89 @@ class AdvancedTextView : TextView {
|
|
|
258
251
|
}
|
|
259
252
|
}
|
|
260
253
|
|
|
254
|
+
override fun onDraw(canvas: Canvas) {
|
|
255
|
+
drawHighlightedWordBackgrounds(canvas)
|
|
256
|
+
super.onDraw(canvas)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private fun drawHighlightedWordBackgrounds(canvas: Canvas) {
|
|
260
|
+
val currentLayout = layout ?: return
|
|
261
|
+
val density = resources.displayMetrics.density
|
|
262
|
+
val horizontalPadding = 6f * density
|
|
263
|
+
val verticalPadding = 2f * density
|
|
264
|
+
val originalColor = paint.color
|
|
265
|
+
|
|
266
|
+
highlightedWords.forEach { highlightedWord ->
|
|
267
|
+
val wordPos = wordPositions.find { it.index == highlightedWord.index } ?: return@forEach
|
|
268
|
+
val start = wordPos.start
|
|
269
|
+
val end = wordPos.extendedEnd.coerceAtMost(currentText.length)
|
|
270
|
+
|
|
271
|
+
if (start >= end) return@forEach
|
|
272
|
+
|
|
273
|
+
val startLine = currentLayout.getLineForOffset(start)
|
|
274
|
+
val safeEndOffset = (end - 1).coerceAtLeast(start)
|
|
275
|
+
val endLine = currentLayout.getLineForOffset(safeEndOffset)
|
|
276
|
+
|
|
277
|
+
paint.color = parseColor(highlightedWord.highlightColor)
|
|
278
|
+
|
|
279
|
+
for (line in startLine..endLine) {
|
|
280
|
+
val lineStartOffset = maxOf(start, currentLayout.getLineStart(line))
|
|
281
|
+
val lineEndOffset = minOf(end, currentLayout.getLineEnd(line))
|
|
282
|
+
if (lineStartOffset >= lineEndOffset) continue
|
|
283
|
+
|
|
284
|
+
val left = currentLayout.getPrimaryHorizontal(lineStartOffset)
|
|
285
|
+
val right = currentLayout.getPrimaryHorizontal(lineEndOffset)
|
|
286
|
+
val top = currentLayout.getLineTop(line).toFloat()
|
|
287
|
+
val bottom = currentLayout.getLineBottom(line).toFloat()
|
|
288
|
+
val rect = RectF(
|
|
289
|
+
totalPaddingLeft + left - horizontalPadding,
|
|
290
|
+
totalPaddingTop + top + verticalPadding,
|
|
291
|
+
totalPaddingLeft + right + horizontalPadding,
|
|
292
|
+
totalPaddingTop + bottom - verticalPadding
|
|
293
|
+
)
|
|
294
|
+
val radius = highlightedWord.borderRadius * density
|
|
295
|
+
canvas.drawRoundRect(rect, radius, radius, paint)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Draw indicator for indicatorWordIndex
|
|
300
|
+
if (indicatorWordIndex >= 0 && indicatorColor.isNotEmpty()) {
|
|
301
|
+
val wordPos = wordPositions.find { it.index == indicatorWordIndex }
|
|
302
|
+
if (wordPos != null) {
|
|
303
|
+
val start = wordPos.start
|
|
304
|
+
val end = wordPos.extendedEnd.coerceAtMost(currentText.length)
|
|
305
|
+
|
|
306
|
+
if (start < end) {
|
|
307
|
+
val startLine = currentLayout.getLineForOffset(start)
|
|
308
|
+
val safeEndOffset = (end - 1).coerceAtLeast(start)
|
|
309
|
+
val endLine = currentLayout.getLineForOffset(safeEndOffset)
|
|
310
|
+
|
|
311
|
+
paint.color = parseColor(indicatorColor)
|
|
312
|
+
|
|
313
|
+
for (line in startLine..endLine) {
|
|
314
|
+
val lineStartOffset = maxOf(start, currentLayout.getLineStart(line))
|
|
315
|
+
val lineEndOffset = minOf(end, currentLayout.getLineEnd(line))
|
|
316
|
+
if (lineStartOffset >= lineEndOffset) continue
|
|
317
|
+
|
|
318
|
+
val left = currentLayout.getPrimaryHorizontal(lineStartOffset)
|
|
319
|
+
val right = currentLayout.getPrimaryHorizontal(lineEndOffset)
|
|
320
|
+
val top = currentLayout.getLineTop(line).toFloat()
|
|
321
|
+
val bottom = currentLayout.getLineBottom(line).toFloat()
|
|
322
|
+
val rect = RectF(
|
|
323
|
+
totalPaddingLeft + left - horizontalPadding,
|
|
324
|
+
totalPaddingTop + top + verticalPadding,
|
|
325
|
+
totalPaddingLeft + right + horizontalPadding,
|
|
326
|
+
totalPaddingTop + bottom - verticalPadding
|
|
327
|
+
)
|
|
328
|
+
canvas.drawRoundRect(rect, 0f, 0f, paint)
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
paint.color = originalColor
|
|
335
|
+
}
|
|
336
|
+
|
|
261
337
|
private fun parseColor(colorString: String): Int {
|
|
262
338
|
return try {
|
|
263
339
|
Color.parseColor(colorString)
|
|
@@ -284,7 +360,8 @@ class AdvancedTextView : TextView {
|
|
|
284
360
|
|
|
285
361
|
private inner class WordClickableSpan(
|
|
286
362
|
private val wordIndex: Int,
|
|
287
|
-
private val word: String
|
|
363
|
+
private val word: String,
|
|
364
|
+
private val wordColor: Int
|
|
288
365
|
) : ClickableSpan() {
|
|
289
366
|
|
|
290
367
|
override fun onClick(widget: View) {
|
|
@@ -295,7 +372,7 @@ class AdvancedTextView : TextView {
|
|
|
295
372
|
override fun updateDrawState(ds: TextPaint) {
|
|
296
373
|
super.updateDrawState(ds)
|
|
297
374
|
ds.isUnderlineText = false
|
|
298
|
-
ds.color =
|
|
375
|
+
ds.color = wordColor
|
|
299
376
|
}
|
|
300
377
|
}
|
|
301
378
|
|
|
@@ -381,5 +458,6 @@ class AdvancedTextView : TextView {
|
|
|
381
458
|
|
|
382
459
|
data class HighlightedWord(
|
|
383
460
|
val index: Int,
|
|
384
|
-
val highlightColor: String
|
|
461
|
+
val highlightColor: String,
|
|
462
|
+
val borderRadius: Float = 0f
|
|
385
463
|
)
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
package com.advancedtext
|
|
3
3
|
|
|
4
4
|
import android.graphics.Color
|
|
5
|
-
import android.util.TypedValue
|
|
6
5
|
import android.view.ViewGroup
|
|
7
6
|
import com.facebook.react.bridge.ReadableArray
|
|
8
7
|
import com.facebook.react.module.annotations.ReactModule
|
|
@@ -50,7 +49,12 @@ class AdvancedTextViewManager : SimpleViewManager<AdvancedTextView>() {
|
|
|
50
49
|
words.add(
|
|
51
50
|
HighlightedWord(
|
|
52
51
|
index = wordMap.getInt("index"),
|
|
53
|
-
highlightColor = wordMap.getString("highlightColor") ?: "#FFFF00"
|
|
52
|
+
highlightColor = wordMap.getString("highlightColor") ?: "#FFFF00",
|
|
53
|
+
borderRadius = if (wordMap.hasKey("borderRadius")) {
|
|
54
|
+
wordMap.getDouble("borderRadius").toFloat()
|
|
55
|
+
} else {
|
|
56
|
+
0f
|
|
57
|
+
}
|
|
54
58
|
)
|
|
55
59
|
)
|
|
56
60
|
}
|
|
@@ -77,12 +81,6 @@ class AdvancedTextViewManager : SimpleViewManager<AdvancedTextView>() {
|
|
|
77
81
|
view?.setMenuOptions(options)
|
|
78
82
|
}
|
|
79
83
|
|
|
80
|
-
@ReactProp(name = "indicatorWordIndex")
|
|
81
|
-
fun setIndicatorWordIndex(view: AdvancedTextView?, index: Int) {
|
|
82
|
-
android.util.Log.d(NAME, "setIndicatorWordIndex: $index")
|
|
83
|
-
view?.setIndicatorWordIndex(if (index >= 0) index else -1)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
84
|
@ReactProp(name = "color")
|
|
87
85
|
fun setColor(view: AdvancedTextView?, color: String?) {
|
|
88
86
|
android.util.Log.d(NAME, "setColor called with: $color")
|
|
@@ -124,6 +122,20 @@ class AdvancedTextViewManager : SimpleViewManager<AdvancedTextView>() {
|
|
|
124
122
|
}
|
|
125
123
|
|
|
126
124
|
|
|
125
|
+
@ReactProp(name = "indicatorWordIndex")
|
|
126
|
+
fun setIndicatorWordIndex(view: AdvancedTextView?, indicatorWordIndex: Int) {
|
|
127
|
+
android.util.Log.d(NAME, "setIndicatorWordIndex called with: $indicatorWordIndex")
|
|
128
|
+
view?.setIndicatorWordIndex(indicatorWordIndex)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@ReactProp(name = "indicatorColor")
|
|
132
|
+
fun setIndicatorColor(view: AdvancedTextView?, indicatorColor: String?) {
|
|
133
|
+
android.util.Log.d(NAME, "setIndicatorColor called with: $indicatorColor")
|
|
134
|
+
if (indicatorColor != null) {
|
|
135
|
+
view?.setIndicatorColor(indicatorColor)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
127
139
|
@ReactProp(name = "lineHeight")
|
|
128
140
|
fun setLineHeight(view: AdvancedTextView?, lineHeight: Float) {
|
|
129
141
|
android.util.Log.d(NAME, "setLineHeight called with: $lineHeight")
|
package/ios/AdvancedTextView.mm
CHANGED
|
@@ -15,17 +15,19 @@ using namespace facebook::react;
|
|
|
15
15
|
@interface AdvancedTextView () <RCTAdvancedTextViewViewProtocol, UIGestureRecognizerDelegate, UITextViewDelegate>
|
|
16
16
|
|
|
17
17
|
@property (nonatomic, strong) NSMutableArray<NSDictionary *> *wordRanges;
|
|
18
|
-
@property (nonatomic, strong) NSMutableDictionary<NSNumber *,
|
|
18
|
+
@property (nonatomic, strong) NSMutableDictionary<NSNumber *, NSDictionary *> *highlightStyles;
|
|
19
19
|
@property (nonatomic, strong) NSArray<NSString *> *menuOptions;
|
|
20
|
-
@property (nonatomic, assign) NSInteger indicatorWordIndex;
|
|
21
20
|
@property (nonatomic, assign) CGFloat fontSize;
|
|
22
21
|
@property (nonatomic, strong) NSString *fontWeight;
|
|
23
22
|
@property (nonatomic, strong) UIColor *textColor;
|
|
24
23
|
@property (nonatomic, strong) NSString *textAlign;
|
|
25
24
|
@property (nonatomic, strong) NSString *fontFamily;
|
|
26
25
|
@property (nonatomic, assign) CGFloat lineHeight;
|
|
26
|
+
@property (nonatomic, assign) NSInteger indicatorWordIndex;
|
|
27
|
+
@property (nonatomic, strong) UIColor *indicatorColor;
|
|
27
28
|
|
|
28
29
|
- (void)handleCustomMenuAction:(UIMenuItem *)sender;
|
|
30
|
+
- (void)drawHighlightedBackgroundsInTextView:(UITextView *)textView;
|
|
29
31
|
|
|
30
32
|
@end
|
|
31
33
|
|
|
@@ -56,6 +58,12 @@ using namespace facebook::react;
|
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
- (void)drawRect:(CGRect)rect
|
|
62
|
+
{
|
|
63
|
+
[self.parentView drawHighlightedBackgroundsInTextView:self];
|
|
64
|
+
[super drawRect:rect];
|
|
65
|
+
}
|
|
66
|
+
|
|
59
67
|
@end
|
|
60
68
|
|
|
61
69
|
|
|
@@ -83,14 +91,15 @@ using namespace facebook::react;
|
|
|
83
91
|
_props = defaultProps;
|
|
84
92
|
|
|
85
93
|
_wordRanges = [NSMutableArray array];
|
|
86
|
-
|
|
87
|
-
_indicatorWordIndex = -1;
|
|
94
|
+
_highlightStyles = [NSMutableDictionary dictionary];
|
|
88
95
|
_fontSize = 16.0;
|
|
89
96
|
_fontWeight = @"normal";
|
|
90
97
|
_textColor = [UIColor labelColor];
|
|
91
98
|
_textAlign = @"left";
|
|
92
99
|
_fontFamily = @"System";
|
|
93
100
|
_lineHeight = 0.0;
|
|
101
|
+
_indicatorWordIndex = -1;
|
|
102
|
+
_indicatorColor = nil;
|
|
94
103
|
|
|
95
104
|
[self setupTextView];
|
|
96
105
|
[self setupGestureRecognizers];
|
|
@@ -155,7 +164,6 @@ using namespace facebook::react;
|
|
|
155
164
|
BOOL textChanged = NO;
|
|
156
165
|
BOOL highlightsChanged = NO;
|
|
157
166
|
BOOL menuChanged = NO;
|
|
158
|
-
BOOL indicatorChanged = NO;
|
|
159
167
|
BOOL styleChanged = NO;
|
|
160
168
|
|
|
161
169
|
if (oldViewProps.fontSize != newViewProps.fontSize && newViewProps.fontSize) {
|
|
@@ -200,7 +208,9 @@ using namespace facebook::react;
|
|
|
200
208
|
for (size_t i = 0; i < oldViewProps.highlightedWords.size(); i++) {
|
|
201
209
|
const auto &oldHW = oldViewProps.highlightedWords[i];
|
|
202
210
|
const auto &newHW = newViewProps.highlightedWords[i];
|
|
203
|
-
if (oldHW.index != newHW.index ||
|
|
211
|
+
if (oldHW.index != newHW.index ||
|
|
212
|
+
oldHW.highlightColor != newHW.highlightColor ||
|
|
213
|
+
oldHW.borderRadius != newHW.borderRadius) {
|
|
204
214
|
highlightsChanged = YES;
|
|
205
215
|
break;
|
|
206
216
|
}
|
|
@@ -218,16 +228,29 @@ using namespace facebook::react;
|
|
|
218
228
|
}
|
|
219
229
|
}
|
|
220
230
|
|
|
221
|
-
if (oldViewProps.indicatorWordIndex != newViewProps.indicatorWordIndex) {
|
|
222
|
-
indicatorChanged = YES;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
231
|
if (oldViewProps.lineHeight != newViewProps.lineHeight) {
|
|
226
232
|
NSLog(@"[AdvancedTextView] Updating lineHeight to: %f", newViewProps.lineHeight);
|
|
227
233
|
_lineHeight = static_cast<CGFloat>(newViewProps.lineHeight);
|
|
228
234
|
styleChanged = YES;
|
|
229
235
|
}
|
|
230
236
|
|
|
237
|
+
if (oldViewProps.indicatorWordIndex != newViewProps.indicatorWordIndex) {
|
|
238
|
+
NSLog(@"[AdvancedTextView] Updating indicatorWordIndex to: %d", newViewProps.indicatorWordIndex);
|
|
239
|
+
_indicatorWordIndex = static_cast<NSInteger>(newViewProps.indicatorWordIndex);
|
|
240
|
+
[_textView setNeedsDisplay];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (oldViewProps.indicatorColor != newViewProps.indicatorColor) {
|
|
244
|
+
NSLog(@"[AdvancedTextView] Updating indicatorColor");
|
|
245
|
+
if (!newViewProps.indicatorColor.empty()) {
|
|
246
|
+
NSString *colorStr = [NSString stringWithUTF8String:newViewProps.indicatorColor.c_str()];
|
|
247
|
+
_indicatorColor = [self hexStringToColor:colorStr];
|
|
248
|
+
} else {
|
|
249
|
+
_indicatorColor = nil;
|
|
250
|
+
}
|
|
251
|
+
[_textView setNeedsDisplay];
|
|
252
|
+
}
|
|
253
|
+
|
|
231
254
|
if (textChanged) {
|
|
232
255
|
NSString *text = [NSString stringWithUTF8String:newViewProps.text.c_str()];
|
|
233
256
|
[self updateTextContent:text];
|
|
@@ -241,11 +264,6 @@ using namespace facebook::react;
|
|
|
241
264
|
[self updateMenuOptions:newViewProps.menuOptions];
|
|
242
265
|
}
|
|
243
266
|
|
|
244
|
-
if (indicatorChanged) {
|
|
245
|
-
_indicatorWordIndex = newViewProps.indicatorWordIndex;
|
|
246
|
-
[self updateTextAppearance];
|
|
247
|
-
}
|
|
248
|
-
|
|
249
267
|
if (styleChanged) {
|
|
250
268
|
NSLog(@"[AdvancedTextView] Style properties changed, updating appearance");
|
|
251
269
|
[self updateTextAppearance];
|
|
@@ -339,15 +357,19 @@ using namespace facebook::react;
|
|
|
339
357
|
NSLog(@"[AdvancedTextView] updateHighlightedWords called with %zu highlights",
|
|
340
358
|
highlightedWords.size());
|
|
341
359
|
@try {
|
|
342
|
-
[
|
|
360
|
+
[_highlightStyles removeAllObjects];
|
|
343
361
|
|
|
344
362
|
for (const auto &hw : highlightedWords) {
|
|
345
363
|
NSInteger index = hw.index;
|
|
346
364
|
NSString *colorString = [NSString stringWithUTF8String:hw.highlightColor.c_str()];
|
|
347
365
|
UIColor *color = [self hexStringToColor:colorString];
|
|
366
|
+
CGFloat borderRadius = static_cast<CGFloat>(hw.borderRadius);
|
|
348
367
|
|
|
349
368
|
if (color) {
|
|
350
|
-
|
|
369
|
+
_highlightStyles[@(index)] = @{
|
|
370
|
+
@"color": color,
|
|
371
|
+
@"borderRadius": @(MAX(borderRadius, 0.0))
|
|
372
|
+
};
|
|
351
373
|
}
|
|
352
374
|
}
|
|
353
375
|
|
|
@@ -430,37 +452,8 @@ using namespace facebook::react;
|
|
|
430
452
|
value:color
|
|
431
453
|
range:NSMakeRange(0, attributedString.length)];
|
|
432
454
|
|
|
433
|
-
|
|
434
|
-
for (NSDictionary *wordInfo in _wordRanges) {
|
|
435
|
-
NSNumber *index = wordInfo[@"index"];
|
|
436
|
-
NSValue *rangeValue = wordInfo[@"range"];
|
|
437
|
-
NSRange range = [rangeValue rangeValue];
|
|
438
|
-
|
|
439
|
-
if (range.location + range.length > attributedString.length) {
|
|
440
|
-
continue;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
UIColor *highlightColor = _highlightColors[index];
|
|
444
|
-
if (highlightColor) {
|
|
445
|
-
NSValue *extendedRangeValue = wordInfo[@"extendedRange"];
|
|
446
|
-
NSRange extendedRange = extendedRangeValue ? [extendedRangeValue rangeValue] : range;
|
|
447
|
-
|
|
448
|
-
if (extendedRange.location + extendedRange.length <= attributedString.length) {
|
|
449
|
-
[attributedString addAttribute:NSBackgroundColorAttributeName
|
|
450
|
-
value:highlightColor
|
|
451
|
-
range:extendedRange];
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
if (_indicatorWordIndex >= 0 && [index integerValue] == _indicatorWordIndex) {
|
|
456
|
-
UIColor *indicatorColor = [[UIColor systemBlueColor] colorWithAlphaComponent:0.3];
|
|
457
|
-
[attributedString addAttribute:NSBackgroundColorAttributeName
|
|
458
|
-
value:indicatorColor
|
|
459
|
-
range:range];
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
455
|
_textView.attributedText = attributedString;
|
|
456
|
+
[_textView setNeedsDisplay];
|
|
464
457
|
|
|
465
458
|
if (_textAlign) {
|
|
466
459
|
if ([_textAlign.lowercaseString isEqualToString:@"center"]) {
|
|
@@ -479,6 +472,81 @@ using namespace facebook::react;
|
|
|
479
472
|
}
|
|
480
473
|
}
|
|
481
474
|
|
|
475
|
+
- (void)drawHighlightedBackgroundsInTextView:(UITextView *)textView
|
|
476
|
+
{
|
|
477
|
+
if (!textView.layoutManager || !textView.textContainer) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
NSLayoutManager *layoutManager = textView.layoutManager;
|
|
482
|
+
NSTextContainer *textContainer = textView.textContainer;
|
|
483
|
+
|
|
484
|
+
for (NSDictionary *wordInfo in _wordRanges) {
|
|
485
|
+
NSNumber *index = wordInfo[@"index"];
|
|
486
|
+
NSDictionary *highlightStyle = _highlightStyles[index];
|
|
487
|
+
if (!highlightStyle) {
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
NSValue *extendedRangeValue = wordInfo[@"extendedRange"];
|
|
492
|
+
NSRange characterRange = extendedRangeValue ? [extendedRangeValue rangeValue] : [wordInfo[@"range"] rangeValue];
|
|
493
|
+
if (characterRange.location == NSNotFound || characterRange.length == 0) {
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
UIColor *highlightColor = highlightStyle[@"color"];
|
|
498
|
+
CGFloat radius = [highlightStyle[@"borderRadius"] doubleValue];
|
|
499
|
+
NSRange glyphRange = [layoutManager glyphRangeForCharacterRange:characterRange actualCharacterRange:nil];
|
|
500
|
+
|
|
501
|
+
[highlightColor setFill];
|
|
502
|
+
[layoutManager enumerateEnclosingRectsForGlyphRange:glyphRange
|
|
503
|
+
withinSelectedGlyphRange:NSMakeRange(NSNotFound, 0)
|
|
504
|
+
inTextContainer:textContainer
|
|
505
|
+
usingBlock:^(CGRect enclosingRect, BOOL *stop) {
|
|
506
|
+
CGRect adjustedRect = CGRectInset(enclosingRect, -6.0, -2.0);
|
|
507
|
+
adjustedRect.origin.x += textView.textContainerInset.left;
|
|
508
|
+
adjustedRect.origin.y += textView.textContainerInset.top;
|
|
509
|
+
|
|
510
|
+
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:adjustedRect
|
|
511
|
+
cornerRadius:MAX(radius, 0.0)];
|
|
512
|
+
[path fill];
|
|
513
|
+
}];
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Draw indicator for indicatorWordIndex
|
|
517
|
+
if (_indicatorWordIndex >= 0 && _indicatorColor) {
|
|
518
|
+
for (NSDictionary *wordInfo in _wordRanges) {
|
|
519
|
+
NSNumber *index = wordInfo[@"index"];
|
|
520
|
+
if ([index integerValue] != _indicatorWordIndex) {
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
NSValue *extendedRangeValue = wordInfo[@"extendedRange"];
|
|
525
|
+
NSRange characterRange = extendedRangeValue ? [extendedRangeValue rangeValue] : [wordInfo[@"range"] rangeValue];
|
|
526
|
+
if (characterRange.location == NSNotFound || characterRange.length == 0) {
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
NSRange glyphRange = [layoutManager glyphRangeForCharacterRange:characterRange actualCharacterRange:nil];
|
|
531
|
+
|
|
532
|
+
[_indicatorColor setFill];
|
|
533
|
+
[layoutManager enumerateEnclosingRectsForGlyphRange:glyphRange
|
|
534
|
+
withinSelectedGlyphRange:NSMakeRange(NSNotFound, 0)
|
|
535
|
+
inTextContainer:textContainer
|
|
536
|
+
usingBlock:^(CGRect enclosingRect, BOOL *stop) {
|
|
537
|
+
CGRect adjustedRect = CGRectInset(enclosingRect, -6.0, -2.0);
|
|
538
|
+
adjustedRect.origin.x += textView.textContainerInset.left;
|
|
539
|
+
adjustedRect.origin.y += textView.textContainerInset.top;
|
|
540
|
+
|
|
541
|
+
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:adjustedRect
|
|
542
|
+
cornerRadius:0.0];
|
|
543
|
+
[path fill];
|
|
544
|
+
}];
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
482
550
|
|
|
483
551
|
- (void)handleTap:(UITapGestureRecognizer *)gesture
|
|
484
552
|
{
|
|
@@ -10,7 +10,6 @@ export const AdvancedText = ({
|
|
|
10
10
|
menuOptions,
|
|
11
11
|
onWordPress,
|
|
12
12
|
onSelection,
|
|
13
|
-
indicatorWordIndex,
|
|
14
13
|
...restProps
|
|
15
14
|
}) => {
|
|
16
15
|
return /*#__PURE__*/_jsx(AdvancedTextViewNativeComponent, {
|
|
@@ -20,8 +19,7 @@ export const AdvancedText = ({
|
|
|
20
19
|
highlightedWords: highlightedWords,
|
|
21
20
|
menuOptions: menuOptions,
|
|
22
21
|
onWordPress: onWordPress,
|
|
23
|
-
onSelection: onSelection
|
|
24
|
-
indicatorWordIndex: indicatorWordIndex
|
|
22
|
+
onSelection: onSelection
|
|
25
23
|
});
|
|
26
24
|
};
|
|
27
25
|
//# sourceMappingURL=AdvancedText.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["React","AdvancedTextViewNativeComponent","jsx","_jsx","AdvancedText","text","style","highlightedWords","menuOptions","onWordPress","onSelection","
|
|
1
|
+
{"version":3,"names":["React","AdvancedTextViewNativeComponent","jsx","_jsx","AdvancedText","text","style","highlightedWords","menuOptions","onWordPress","onSelection","restProps"],"sourceRoot":"../../src","sources":["AdvancedText.tsx"],"mappings":";;AAAA,OAAOA,KAAK,MAAM,OAAO;AACzB,OAAOC,+BAA+B,MAE/B,mCAAmC;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAE3C,OAAO,MAAMC,YAAmC,GAAGA,CAAC;EAClDC,IAAI;EACJC,KAAK;EACLC,gBAAgB;EAChBC,WAAW;EACXC,WAAW;EACXC,WAAW;EACX,GAAGC;AACL,CAAC,KAAK;EACJ,oBACER,IAAA,CAACF,+BAA+B;IAAA,GAC1BU,SAAS;IACbL,KAAK,EAAEA,KAAM;IACbD,IAAI,EAAEA,IAAK;IACXE,gBAAgB,EAAEA,gBAAiB;IACnCC,WAAW,EAAEA,WAAY;IACzBC,WAAW,EAAEA,WAAY;IACzBC,WAAW,EAAEA;EAAY,CAC1B,CAAC;AAEN,CAAC","ignoreList":[]}
|
|
@@ -7,6 +7,7 @@ import type { DirectEventHandler, Int32 , Float } from 'react-native/Libraries/
|
|
|
7
7
|
interface HighlightedWord {
|
|
8
8
|
index: Int32;
|
|
9
9
|
highlightColor: string;
|
|
10
|
+
borderRadius?: Float;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export interface NativeProps extends ViewProps {
|
|
@@ -15,13 +16,14 @@ export interface NativeProps extends ViewProps {
|
|
|
15
16
|
menuOptions?: ReadonlyArray<string>;
|
|
16
17
|
onWordPress?: DirectEventHandler<{ word: string; index: Int32 }>;
|
|
17
18
|
onSelection?: DirectEventHandler<{ selectedText: string; event: string }>;
|
|
18
|
-
indicatorWordIndex?: Int32;
|
|
19
19
|
fontSize?: Int32;
|
|
20
20
|
fontWeight?: string;
|
|
21
21
|
color?: string;
|
|
22
22
|
textAlign?: string;
|
|
23
23
|
fontFamily?: string;
|
|
24
24
|
lineHeight?: Float;
|
|
25
|
+
indicatorWordIndex?: Int32;
|
|
26
|
+
indicatorColor?: string;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
export default codegenNativeComponent<NativeProps>('AdvancedTextView');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdvancedText.d.ts","sourceRoot":"","sources":["../../../src/AdvancedText.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAwC,EACtC,KAAK,WAAW,EACjB,MAAM,mCAAmC,CAAC;AAE3C,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,
|
|
1
|
+
{"version":3,"file":"AdvancedText.d.ts","sourceRoot":"","sources":["../../../src/AdvancedText.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAwC,EACtC,KAAK,WAAW,EACjB,MAAM,mCAAmC,CAAC;AAE3C,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,CAoB9C,CAAC"}
|
|
@@ -3,6 +3,7 @@ import type { DirectEventHandler, Int32, Float } from 'react-native/Libraries/Ty
|
|
|
3
3
|
interface HighlightedWord {
|
|
4
4
|
index: Int32;
|
|
5
5
|
highlightColor: string;
|
|
6
|
+
borderRadius?: Float;
|
|
6
7
|
}
|
|
7
8
|
export interface NativeProps extends ViewProps {
|
|
8
9
|
text: string;
|
|
@@ -16,13 +17,14 @@ export interface NativeProps extends ViewProps {
|
|
|
16
17
|
selectedText: string;
|
|
17
18
|
event: string;
|
|
18
19
|
}>;
|
|
19
|
-
indicatorWordIndex?: Int32;
|
|
20
20
|
fontSize?: Int32;
|
|
21
21
|
fontWeight?: string;
|
|
22
22
|
color?: string;
|
|
23
23
|
textAlign?: string;
|
|
24
24
|
fontFamily?: string;
|
|
25
25
|
lineHeight?: Float;
|
|
26
|
+
indicatorWordIndex?: Int32;
|
|
27
|
+
indicatorColor?: string;
|
|
26
28
|
}
|
|
27
29
|
declare const _default: import("react-native/types_generated/Libraries/Utilities/codegenNativeComponent").NativeComponentType<NativeProps>;
|
|
28
30
|
export default _default;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdvancedTextViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../src/AdvancedTextViewNativeComponent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAG9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAG,KAAK,EAAG,MAAM,2CAA2C,CAAC;AAEpG,UAAU,eAAe;IACvB,KAAK,EAAE,KAAK,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"AdvancedTextViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../src/AdvancedTextViewNativeComponent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAG9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAG,KAAK,EAAG,MAAM,2CAA2C,CAAC;AAEpG,UAAU,eAAe;IACvB,KAAK,EAAE,KAAK,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,KAAK,CAAC;CACtB;AAED,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAClD,WAAW,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACpC,WAAW,CAAC,EAAE,kBAAkB,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,CAAC,CAAC;IACjE,WAAW,CAAC,EAAE,kBAAkB,CAAC;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1E,QAAQ,CAAC,EAAE,KAAK,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,KAAK,CAAC;IACnB,kBAAkB,CAAC,EAAE,KAAK,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;;AAED,wBAAuE"}
|
package/package.json
CHANGED
package/src/AdvancedText.tsx
CHANGED
|
@@ -10,7 +10,6 @@ export const AdvancedText: React.FC<NativeProps> = ({
|
|
|
10
10
|
menuOptions,
|
|
11
11
|
onWordPress,
|
|
12
12
|
onSelection,
|
|
13
|
-
indicatorWordIndex,
|
|
14
13
|
...restProps
|
|
15
14
|
}) => {
|
|
16
15
|
return (
|
|
@@ -22,7 +21,6 @@ export const AdvancedText: React.FC<NativeProps> = ({
|
|
|
22
21
|
menuOptions={menuOptions}
|
|
23
22
|
onWordPress={onWordPress}
|
|
24
23
|
onSelection={onSelection}
|
|
25
|
-
indicatorWordIndex={indicatorWordIndex}
|
|
26
24
|
/>
|
|
27
25
|
);
|
|
28
26
|
};
|
|
@@ -7,6 +7,7 @@ import type { DirectEventHandler, Int32 , Float } from 'react-native/Libraries/
|
|
|
7
7
|
interface HighlightedWord {
|
|
8
8
|
index: Int32;
|
|
9
9
|
highlightColor: string;
|
|
10
|
+
borderRadius?: Float;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export interface NativeProps extends ViewProps {
|
|
@@ -15,13 +16,14 @@ export interface NativeProps extends ViewProps {
|
|
|
15
16
|
menuOptions?: ReadonlyArray<string>;
|
|
16
17
|
onWordPress?: DirectEventHandler<{ word: string; index: Int32 }>;
|
|
17
18
|
onSelection?: DirectEventHandler<{ selectedText: string; event: string }>;
|
|
18
|
-
indicatorWordIndex?: Int32;
|
|
19
19
|
fontSize?: Int32;
|
|
20
20
|
fontWeight?: string;
|
|
21
21
|
color?: string;
|
|
22
22
|
textAlign?: string;
|
|
23
23
|
fontFamily?: string;
|
|
24
24
|
lineHeight?: Float;
|
|
25
|
+
indicatorWordIndex?: Int32;
|
|
26
|
+
indicatorColor?: string;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
export default codegenNativeComponent<NativeProps>('AdvancedTextView');
|