react-native-highlight-text-view 0.1.3 → 0.1.5

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 CHANGED
@@ -61,7 +61,7 @@ export default function App() {
61
61
 
62
62
 
63
63
  **Note:** Vertical alignment is currently supported on iOS only. On Android, text will use default vertical positioning.
64
- ```
64
+
65
65
 
66
66
 
67
67
  ## Contributing
@@ -70,10 +70,7 @@ export default function App() {
70
70
  - [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
71
71
  - [Code of conduct](CODE_OF_CONDUCT.md)
72
72
 
73
- ## License
74
-
75
- MIT
76
73
 
77
- ---
74
+ ## License MIT
78
75
 
79
76
  Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
@@ -1,15 +1,299 @@
1
1
  package com.highlighttext
2
2
 
3
3
  import android.content.Context
4
+ import android.graphics.Canvas
5
+ import android.graphics.Color
6
+ import android.graphics.Paint
7
+ import android.graphics.RectF
8
+ import android.text.Editable
9
+ import android.text.Spannable
10
+ import android.text.SpannableString
11
+ import android.text.TextWatcher
12
+ import android.text.style.ReplacementSpan
4
13
  import android.util.AttributeSet
5
- import android.view.View
14
+ import android.util.TypedValue
15
+ import android.view.Gravity
16
+ import androidx.appcompat.widget.AppCompatEditText
6
17
 
7
- class HighlightTextView : View {
8
- constructor(context: Context?) : super(context)
9
- constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
18
+ class RoundedBackgroundSpan(
19
+ private val backgroundColor: Int,
20
+ private val textColor: Int,
21
+ private val paddingLeft: Float,
22
+ private val paddingRight: Float,
23
+ private val paddingTop: Float,
24
+ private val paddingBottom: Float,
25
+ private val cornerRadius: Float,
26
+ private val isFirstInGroup: Boolean = false,
27
+ private val isLastInGroup: Boolean = false
28
+ ) : ReplacementSpan() {
29
+
30
+ override fun getSize(
31
+ paint: Paint,
32
+ text: CharSequence?,
33
+ start: Int,
34
+ end: Int,
35
+ fm: Paint.FontMetricsInt?
36
+ ): Int {
37
+ val width = paint.measureText(text, start, end)
38
+ // Only add padding for first and last characters in a group
39
+ val leftPad = if (isFirstInGroup) paddingLeft else 0f
40
+ val rightPad = if (isLastInGroup) paddingRight else 0f
41
+ return (width + leftPad + rightPad).toInt()
42
+ }
43
+
44
+ override fun draw(
45
+ canvas: Canvas,
46
+ text: CharSequence?,
47
+ start: Int,
48
+ end: Int,
49
+ x: Float,
50
+ top: Int,
51
+ y: Int,
52
+ bottom: Int,
53
+ paint: Paint
54
+ ) {
55
+ // Draw background with padding
56
+ val bgPaint = Paint().apply {
57
+ color = backgroundColor
58
+ style = Paint.Style.FILL
59
+ isAntiAlias = true
60
+ }
61
+
62
+ val width = paint.measureText(text, start, end)
63
+
64
+ // Use font metrics for consistent height instead of character bounds
65
+ val fontMetrics = paint.fontMetrics
66
+ val textHeight = fontMetrics.descent - fontMetrics.ascent
67
+ val textTop = y + fontMetrics.ascent
68
+
69
+ // Only add padding for first and last characters in a group
70
+ val leftPad = if (isFirstInGroup) paddingLeft else 0f
71
+ val rightPad = if (isLastInGroup) paddingRight else 0f
72
+
73
+ // Extend background slightly to overlap and eliminate gaps
74
+ val overlapExtension = 1f
75
+ val leftExtension = if (!isFirstInGroup) overlapExtension else 0f
76
+ val rightExtension = if (!isLastInGroup) overlapExtension else 0f
77
+
78
+ // Calculate proper bounds with padding using consistent font metrics
79
+ val rect = RectF(
80
+ x - leftExtension,
81
+ textTop - paddingTop,
82
+ x + width + leftPad + rightPad + rightExtension,
83
+ textTop + textHeight + paddingBottom
84
+ )
85
+
86
+ // Draw background with selective corner rounding
87
+ when {
88
+ isFirstInGroup && isLastInGroup -> {
89
+ // Single character or isolated group - round all corners
90
+ canvas.drawRoundRect(rect, cornerRadius, cornerRadius, bgPaint)
91
+ }
92
+ isFirstInGroup -> {
93
+ // First character - round left corners only
94
+ val path = android.graphics.Path()
95
+ path.addRoundRect(
96
+ rect,
97
+ floatArrayOf(
98
+ cornerRadius, cornerRadius, // top-left
99
+ 0f, 0f, // top-right
100
+ 0f, 0f, // bottom-right
101
+ cornerRadius, cornerRadius // bottom-left
102
+ ),
103
+ android.graphics.Path.Direction.CW
104
+ )
105
+ canvas.drawPath(path, bgPaint)
106
+ }
107
+ isLastInGroup -> {
108
+ // Last character - round right corners only
109
+ val path = android.graphics.Path()
110
+ path.addRoundRect(
111
+ rect,
112
+ floatArrayOf(
113
+ 0f, 0f, // top-left
114
+ cornerRadius, cornerRadius, // top-right
115
+ cornerRadius, cornerRadius, // bottom-right
116
+ 0f, 0f // bottom-left
117
+ ),
118
+ android.graphics.Path.Direction.CW
119
+ )
120
+ canvas.drawPath(path, bgPaint)
121
+ }
122
+ else -> {
123
+ // Middle character - no rounded corners, just rectangle
124
+ canvas.drawRect(rect, bgPaint)
125
+ }
126
+ }
127
+
128
+ // Draw text with left padding offset only if first in group
129
+ val textPaint = Paint(paint).apply {
130
+ color = textColor
131
+ isAntiAlias = true
132
+ }
133
+ canvas.drawText(text!!, start, end, x + leftPad, y.toFloat(), textPaint)
134
+ }
135
+ }
136
+
137
+ class HighlightTextView : AppCompatEditText {
138
+ private var characterBackgroundColor: Int = Color.parseColor("#FFFF00")
139
+ private var textColorValue: Int = Color.BLACK
140
+ private var cornerRadius: Float = 4f
141
+ private var charPaddingLeft: Float = 8f
142
+ private var charPaddingRight: Float = 8f
143
+ private var charPaddingTop: Float = 4f
144
+ private var charPaddingBottom: Float = 4f
145
+ private var isUpdatingText: Boolean = false
146
+
147
+ var onTextChangeListener: ((String) -> Unit)? = null
148
+
149
+ constructor(context: Context?) : super(context!!) {
150
+ init()
151
+ }
152
+
153
+ constructor(context: Context?, attrs: AttributeSet?) : super(context!!, attrs) {
154
+ init()
155
+ }
156
+
10
157
  constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
11
- context,
158
+ context!!,
12
159
  attrs,
13
160
  defStyleAttr
14
- )
161
+ ) {
162
+ init()
163
+ }
164
+
165
+ private fun init() {
166
+ setBackgroundColor(Color.TRANSPARENT)
167
+ setTextSize(TypedValue.COMPLEX_UNIT_SP, 32f)
168
+ gravity = Gravity.CENTER
169
+ setPadding(20, 20, 20, 20)
170
+ textColorValue = currentTextColor
171
+
172
+ // Enable text wrapping
173
+ maxLines = Int.MAX_VALUE
174
+ isSingleLine = false
175
+ setHorizontallyScrolling(false)
176
+
177
+ addTextChangedListener(object : TextWatcher {
178
+ override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
179
+ override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
180
+ override fun afterTextChanged(s: Editable?) {
181
+ if (!isUpdatingText) {
182
+ onTextChangeListener?.invoke(s?.toString() ?: "")
183
+ applyCharacterBackgrounds()
184
+ }
185
+ }
186
+ })
187
+ }
188
+
189
+ fun setCharacterBackgroundColor(color: Int) {
190
+ characterBackgroundColor = color
191
+ applyCharacterBackgrounds()
192
+ }
193
+
194
+ override fun setTextColor(color: Int) {
195
+ super.setTextColor(color)
196
+ textColorValue = color
197
+ applyCharacterBackgrounds()
198
+ }
199
+
200
+ fun setCharPadding(left: Float, top: Float, right: Float, bottom: Float) {
201
+ charPaddingLeft = left
202
+ charPaddingTop = top
203
+ charPaddingRight = right
204
+ charPaddingBottom = bottom
205
+ applyCharacterBackgrounds()
206
+ }
207
+
208
+ fun setCharPaddingLeft(padding: Float) {
209
+ charPaddingLeft = padding
210
+ applyCharacterBackgrounds()
211
+ }
212
+
213
+ fun setCharPaddingRight(padding: Float) {
214
+ charPaddingRight = padding
215
+ applyCharacterBackgrounds()
216
+ }
217
+
218
+ fun setCharPaddingTop(padding: Float) {
219
+ charPaddingTop = padding
220
+ applyCharacterBackgrounds()
221
+ }
222
+
223
+ fun setCharPaddingBottom(padding: Float) {
224
+ charPaddingBottom = padding
225
+ applyCharacterBackgrounds()
226
+ }
227
+
228
+ fun setTextProp(text: String) {
229
+ if (this.text?.toString() != text) {
230
+ isUpdatingText = true
231
+ setText(text)
232
+ applyCharacterBackgrounds()
233
+ isUpdatingText = false
234
+ }
235
+ }
236
+
237
+ private fun applyCharacterBackgrounds() {
238
+ val text = text?.toString() ?: return
239
+ if (text.isEmpty()) return
240
+
241
+ val spannable = SpannableString(text)
242
+
243
+ // Apply character-by-character for proper line wrapping
244
+ for (i in text.indices) {
245
+ val char = text[i]
246
+
247
+ // Check if this is a space that should be highlighted
248
+ val shouldHighlight = when {
249
+ char == '\n' || char == '\t' -> false // Never highlight newlines or tabs
250
+ char == ' ' -> {
251
+ // Highlight space only if it's a single space (not multiple consecutive)
252
+ val hasSpaceBefore = i > 0 && text[i - 1] == ' '
253
+ val hasSpaceAfter = i < text.length - 1 && text[i + 1] == ' '
254
+ !hasSpaceBefore && !hasSpaceAfter
255
+ }
256
+ else -> true // Highlight all other characters
257
+ }
258
+
259
+ if (shouldHighlight) {
260
+ // Determine if this is the first or last character in a word group
261
+ val isFirst = i == 0 || !shouldHighlightChar(text, i - 1)
262
+ val isLast = i == text.length - 1 || !shouldHighlightChar(text, i + 1)
263
+
264
+ val span = RoundedBackgroundSpan(
265
+ characterBackgroundColor,
266
+ textColorValue,
267
+ charPaddingLeft,
268
+ charPaddingRight,
269
+ charPaddingTop,
270
+ charPaddingBottom,
271
+ cornerRadius,
272
+ isFirst,
273
+ isLast
274
+ )
275
+ spannable.setSpan(span, i, i + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
276
+ }
277
+ }
278
+
279
+ isUpdatingText = true
280
+ setText(spannable)
281
+ setSelection(text.length) // Keep cursor at end
282
+ isUpdatingText = false
283
+ }
284
+
285
+ private fun shouldHighlightChar(text: String, index: Int): Boolean {
286
+ if (index < 0 || index >= text.length) return false
287
+ val char = text[index]
288
+
289
+ return when {
290
+ char == '\n' || char == '\t' -> false
291
+ char == ' ' -> {
292
+ val hasSpaceBefore = index > 0 && text[index - 1] == ' '
293
+ val hasSpaceAfter = index < text.length - 1 && text[index + 1] == ' '
294
+ !hasSpaceBefore && !hasSpaceAfter
295
+ }
296
+ else -> true
297
+ }
298
+ }
15
299
  }
@@ -1,11 +1,17 @@
1
1
  package com.highlighttext
2
2
 
3
3
  import android.graphics.Color
4
+ import android.text.InputType
5
+ import android.view.Gravity
6
+ import com.facebook.react.bridge.Arguments
7
+ import com.facebook.react.bridge.ReactContext
8
+ import com.facebook.react.bridge.WritableMap
4
9
  import com.facebook.react.module.annotations.ReactModule
5
10
  import com.facebook.react.uimanager.SimpleViewManager
6
11
  import com.facebook.react.uimanager.ThemedReactContext
7
12
  import com.facebook.react.uimanager.ViewManagerDelegate
8
13
  import com.facebook.react.uimanager.annotations.ReactProp
14
+ import com.facebook.react.uimanager.events.RCTEventEmitter
9
15
  import com.facebook.react.viewmanagers.HighlightTextViewManagerInterface
10
16
  import com.facebook.react.viewmanagers.HighlightTextViewManagerDelegate
11
17
 
@@ -27,12 +33,135 @@ class HighlightTextViewManager : SimpleViewManager<HighlightTextView>(),
27
33
  }
28
34
 
29
35
  public override fun createViewInstance(context: ThemedReactContext): HighlightTextView {
30
- return HighlightTextView(context)
36
+ val view = HighlightTextView(context)
37
+ view.onTextChangeListener = { text ->
38
+ val event: WritableMap = Arguments.createMap()
39
+ event.putString("text", text)
40
+
41
+ val reactContext = context as ReactContext
42
+ reactContext
43
+ .getJSModule(RCTEventEmitter::class.java)
44
+ .receiveEvent(view.id, "onChange", event)
45
+ }
46
+ return view
31
47
  }
32
48
 
33
49
  @ReactProp(name = "color")
34
50
  override fun setColor(view: HighlightTextView?, color: String?) {
35
- view?.setBackgroundColor(Color.parseColor(color))
51
+ color?.let {
52
+ try {
53
+ view?.setCharacterBackgroundColor(Color.parseColor(it))
54
+ } catch (e: IllegalArgumentException) {
55
+ // Invalid color format, use default
56
+ view?.setCharacterBackgroundColor(Color.parseColor("#FFFF00"))
57
+ }
58
+ }
59
+ }
60
+
61
+ @ReactProp(name = "textColor")
62
+ override fun setTextColor(view: HighlightTextView?, value: String?) {
63
+ value?.let {
64
+ try {
65
+ view?.setTextColor(Color.parseColor(it))
66
+ } catch (e: IllegalArgumentException) {
67
+ // Invalid color format, use default
68
+ view?.setTextColor(Color.BLACK)
69
+ }
70
+ }
71
+ }
72
+
73
+ @ReactProp(name = "textAlign")
74
+ override fun setTextAlign(view: HighlightTextView?, value: String?) {
75
+ // Parse combined alignment (e.g., "top-left", "bottom-center")
76
+ val parts = value?.split("-") ?: emptyList()
77
+ var horizontalAlign = value
78
+
79
+ if (parts.size == 2) {
80
+ // Combined format: use the second part for horizontal alignment
81
+ horizontalAlign = parts[1]
82
+ }
83
+
84
+ // Apply horizontal alignment
85
+ view?.gravity = when (horizontalAlign) {
86
+ "left", "flex-start" -> Gravity.START or Gravity.CENTER_VERTICAL
87
+ "right", "flex-end" -> Gravity.END or Gravity.CENTER_VERTICAL
88
+ "center" -> Gravity.CENTER
89
+ "justify" -> Gravity.START or Gravity.CENTER_VERTICAL // Android doesn't support justify natively
90
+ else -> Gravity.CENTER
91
+ }
92
+ }
93
+
94
+ @ReactProp(name = "verticalAlign")
95
+ override fun setVerticalAlign(view: HighlightTextView?, value: String?) {
96
+ // Android EditText doesn't support vertical alignment as easily as iOS
97
+ // This would require custom implementation with layout adjustments
98
+ // For now, we'll keep the default behavior
99
+ }
100
+
101
+ @ReactProp(name = "fontFamily")
102
+ override fun setFontFamily(view: HighlightTextView?, value: String?) {
103
+ // Font family handling can be added if needed
104
+ }
105
+
106
+ @ReactProp(name = "fontSize")
107
+ override fun setFontSize(view: HighlightTextView?, value: String?) {
108
+ value?.toFloatOrNull()?.let { size ->
109
+ view?.textSize = size
110
+ }
111
+ }
112
+
113
+ @ReactProp(name = "padding")
114
+ override fun setPadding(view: HighlightTextView?, value: String?) {
115
+ value?.toFloatOrNull()?.let { padding ->
116
+ view?.setCharPadding(padding, padding, padding, padding)
117
+ }
118
+ }
119
+
120
+ @ReactProp(name = "paddingLeft")
121
+ override fun setPaddingLeft(view: HighlightTextView?, value: String?) {
122
+ value?.toFloatOrNull()?.let { padding ->
123
+ view?.setCharPaddingLeft(padding)
124
+ }
125
+ }
126
+
127
+ @ReactProp(name = "paddingRight")
128
+ override fun setPaddingRight(view: HighlightTextView?, value: String?) {
129
+ value?.toFloatOrNull()?.let { padding ->
130
+ view?.setCharPaddingRight(padding)
131
+ }
132
+ }
133
+
134
+ @ReactProp(name = "paddingTop")
135
+ override fun setPaddingTop(view: HighlightTextView?, value: String?) {
136
+ value?.toFloatOrNull()?.let { padding ->
137
+ view?.setCharPaddingTop(padding)
138
+ }
139
+ }
140
+
141
+ @ReactProp(name = "paddingBottom")
142
+ override fun setPaddingBottom(view: HighlightTextView?, value: String?) {
143
+ value?.toFloatOrNull()?.let { padding ->
144
+ view?.setCharPaddingBottom(padding)
145
+ }
146
+ }
147
+
148
+ @ReactProp(name = "text")
149
+ override fun setText(view: HighlightTextView?, value: String?) {
150
+ view?.setTextProp(value ?: "")
151
+ }
152
+
153
+ @ReactProp(name = "isEditable")
154
+ override fun setIsEditable(view: HighlightTextView?, value: Boolean) {
155
+ view?.apply {
156
+ isFocusable = value
157
+ isFocusableInTouchMode = value
158
+ isEnabled = value
159
+ inputType = if (value) {
160
+ InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE
161
+ } else {
162
+ InputType.TYPE_NULL
163
+ }
164
+ }
36
165
  }
37
166
 
38
167
  companion object {
@@ -74,6 +74,8 @@ using namespace facebook::react;
74
74
  CGFloat _paddingBottom;
75
75
  CGFloat _cornerRadius;
76
76
  BOOL _isUpdatingText;
77
+ NSString * _currentVerticalAlignment;
78
+ NSTextAlignment _currentHorizontalAlignment;
77
79
  }
78
80
 
79
81
  + (ComponentDescriptorProvider)componentDescriptorProvider
@@ -94,6 +96,8 @@ using namespace facebook::react;
94
96
  _paddingTop = 4.0;
95
97
  _paddingBottom = 4.0;
96
98
  _cornerRadius = 4.0;
99
+ _currentVerticalAlignment = nil;
100
+ _currentHorizontalAlignment = NSTextAlignmentCenter;
97
101
 
98
102
  // Create text storage, layout manager, and text container
99
103
  NSTextStorage *textStorage = [[NSTextStorage alloc] init];
@@ -128,6 +132,52 @@ using namespace facebook::react;
128
132
  return self;
129
133
  }
130
134
 
135
+ - (void)layoutSubviews
136
+ {
137
+ [super layoutSubviews];
138
+
139
+ // Recalculate vertical alignment after layout
140
+ if (_currentVerticalAlignment) {
141
+ [self updateVerticalAlignment:_currentVerticalAlignment];
142
+ }
143
+ }
144
+
145
+ - (void)updateVerticalAlignment:(NSString *)verticalAlign
146
+ {
147
+ if ([verticalAlign isEqualToString:@"top"]) {
148
+ _textView.textContainerInset = UIEdgeInsetsMake(10, 10, 0, 10);
149
+ } else if ([verticalAlign isEqualToString:@"bottom"]) {
150
+ // Force layout to get accurate content height
151
+ [_textView.layoutManager ensureLayoutForTextContainer:_textView.textContainer];
152
+
153
+ CGFloat contentHeight = [_textView.layoutManager usedRectForTextContainer:_textView.textContainer].size.height;
154
+ CGFloat viewHeight = _textView.bounds.size.height;
155
+
156
+ // Only apply bottom alignment if we have valid dimensions
157
+ if (viewHeight > 0 && contentHeight > 0) {
158
+ if (contentHeight + 20 <= viewHeight) {
159
+ // Content fits in view - align to bottom with inset
160
+ CGFloat topInset = MAX(10, viewHeight - contentHeight - 10);
161
+ _textView.textContainerInset = UIEdgeInsetsMake(topInset, 10, 10, 10);
162
+ } else {
163
+ // Content exceeds view - use minimal inset and scroll to bottom
164
+ _textView.textContainerInset = UIEdgeInsetsMake(10, 10, 10, 10);
165
+
166
+ // Scroll to bottom to show the latest text
167
+ dispatch_async(dispatch_get_main_queue(), ^{
168
+ CGFloat bottomOffset = self->_textView.contentSize.height - self->_textView.bounds.size.height + self->_textView.contentInset.bottom;
169
+ if (bottomOffset > 0) {
170
+ [self->_textView setContentOffset:CGPointMake(0, bottomOffset) animated:NO];
171
+ }
172
+ });
173
+ }
174
+ }
175
+ } else {
176
+ // Default or center
177
+ _textView.textContainerInset = UIEdgeInsetsMake(10, 10, 10, 10);
178
+ }
179
+ }
180
+
131
181
  - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
132
182
  {
133
183
  const auto &oldViewProps = *std::static_pointer_cast<HighlightTextViewProps const>(_props);
@@ -169,29 +219,26 @@ using namespace facebook::react;
169
219
  // Apply horizontal alignment
170
220
  if (horizontalPart) {
171
221
  if ([horizontalPart isEqualToString:@"center"]) {
172
- _textView.textAlignment = NSTextAlignmentCenter;
222
+ _currentHorizontalAlignment = NSTextAlignmentCenter;
173
223
  } else if ([horizontalPart isEqualToString:@"right"] || [horizontalPart isEqualToString:@"flex-end"]) {
174
- _textView.textAlignment = NSTextAlignmentRight;
224
+ _currentHorizontalAlignment = NSTextAlignmentRight;
175
225
  } else if ([horizontalPart isEqualToString:@"left"] || [horizontalPart isEqualToString:@"flex-start"]) {
176
- _textView.textAlignment = NSTextAlignmentLeft;
226
+ _currentHorizontalAlignment = NSTextAlignmentLeft;
177
227
  } else if ([horizontalPart isEqualToString:@"justify"]) {
178
- _textView.textAlignment = NSTextAlignmentJustified;
228
+ _currentHorizontalAlignment = NSTextAlignmentJustified;
179
229
  } else {
180
- _textView.textAlignment = NSTextAlignmentLeft;
230
+ _currentHorizontalAlignment = NSTextAlignmentLeft;
181
231
  }
232
+ _textView.textAlignment = _currentHorizontalAlignment;
182
233
  }
183
234
 
184
- // Apply vertical alignment via textContainerInset
185
- if ([verticalPart isEqualToString:@"top"]) {
186
- _textView.textContainerInset = UIEdgeInsetsMake(10, 10, 0, 10);
187
- } else if ([verticalPart isEqualToString:@"bottom"]) {
188
- // Calculate bottom alignment by adjusting top inset
189
- CGFloat contentHeight = [_textView.layoutManager usedRectForTextContainer:_textView.textContainer].size.height;
190
- CGFloat viewHeight = _textView.bounds.size.height;
191
- CGFloat topInset = MAX(10, viewHeight - contentHeight - 10);
192
- _textView.textContainerInset = UIEdgeInsetsMake(topInset, 10, 10, 10);
193
- } else if (!verticalPart) {
235
+ // Store and apply vertical alignment
236
+ if (verticalPart) {
237
+ _currentVerticalAlignment = verticalPart;
238
+ [self updateVerticalAlignment:verticalPart];
239
+ } else if (!verticalPart && horizontalPart) {
194
240
  // Default vertical centering for horizontal-only alignments
241
+ _currentVerticalAlignment = nil;
195
242
  _textView.textContainerInset = UIEdgeInsetsMake(10, 10, 10, 10);
196
243
  }
197
244
 
@@ -269,6 +316,12 @@ using namespace facebook::react;
269
316
  _isUpdatingText = YES;
270
317
  _textView.text = text;
271
318
  [self applyCharacterBackgrounds];
319
+
320
+ // Recalculate vertical alignment when text changes
321
+ if (_currentVerticalAlignment) {
322
+ [self updateVerticalAlignment:_currentVerticalAlignment];
323
+ }
324
+
272
325
  _isUpdatingText = NO;
273
326
  }
274
327
  }
@@ -279,18 +332,8 @@ using namespace facebook::react;
279
332
 
280
333
  if (oldViewProps.verticalAlign != newViewProps.verticalAlign) {
281
334
  NSString *verticalAlign = [[NSString alloc] initWithUTF8String: newViewProps.verticalAlign.c_str()];
282
-
283
- if ([verticalAlign isEqualToString:@"top"]) {
284
- _textView.textContainerInset = UIEdgeInsetsMake(10, 10, 0, 10);
285
- } else if ([verticalAlign isEqualToString:@"bottom"]) {
286
- CGFloat contentHeight = [_textView.layoutManager usedRectForTextContainer:_textView.textContainer].size.height;
287
- CGFloat viewHeight = _textView.bounds.size.height;
288
- CGFloat topInset = MAX(10, viewHeight - contentHeight - 10);
289
- _textView.textContainerInset = UIEdgeInsetsMake(topInset, 10, 10, 10);
290
- } else if ([verticalAlign isEqualToString:@"center"] || [verticalAlign isEqualToString:@"middle"]) {
291
- _textView.textContainerInset = UIEdgeInsetsMake(10, 10, 10, 10);
292
- }
293
-
335
+ _currentVerticalAlignment = verticalAlign;
336
+ [self updateVerticalAlignment:verticalAlign];
294
337
  [self applyCharacterBackgrounds];
295
338
  }
296
339
 
@@ -305,6 +348,12 @@ Class<RCTComponentViewProtocol> HighlightTextViewCls(void)
305
348
  - (void)textViewDidChange:(UITextView *)textView
306
349
  {
307
350
  [self applyCharacterBackgrounds];
351
+
352
+ // Recalculate vertical alignment when text changes
353
+ if (_currentVerticalAlignment) {
354
+ [self updateVerticalAlignment:_currentVerticalAlignment];
355
+ }
356
+
308
357
  if (!_isUpdatingText) {
309
358
  if (_eventEmitter != nullptr) {
310
359
  std::dynamic_pointer_cast<const HighlightTextViewEventEmitter>(_eventEmitter)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-highlight-text-view",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
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",