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
- private val backgroundColor: Int,
21
- private val textColor: Int,
22
- private val paddingLeft: Float,
23
- private val paddingRight: Float,
24
- private val paddingTop: Float,
25
- private val paddingBottom: Float,
26
- private val backgroundInsetTop: Float,
27
- private val backgroundInsetBottom: Float,
28
- private val backgroundInsetLeft: Float,
29
- private val backgroundInsetRight: Float,
30
- private val cornerRadius: Float,
31
- private val isFirstInGroup: Boolean = false,
32
- private val isLastInGroup: Boolean = false
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
- // Only add padding for first and last characters in a group
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
- // Only add padding for first and last characters in a group
75
- val leftPad = if (isFirstInGroup) paddingLeft else 0f
76
- val rightPad = if (isLastInGroup) paddingRight else 0f
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
- // Extend background to overlap and eliminate gaps between characters
82
+ // Small overlap to eliminate gaps between characters
79
83
  val overlapExtension = 2f
80
- val leftExtension = if (!isFirstInGroup) overlapExtension else 0f
81
- val rightExtension = if (!isLastInGroup) overlapExtension else 0f
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 proper bounds with insets then padding
91
+ // Calculate background rect
88
92
  val rect = RectF(
89
- x - leftExtension + backgroundInsetLeft,
93
+ x - leftOverlap + backgroundInsetLeft,
90
94
  insetTop - paddingTop,
91
- x + width + leftPad + rightPad + rightExtension - backgroundInsetRight,
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
- isFirstInGroup && isLastInGroup -> {
101
+ isReallyFirst && isReallyLast -> {
98
102
  // Single character or isolated group - round all corners
99
103
  canvas.drawRoundRect(rect, cornerRadius, cornerRadius, bgPaint)
100
104
  }
101
- isFirstInGroup -> {
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
- isLastInGroup -> {
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.13",
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",