react-native-highlight-text-view 0.1.13 → 0.1.15
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.
|
@@ -17,19 +17,21 @@ import android.view.Gravity
|
|
|
17
17
|
import androidx.appcompat.widget.AppCompatEditText
|
|
18
18
|
|
|
19
19
|
class RoundedBackgroundSpan(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
20
|
+
internal val backgroundColor: Int,
|
|
21
|
+
internal val textColor: Int,
|
|
22
|
+
internal val paddingLeft: Float,
|
|
23
|
+
internal val paddingRight: Float,
|
|
24
|
+
internal val paddingTop: Float,
|
|
25
|
+
internal val paddingBottom: Float,
|
|
26
|
+
internal val backgroundInsetTop: Float,
|
|
27
|
+
internal val backgroundInsetBottom: Float,
|
|
28
|
+
internal val backgroundInsetLeft: Float,
|
|
29
|
+
internal val backgroundInsetRight: Float,
|
|
30
|
+
internal val cornerRadius: Float,
|
|
31
|
+
internal val isFirstInGroup: Boolean = false,
|
|
32
|
+
internal val isLastInGroup: Boolean = false,
|
|
33
|
+
private val isStartOfLine: Boolean = false,
|
|
34
|
+
private val isEndOfLine: Boolean = false
|
|
33
35
|
) : ReplacementSpan() {
|
|
34
36
|
|
|
35
37
|
override fun getSize(
|
|
@@ -40,9 +42,9 @@ class RoundedBackgroundSpan(
|
|
|
40
42
|
fm: Paint.FontMetricsInt?
|
|
41
43
|
): Int {
|
|
42
44
|
val width = paint.measureText(text, start, end)
|
|
43
|
-
//
|
|
44
|
-
val leftPad = if (isFirstInGroup) paddingLeft else 0f
|
|
45
|
-
val rightPad = if (isLastInGroup) paddingRight else 0f
|
|
45
|
+
// Add padding for word boundaries AND line boundaries (for consistent alignment)
|
|
46
|
+
val leftPad = if (isFirstInGroup || isStartOfLine) paddingLeft else 0f
|
|
47
|
+
val rightPad = if (isLastInGroup || isEndOfLine) paddingRight else 0f
|
|
46
48
|
return (width + leftPad + rightPad).toInt()
|
|
47
49
|
}
|
|
48
50
|
|
|
@@ -71,35 +73,37 @@ class RoundedBackgroundSpan(
|
|
|
71
73
|
val textHeight = fontMetrics.descent - fontMetrics.ascent
|
|
72
74
|
val textTop = y + fontMetrics.ascent
|
|
73
75
|
|
|
74
|
-
//
|
|
75
|
-
val
|
|
76
|
-
val
|
|
76
|
+
// Add padding for word AND line boundaries (consistent alignment)
|
|
77
|
+
val isReallyFirst = isFirstInGroup || isStartOfLine
|
|
78
|
+
val isReallyLast = isLastInGroup || isEndOfLine
|
|
79
|
+
val leftPad = if (isReallyFirst) paddingLeft else 0f
|
|
80
|
+
val rightPad = if (isReallyLast) paddingRight else 0f
|
|
77
81
|
|
|
78
|
-
//
|
|
82
|
+
// Small overlap to eliminate gaps between characters
|
|
79
83
|
val overlapExtension = 2f
|
|
80
|
-
val
|
|
81
|
-
val
|
|
84
|
+
val leftOverlap = if (!isReallyFirst) overlapExtension else 0f
|
|
85
|
+
val rightOverlap = if (!isReallyLast) overlapExtension else 0f
|
|
82
86
|
|
|
83
87
|
// Apply background insets first (shrinks from line box)
|
|
84
88
|
val insetTop = textTop + backgroundInsetTop
|
|
85
89
|
val insetHeight = textHeight - (backgroundInsetTop + backgroundInsetBottom)
|
|
86
90
|
|
|
87
|
-
// Calculate
|
|
91
|
+
// Calculate background rect
|
|
88
92
|
val rect = RectF(
|
|
89
|
-
x -
|
|
93
|
+
x - leftOverlap + backgroundInsetLeft,
|
|
90
94
|
insetTop - paddingTop,
|
|
91
|
-
x + width + leftPad + rightPad +
|
|
95
|
+
x + width + leftPad + rightPad + rightOverlap - backgroundInsetRight,
|
|
92
96
|
insetTop + insetHeight + paddingBottom
|
|
93
97
|
)
|
|
94
98
|
|
|
95
|
-
// Draw background with selective corner rounding
|
|
99
|
+
// Draw background with selective corner rounding (respects line wraps)
|
|
96
100
|
when {
|
|
97
|
-
|
|
101
|
+
isReallyFirst && isReallyLast -> {
|
|
98
102
|
// Single character or isolated group - round all corners
|
|
99
103
|
canvas.drawRoundRect(rect, cornerRadius, cornerRadius, bgPaint)
|
|
100
104
|
}
|
|
101
|
-
|
|
102
|
-
// First character - round left corners only
|
|
105
|
+
isReallyFirst -> {
|
|
106
|
+
// First character (word start or line start) - round left corners only
|
|
103
107
|
val path = android.graphics.Path()
|
|
104
108
|
path.addRoundRect(
|
|
105
109
|
rect,
|
|
@@ -113,8 +117,8 @@ class RoundedBackgroundSpan(
|
|
|
113
117
|
)
|
|
114
118
|
canvas.drawPath(path, bgPaint)
|
|
115
119
|
}
|
|
116
|
-
|
|
117
|
-
// Last character - round right corners only
|
|
120
|
+
isReallyLast -> {
|
|
121
|
+
// Last character (word end or line end) - round right corners only
|
|
118
122
|
val path = android.graphics.Path()
|
|
119
123
|
path.addRoundRect(
|
|
120
124
|
rect,
|
|
@@ -418,6 +422,61 @@ class HighlightTextView : AppCompatEditText {
|
|
|
418
422
|
setText(spannable)
|
|
419
423
|
setSelection(text.length) // Keep cursor at end
|
|
420
424
|
isUpdatingText = false
|
|
425
|
+
|
|
426
|
+
// Detect line wraps after layout is ready
|
|
427
|
+
post { detectLineWraps() }
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
private fun detectLineWraps() {
|
|
431
|
+
val layout = layout ?: return
|
|
432
|
+
val text = text as? Spannable ?: return
|
|
433
|
+
val textStr = text.toString()
|
|
434
|
+
val spans = text.getSpans(0, text.length, RoundedBackgroundSpan::class.java)
|
|
435
|
+
|
|
436
|
+
for (span in spans) {
|
|
437
|
+
val spanStart = text.getSpanStart(span)
|
|
438
|
+
val spanEnd = text.getSpanEnd(span)
|
|
439
|
+
|
|
440
|
+
if (spanStart >= 0 && spanStart < text.length) {
|
|
441
|
+
val line = layout.getLineForOffset(spanStart)
|
|
442
|
+
val lineStart = layout.getLineStart(line)
|
|
443
|
+
val lineEnd = layout.getLineEnd(line)
|
|
444
|
+
|
|
445
|
+
// Check for manual line break (\n) before this character
|
|
446
|
+
val hasNewlineBefore = spanStart > 0 && textStr[spanStart - 1] == '\n'
|
|
447
|
+
// Check for manual line break (\n) after this character
|
|
448
|
+
val hasNewlineAfter = spanEnd < textStr.length && textStr[spanEnd] == '\n'
|
|
449
|
+
|
|
450
|
+
// Check if this char is at start of visual line (wrapped OR after \n)
|
|
451
|
+
val isAtLineStart = (spanStart == lineStart && !span.isFirstInGroup) || hasNewlineBefore
|
|
452
|
+
// Check if this char is at end of visual line (wrapped OR before \n)
|
|
453
|
+
val isAtLineEnd = (spanEnd == lineEnd && !span.isLastInGroup) || hasNewlineAfter
|
|
454
|
+
|
|
455
|
+
if (isAtLineStart || isAtLineEnd) {
|
|
456
|
+
// Create new span with line boundary flags
|
|
457
|
+
val newSpan = RoundedBackgroundSpan(
|
|
458
|
+
span.backgroundColor,
|
|
459
|
+
span.textColor,
|
|
460
|
+
span.paddingLeft,
|
|
461
|
+
span.paddingRight,
|
|
462
|
+
span.paddingTop,
|
|
463
|
+
span.paddingBottom,
|
|
464
|
+
span.backgroundInsetTop,
|
|
465
|
+
span.backgroundInsetBottom,
|
|
466
|
+
span.backgroundInsetLeft,
|
|
467
|
+
span.backgroundInsetRight,
|
|
468
|
+
span.cornerRadius,
|
|
469
|
+
span.isFirstInGroup,
|
|
470
|
+
span.isLastInGroup,
|
|
471
|
+
isAtLineStart,
|
|
472
|
+
isAtLineEnd
|
|
473
|
+
)
|
|
474
|
+
text.removeSpan(span)
|
|
475
|
+
text.setSpan(newSpan, spanStart, spanEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
invalidate()
|
|
421
480
|
}
|
|
422
481
|
|
|
423
482
|
private fun shouldHighlightChar(text: String, index: Int): Boolean {
|
|
@@ -88,10 +88,12 @@ class HighlightTextViewManager : SimpleViewManager<HighlightTextView>(),
|
|
|
88
88
|
horizontalAlign = null
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
// Determine vertical gravity
|
|
91
|
+
// Determine vertical gravity - preserve existing if not specified
|
|
92
92
|
val vGravity = when (verticalAlign) {
|
|
93
93
|
"top" -> Gravity.TOP
|
|
94
94
|
"bottom" -> Gravity.BOTTOM
|
|
95
|
+
"center" -> Gravity.CENTER_VERTICAL
|
|
96
|
+
null -> view?.gravity?.and(Gravity.VERTICAL_GRAVITY_MASK) ?: Gravity.CENTER_VERTICAL
|
|
95
97
|
else -> Gravity.CENTER_VERTICAL
|
|
96
98
|
}
|
|
97
99
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-highlight-text-view",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "A native text input for React Native that supports inline text highlighting",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|