react-native-highlight-text-view 0.1.15 → 0.1.17

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
@@ -4,12 +4,10 @@ A native text input for React Native that supports inline text highlighting
4
4
 
5
5
  ## Installation
6
6
 
7
-
8
7
  ```sh
9
8
  npm install react-native-highlight-text
10
9
  ```
11
10
 
12
-
13
11
  ## Usage
14
12
 
15
13
  ```js
@@ -42,28 +40,29 @@ export default function App() {
42
40
 
43
41
  ## Props
44
42
 
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. **Note:** Android does not support vertical alignment and will use default vertical positioning. |
51
- | `fontFamily` | `string` | - | Font family name |
52
- | `fontSize` | `string` | `32` | Font size in points |
53
- | `lineHeight` | `string` | `0` | Line height override (0 means use default line height) |
54
- | `highlightBorderRadius` | `string` | `0` | Border radius for the highlight background |
55
- | `padding` | `string` | `4` | Padding around each character highlight (expands background outward) |
56
- | `paddingLeft` | `string` | - | Left padding for character highlight |
57
- | `paddingRight` | `string` | - | Right padding for character highlight |
58
- | `paddingTop` | `string` | - | Top padding for character highlight |
59
- | `paddingBottom` | `string` | - | Bottom padding for character highlight |
60
- | `backgroundInsetTop` | `string` | `0` | Shrinks background from top (useful for fonts with large vertical metrics) |
61
- | `backgroundInsetBottom` | `string` | `0` | Shrinks background from bottom (useful for fonts with large vertical metrics) |
62
- | `backgroundInsetLeft` | `string` | `0` | Shrinks background from left |
63
- | `backgroundInsetRight` | `string` | `0` | Shrinks background from right |
64
- | `text` | `string` | - | Controlled text value |
65
- | `isEditable` | `boolean` | `true` | Whether the text is editable |
66
- | `onChange` | `(event: { nativeEvent: { text: string } }) => void` | - | Callback fired when text changes |
43
+ | Prop | Type | Default | Description |
44
+ | ----------------------- | ---------------------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
45
+ | `color` | `string` | `#FFFF00` | Background highlight color (hex format) |
46
+ | `textColor` | `string` | - | Text color (hex format) |
47
+ | `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'` |
48
+ | `verticalAlign` | `'top' \| 'center' \| 'middle' \| 'bottom'` | - | Vertical alignment (iOS only). Alternative to using combined `textAlign` values. **Note:** Android does not support vertical alignment and will use default vertical positioning. |
49
+ | `fontFamily` | `string` | - | Font family name |
50
+ | `fontSize` | `string` | `32` | Font size in points |
51
+ | `lineHeight` | `string` | `0` | Line height override (0 means use default line height) |
52
+ | `highlightBorderRadius` | `string` | `0` | Border radius for the highlight background |
53
+ | `padding` | `string` | `4` | Padding around each character highlight (expands background outward) |
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
+ | `backgroundInsetTop` | `string` | `0` | Shrinks background from top (useful for fonts with large vertical metrics) |
59
+ | `backgroundInsetBottom` | `string` | `0` | Shrinks background from bottom (useful for fonts with large vertical metrics) |
60
+ | `backgroundInsetLeft` | `string` | `0` | Shrinks background from left |
61
+ | `backgroundInsetRight` | `string` | `0` | Shrinks background from right |
62
+ | `text` | `string` | - | Controlled text value |
63
+ | `isEditable` | `boolean` | `true` | Whether the text is editable |
64
+ | `autoFocus` | `boolean` | `false` | If true, automatically focuses the text input and opens the keyboard when component mounts (only works when `isEditable` is `true`) |
65
+ | `onChange` | `(event: { nativeEvent: { text: string } }) => void` | - | Callback fired when text changes |
67
66
 
68
67
  ### Understanding Padding vs Background Insets
69
68
 
@@ -73,6 +72,7 @@ export default function App() {
73
72
  **Use case for background insets:** Some fonts (like Eczar, Georgia, etc.) have large built-in vertical metrics (ascender/descender), making highlights appear too tall. Use `backgroundInsetTop` and `backgroundInsetBottom` to create a tighter fit around the visible glyphs.
74
73
 
75
74
  **Example with large-metric font:**
75
+
76
76
  ```jsx
77
77
  <HighlightTextView
78
78
  fontFamily="Eczar"
@@ -81,24 +81,25 @@ export default function App() {
81
81
  paddingRight="8"
82
82
  paddingTop="4"
83
83
  paddingBottom="4"
84
- backgroundInsetTop="6"
85
- backgroundInsetBottom="6"
84
+ backgroundInsetTop="6"
85
+ backgroundInsetBottom="6"
86
86
  text="Tight Background"
87
87
  />
88
88
  ```
89
89
 
90
90
  **Example with touching backgrounds (tight line spacing):**
91
91
  To make backgrounds touch vertically across multiple lines, combine `lineHeight` with `backgroundInset`:
92
+
92
93
  ```jsx
93
94
  <HighlightTextView
94
95
  fontSize="32"
95
- lineHeight="36" // Slightly larger than fontSize for tight spacing
96
+ lineHeight="36" // Slightly larger than fontSize for tight spacing
96
97
  paddingLeft="8"
97
98
  paddingRight="8"
98
99
  paddingTop="4"
99
100
  paddingBottom="4"
100
- backgroundInsetTop="14" // Large inset reduces background height
101
- backgroundInsetBottom="14" // Creates room for lines to touch
101
+ backgroundInsetTop="14" // Large inset reduces background height
102
+ backgroundInsetBottom="14" // Creates room for lines to touch
102
103
  highlightBorderRadius="4"
103
104
  text="Multiple lines with touching backgrounds create smooth vertical flow"
104
105
  />
@@ -108,7 +109,24 @@ To make backgrounds touch vertically across multiple lines, combine `lineHeight`
108
109
 
109
110
  **Note:** Vertical alignment is currently supported on iOS only. On Android, text will use default vertical positioning.
110
111
 
112
+ ### Auto-focusing the Input
111
113
 
114
+ To automatically open the keyboard when the component mounts, use the `autoFocus` prop:
115
+
116
+ ```jsx
117
+ <HighlightTextView
118
+ color="#00A4A3"
119
+ textColor="#FFFFFF"
120
+ fontSize="20"
121
+ text={text}
122
+ isEditable={true}
123
+ autoFocus={true} // Keyboard opens automatically
124
+ onChange={(e) => setText(e.nativeEvent.text)}
125
+ style={{ width: '100%', height: 100 }}
126
+ />
127
+ ```
128
+
129
+ This eliminates the need for double-tapping to open the keyboard - it will open on first render.
112
130
 
113
131
  ## Contributing
114
132
 
@@ -116,7 +134,6 @@ To make backgrounds touch vertically across multiple lines, combine `lineHeight`
116
134
  - [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
117
135
  - [Code of conduct](CODE_OF_CONDUCT.md)
118
136
 
119
-
120
137
  ## License MIT
121
138
 
122
139
  Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
@@ -206,6 +206,13 @@ class HighlightTextView : AppCompatEditText {
206
206
  }
207
207
  }
208
208
  })
209
+
210
+ // Set cursor at end when view gains focus
211
+ setOnFocusChangeListener { _, hasFocus ->
212
+ if (hasFocus) {
213
+ text?.length?.let { setSelection(it) }
214
+ }
215
+ }
209
216
  }
210
217
 
211
218
  fun setCharacterBackgroundColor(color: Int) {
@@ -309,7 +316,14 @@ class HighlightTextView : AppCompatEditText {
309
316
  }
310
317
 
311
318
  this.typeface = typeface
312
- applyCharacterBackgrounds()
319
+
320
+ // Force complete layout recalculation with new font metrics
321
+ // This ensures background highlights recalculate with new ascender/descender values
322
+ post {
323
+ applyCharacterBackgrounds()
324
+ requestLayout()
325
+ invalidate()
326
+ }
313
327
  }
314
328
 
315
329
  fun setVerticalAlign(align: String?) {
@@ -363,10 +377,25 @@ class HighlightTextView : AppCompatEditText {
363
377
  }
364
378
  }
365
379
 
380
+ fun setAutoFocus(autoFocus: Boolean) {
381
+ if (autoFocus && isFocusable && isFocusableInTouchMode) {
382
+ post {
383
+ requestFocus()
384
+ // Set cursor at the end of text
385
+ text?.length?.let { setSelection(it) }
386
+ val imm = context.getSystemService(android.content.Context.INPUT_METHOD_SERVICE) as? android.view.inputmethod.InputMethodManager
387
+ imm?.showSoftInput(this, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT)
388
+ }
389
+ }
390
+ }
391
+
366
392
  private fun applyCharacterBackgrounds() {
367
393
  val text = text?.toString() ?: return
368
394
  if (text.isEmpty()) return
369
395
 
396
+ // Save current cursor position
397
+ val currentSelection = selectionStart
398
+
370
399
  val spannable = SpannableString(text)
371
400
 
372
401
  // Apply line height if specified
@@ -420,7 +449,9 @@ class HighlightTextView : AppCompatEditText {
420
449
 
421
450
  isUpdatingText = true
422
451
  setText(spannable)
423
- setSelection(text.length) // Keep cursor at end
452
+ // Restore cursor position, or set to end if position is invalid
453
+ val safePosition = currentSelection.coerceIn(0, text.length)
454
+ setSelection(safePosition)
424
455
  isUpdatingText = false
425
456
 
426
457
  // Detect line wraps after layout is ready
@@ -235,6 +235,11 @@ class HighlightTextViewManager : SimpleViewManager<HighlightTextView>(),
235
235
  }
236
236
  }
237
237
 
238
+ @ReactProp(name = "autoFocus")
239
+ override fun setAutoFocus(view: HighlightTextView?, value: Boolean) {
240
+ view?.setAutoFocus(value)
241
+ }
242
+
238
243
  companion object {
239
244
  const val NAME = "HighlightTextView"
240
245
  }
@@ -426,6 +426,17 @@ using namespace facebook::react;
426
426
  _textView.editable = newViewProps.isEditable;
427
427
  }
428
428
 
429
+ if (oldViewProps.autoFocus != newViewProps.autoFocus) {
430
+ if (newViewProps.autoFocus && _textView.isEditable) {
431
+ dispatch_async(dispatch_get_main_queue(), ^{
432
+ [self->_textView becomeFirstResponder];
433
+ // Set cursor at the end of text
434
+ NSUInteger textLength = self->_textView.text.length;
435
+ self->_textView.selectedRange = NSMakeRange(textLength, 0);
436
+ });
437
+ }
438
+ }
439
+
429
440
  if (oldViewProps.verticalAlign != newViewProps.verticalAlign) {
430
441
  NSString *verticalAlign = [[NSString alloc] initWithUTF8String: newViewProps.verticalAlign.c_str()];
431
442
  _currentVerticalAlignment = verticalAlign;
@@ -460,6 +471,13 @@ Class<RCTComponentViewProtocol> HighlightTextViewCls(void)
460
471
  }
461
472
  }
462
473
 
474
+ - (void)textViewDidBeginEditing:(UITextView *)textView
475
+ {
476
+ // Set cursor at the end when editing begins
477
+ NSUInteger textLength = textView.text.length;
478
+ textView.selectedRange = NSMakeRange(textLength, 0);
479
+ }
480
+
463
481
  - (void)updateFont
464
482
  {
465
483
  CGFloat fontSize = _fontSize > 0 ? _fontSize : 32.0;
@@ -511,7 +529,18 @@ Class<RCTComponentViewProtocol> HighlightTextViewCls(void)
511
529
  }
512
530
 
513
531
  _textView.font = newFont;
532
+
533
+ // Force layout manager to invalidate and recalculate layout
534
+ [_layoutManager invalidateLayoutForCharacterRange:NSMakeRange(0, _textView.textStorage.length) actualCharacterRange:NULL];
535
+ [_layoutManager invalidateDisplayForCharacterRange:NSMakeRange(0, _textView.textStorage.length)];
536
+
537
+ // Reapply character backgrounds with new font metrics
514
538
  [self applyCharacterBackgrounds];
539
+
540
+ // Force the text view to redraw
541
+ [_textView setNeedsDisplay];
542
+ [_textView setNeedsLayout];
543
+ [_textView layoutIfNeeded];
515
544
  }
516
545
 
517
546
  - (void)applyCharacterBackgrounds
@@ -521,6 +550,9 @@ Class<RCTComponentViewProtocol> HighlightTextViewCls(void)
521
550
  return;
522
551
  }
523
552
 
553
+ // Save current cursor position
554
+ NSRange currentSelection = _textView.selectedRange;
555
+
524
556
  NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:text];
525
557
  UIColor *bgColor = [self hexStringToColor:_characterBackgroundColor];
526
558
 
@@ -569,6 +601,12 @@ Class<RCTComponentViewProtocol> HighlightTextViewCls(void)
569
601
  }
570
602
 
571
603
  _textView.attributedText = attributedString;
604
+
605
+ // Restore cursor position if valid
606
+ if (currentSelection.location <= text.length) {
607
+ _textView.selectedRange = currentSelection;
608
+ }
609
+
572
610
  [_textView setNeedsDisplay];
573
611
  }
574
612
 
@@ -64,6 +64,7 @@ export interface HighlightTextViewProps extends ViewProps {
64
64
  backgroundInsetRight?: string;
65
65
  text?: string;
66
66
  isEditable?: boolean;
67
+ autoFocus?: boolean;
67
68
  onChange?: BubblingEventHandler<OnChangeEventData>;
68
69
  }
69
70
 
@@ -46,6 +46,7 @@ export interface HighlightTextViewProps extends ViewProps {
46
46
  backgroundInsetRight?: string;
47
47
  text?: string;
48
48
  isEditable?: boolean;
49
+ autoFocus?: boolean;
49
50
  onChange?: BubblingEventHandler<OnChangeEventData>;
50
51
  }
51
52
  declare const _default: import("react-native/types_generated/Libraries/Utilities/codegenNativeComponent").NativeComponentType<HighlightTextViewProps>;
@@ -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;;;;;;;;;;;;;;;;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,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,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,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oFAAoF;IACpF,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,gFAAgF;IAChF,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iFAAiF;IACjF,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,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,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,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,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oFAAoF;IACpF,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,gFAAgF;IAChF,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iFAAiF;IACjF,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,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.15",
3
+ "version": "0.1.17",
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",
@@ -64,6 +64,7 @@ export interface HighlightTextViewProps extends ViewProps {
64
64
  backgroundInsetRight?: string;
65
65
  text?: string;
66
66
  isEditable?: boolean;
67
+ autoFocus?: boolean;
67
68
  onChange?: BubblingEventHandler<OnChangeEventData>;
68
69
  }
69
70