react-native-highlight-text-view 0.1.2 → 0.1.4

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
@@ -12,14 +12,56 @@ npm install react-native-highlight-text
12
12
 
13
13
  ## Usage
14
14
 
15
-
16
15
  ```js
17
- import { HighlightTextView } from "react-native-highlight-text";
16
+ import { useState } from 'react';
17
+ import { HighlightTextView } from 'react-native-highlight-text-view';
18
+
19
+ export default function App() {
20
+ const [text, setText] = useState('Hello World');
21
+
22
+ return (
23
+ <HighlightTextView
24
+ color="#00A4A3"
25
+ textColor="#000000"
26
+ textAlign="flex-start"
27
+ fontSize="32"
28
+ paddingLeft="8"
29
+ paddingRight="8"
30
+ paddingTop="4"
31
+ paddingBottom="4"
32
+ text={text}
33
+ isEditable={true}
34
+ onChange={(e) => {
35
+ setText(e.nativeEvent.text);
36
+ }}
37
+ style={{ width: '100%', height: 200 }}
38
+ />
39
+ );
40
+ }
41
+ ```
18
42
 
19
- // ...
43
+ ## Props
44
+
45
+ | Prop | Type | Default | Description |
46
+ |------|------|---------|-------------|
47
+ | `color` | `string` | `#FFFF00` | Background highlight color (hex format) |
48
+ | `textColor` | `string` | - | Text color (hex format) |
49
+ | `textAlign` | `string` | `left` | Text alignment. Supports: `'left'`, `'center'`, `'right'`, `'justify'`, `'flex-start'`, `'flex-end'`, `'top'`, `'bottom'`, `'top-left'`, `'top-center'`, `'top-right'`, `'bottom-left'`, `'bottom-center'`, `'bottom-right'` |
50
+ | `verticalAlign` | `'top' \| 'center' \| 'middle' \| 'bottom'` | - | Vertical alignment (iOS only). Alternative to using combined `textAlign` values |
51
+ | `fontFamily` | `string` | - | Font family name |
52
+ | `fontSize` | `string` | `32` | Font size in points |
53
+ | `padding` | `string` | `4` | Padding around each character highlight |
54
+ | `paddingLeft` | `string` | - | Left padding for character highlight |
55
+ | `paddingRight` | `string` | - | Right padding for character highlight |
56
+ | `paddingTop` | `string` | - | Top padding for character highlight |
57
+ | `paddingBottom` | `string` | - | Bottom padding for character highlight |
58
+ | `text` | `string` | - | Controlled text value |
59
+ | `isEditable` | `boolean` | `true` | Whether the text is editable |
60
+ | `onChange` | `(event: { nativeEvent: { text: string } }) => void` | - | Callback fired when text changes |
61
+
62
+
63
+ **Note:** Vertical alignment is currently supported on iOS only. On Android, text will use default vertical positioning.
20
64
 
21
- <HighlightTextView color="tomato" />
22
- ```
23
65
 
24
66
 
25
67
  ## Contributing
@@ -28,10 +70,7 @@ import { HighlightTextView } from "react-native-highlight-text";
28
70
  - [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
29
71
  - [Code of conduct](CODE_OF_CONDUCT.md)
30
72
 
31
- ## License
32
-
33
- MIT
34
73
 
35
- ---
74
+ ## License MIT
36
75
 
37
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 {
@@ -147,13 +147,54 @@ using namespace facebook::react;
147
147
 
148
148
  if (oldViewProps.textAlign != newViewProps.textAlign) {
149
149
  NSString *alignment = [[NSString alloc] initWithUTF8String: newViewProps.textAlign.c_str()];
150
- if ([alignment isEqualToString:@"center"]) {
151
- _textView.textAlignment = NSTextAlignmentCenter;
152
- } else if ([alignment isEqualToString:@"right"]) {
153
- _textView.textAlignment = NSTextAlignmentRight;
150
+
151
+ // Parse combined alignment (e.g., "top-left", "bottom-center")
152
+ NSArray *parts = [alignment componentsSeparatedByString:@"-"];
153
+ NSString *verticalPart = nil;
154
+ NSString *horizontalPart = nil;
155
+
156
+ if (parts.count == 2) {
157
+ // Combined format: "top-left", "bottom-center", etc.
158
+ verticalPart = parts[0];
159
+ horizontalPart = parts[1];
154
160
  } else {
155
- _textView.textAlignment = NSTextAlignmentLeft;
161
+ // Single value - determine if it's horizontal or vertical
162
+ if ([alignment isEqualToString:@"top"] || [alignment isEqualToString:@"bottom"]) {
163
+ verticalPart = alignment;
164
+ } else {
165
+ horizontalPart = alignment;
166
+ }
167
+ }
168
+
169
+ // Apply horizontal alignment
170
+ if (horizontalPart) {
171
+ if ([horizontalPart isEqualToString:@"center"]) {
172
+ _textView.textAlignment = NSTextAlignmentCenter;
173
+ } else if ([horizontalPart isEqualToString:@"right"] || [horizontalPart isEqualToString:@"flex-end"]) {
174
+ _textView.textAlignment = NSTextAlignmentRight;
175
+ } else if ([horizontalPart isEqualToString:@"left"] || [horizontalPart isEqualToString:@"flex-start"]) {
176
+ _textView.textAlignment = NSTextAlignmentLeft;
177
+ } else if ([horizontalPart isEqualToString:@"justify"]) {
178
+ _textView.textAlignment = NSTextAlignmentJustified;
179
+ } else {
180
+ _textView.textAlignment = NSTextAlignmentLeft;
181
+ }
182
+ }
183
+
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) {
194
+ // Default vertical centering for horizontal-only alignments
195
+ _textView.textContainerInset = UIEdgeInsetsMake(10, 10, 10, 10);
156
196
  }
197
+
157
198
  [self applyCharacterBackgrounds]; // Reapply to update alignment
158
199
  }
159
200
 
@@ -235,6 +276,23 @@ using namespace facebook::react;
235
276
  if (oldViewProps.isEditable != newViewProps.isEditable) {
236
277
  _textView.editable = newViewProps.isEditable;
237
278
  }
279
+
280
+ if (oldViewProps.verticalAlign != newViewProps.verticalAlign) {
281
+ 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
+
294
+ [self applyCharacterBackgrounds];
295
+ }
238
296
 
239
297
  [super updateProps:props oldProps:oldProps];
240
298
  }
@@ -6,10 +6,44 @@ export interface OnChangeEventData {
6
6
  readonly text: string;
7
7
  }
8
8
 
9
+ /**
10
+ * Text alignment options
11
+ *
12
+ * Horizontal alignment:
13
+ * - 'left' or 'flex-start': Align text to the left
14
+ * - 'center': Center align text
15
+ * - 'right' or 'flex-end': Align text to the right
16
+ * - 'justify': Justify text (distribute evenly)
17
+ *
18
+ * Vertical alignment (iOS only):
19
+ * - 'top': Align to top
20
+ * - 'bottom': Align to bottom
21
+ *
22
+ * Combined alignment (iOS only):
23
+ * - 'top-left', 'top-center', 'top-right'
24
+ * - 'bottom-left', 'bottom-center', 'bottom-right'
25
+ */
26
+ export type TextAlignment =
27
+ | 'left'
28
+ | 'center'
29
+ | 'right'
30
+ | 'justify'
31
+ | 'flex-start'
32
+ | 'flex-end'
33
+ | 'top'
34
+ | 'bottom'
35
+ | 'top-left'
36
+ | 'top-center'
37
+ | 'top-right'
38
+ | 'bottom-left'
39
+ | 'bottom-center'
40
+ | 'bottom-right';
41
+
9
42
  export interface HighlightTextViewProps extends ViewProps {
10
43
  color?: string;
11
44
  textColor?: string;
12
45
  textAlign?: string;
46
+ verticalAlign?: string;
13
47
  fontFamily?: string;
14
48
  fontSize?: string;
15
49
  padding?: string;
@@ -3,10 +3,29 @@ import type { BubblingEventHandler } from 'react-native/Libraries/Types/CodegenT
3
3
  export interface OnChangeEventData {
4
4
  readonly text: string;
5
5
  }
6
+ /**
7
+ * Text alignment options
8
+ *
9
+ * Horizontal alignment:
10
+ * - 'left' or 'flex-start': Align text to the left
11
+ * - 'center': Center align text
12
+ * - 'right' or 'flex-end': Align text to the right
13
+ * - 'justify': Justify text (distribute evenly)
14
+ *
15
+ * Vertical alignment (iOS only):
16
+ * - 'top': Align to top
17
+ * - 'bottom': Align to bottom
18
+ *
19
+ * Combined alignment (iOS only):
20
+ * - 'top-left', 'top-center', 'top-right'
21
+ * - 'bottom-left', 'bottom-center', 'bottom-right'
22
+ */
23
+ export type TextAlignment = 'left' | 'center' | 'right' | 'justify' | 'flex-start' | 'flex-end' | 'top' | 'bottom' | 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
6
24
  export interface HighlightTextViewProps extends ViewProps {
7
25
  color?: string;
8
26
  textColor?: string;
9
27
  textAlign?: string;
28
+ verticalAlign?: string;
10
29
  fontFamily?: string;
11
30
  fontSize?: string;
12
31
  padding?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"HighlightTextViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../src/HighlightTextViewNativeComponent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEtF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,sBAAuB,SAAQ,SAAS;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,oBAAoB,CAAC,iBAAiB,CAAC,CAAC;CACpD;;AAED,wBAEE"}
1
+ {"version":3,"file":"HighlightTextViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../src/HighlightTextViewNativeComponent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEtF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,QAAQ,GACR,OAAO,GACP,SAAS,GACT,YAAY,GACZ,UAAU,GACV,KAAK,GACL,QAAQ,GACR,UAAU,GACV,YAAY,GACZ,WAAW,GACX,aAAa,GACb,eAAe,GACf,cAAc,CAAC;AAEnB,MAAM,WAAW,sBAAuB,SAAQ,SAAS;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,oBAAoB,CAAC,iBAAiB,CAAC,CAAC;CACpD;;AAED,wBAEE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-highlight-text-view",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
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",
@@ -6,10 +6,44 @@ export interface OnChangeEventData {
6
6
  readonly text: string;
7
7
  }
8
8
 
9
+ /**
10
+ * Text alignment options
11
+ *
12
+ * Horizontal alignment:
13
+ * - 'left' or 'flex-start': Align text to the left
14
+ * - 'center': Center align text
15
+ * - 'right' or 'flex-end': Align text to the right
16
+ * - 'justify': Justify text (distribute evenly)
17
+ *
18
+ * Vertical alignment (iOS only):
19
+ * - 'top': Align to top
20
+ * - 'bottom': Align to bottom
21
+ *
22
+ * Combined alignment (iOS only):
23
+ * - 'top-left', 'top-center', 'top-right'
24
+ * - 'bottom-left', 'bottom-center', 'bottom-right'
25
+ */
26
+ export type TextAlignment =
27
+ | 'left'
28
+ | 'center'
29
+ | 'right'
30
+ | 'justify'
31
+ | 'flex-start'
32
+ | 'flex-end'
33
+ | 'top'
34
+ | 'bottom'
35
+ | 'top-left'
36
+ | 'top-center'
37
+ | 'top-right'
38
+ | 'bottom-left'
39
+ | 'bottom-center'
40
+ | 'bottom-right';
41
+
9
42
  export interface HighlightTextViewProps extends ViewProps {
10
43
  color?: string;
11
44
  textColor?: string;
12
45
  textAlign?: string;
46
+ verticalAlign?: string;
13
47
  fontFamily?: string;
14
48
  fontSize?: string;
15
49
  padding?: string;