react-native-advanced-text 0.2.16 → 0.2.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
@@ -52,6 +52,15 @@ import { AdvancedText } from "react-native-advanced-text";
52
52
  />
53
53
  ```
54
54
 
55
+ ## Word gestures
56
+
57
+ | Prop | Fires on | Notes |
58
+ | --- | --- | --- |
59
+ | `onWordPress` | a tap on a word | `{ word, index }` |
60
+ | `onWordDoublePress` | a double tap on a word | `{ word, index }`. A double tap never selects text. While this is set, a single tap waits out the double-tap delay (~300 ms) before `onWordPress`, so a double tap doesn't also fire it. |
61
+ | `onWordLongPress` | a long press on a word | `{ word, index }`. Text selection still starts as usual. |
62
+ | `onSelection` | the selection menu | `{ selectedText, event, startWordIndex, endWordIndex }`: the menu option, and the first and last word index the selection touches (`-1` if none). |
63
+
55
64
  ## Underlined words
56
65
 
57
66
  An entry in `highlightedWords` with `underline: true` draws a line under the
@@ -24,6 +24,7 @@ import android.view.Menu
24
24
  import android.view.MenuItem
25
25
  import android.view.View
26
26
  import android.view.MotionEvent
27
+ import android.view.ViewConfiguration
27
28
  import android.view.accessibility.AccessibilityNodeInfo
28
29
  import android.widget.TextView
29
30
  import com.facebook.react.bridge.Arguments
@@ -40,6 +41,8 @@ class AdvancedTextView : TextView {
40
41
  private var highlightedWords: List<HighlightedWord> = emptyList()
41
42
  private var menuOptions: List<String> = emptyList()
42
43
  private var lastSelectedText: String = ""
44
+ // First and last word index the selection touches, -1 for none.
45
+ private var lastSelectedWords: Pair<Int, Int> = -1 to -1
43
46
  private var customActionMode: ActionMode? = null
44
47
  private var currentText: String = ""
45
48
  private var textColor: String = "#000000"
@@ -68,6 +71,23 @@ class AdvancedTextView : TextView {
68
71
  private val rangeBounds = RectF()
69
72
  private val underlineBounds = RectF()
70
73
 
74
+ // Double taps are only told apart while the app handles them
75
+ // (onWordDoublePress); otherwise a tap fires onWordPress at once.
76
+ private var wordDoublePressEnabled = false
77
+ private val doubleTapTimeout = ViewConfiguration.getDoubleTapTimeout().toLong()
78
+ private var doubleTapSlopSquare = 0
79
+ private var lastTapUpTime = 0L
80
+ private var lastTapUpX = 0f
81
+ private var lastTapUpY = 0f
82
+ // From a double tap's second touch down to its up. The TextView never sees
83
+ // those events, so it can't select the word as it would on a double tap.
84
+ private var handlingDoubleTap = false
85
+ // A tap's onWordPress, held back while a second tap could still come.
86
+ private var pendingWordPress: Runnable? = null
87
+ private var lastDownX = 0f
88
+ private var lastDownY = 0f
89
+ private var longPressed = false
90
+
71
91
  constructor(context: Context?) : super(context) { init() }
72
92
  constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { init() }
73
93
  constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { init() }
@@ -93,6 +113,19 @@ class AdvancedTextView : TextView {
93
113
 
94
114
  movementMethod = SmartMovementMethod
95
115
 
116
+ val slop = ViewConfiguration.get(context).scaledDoubleTapSlop
117
+ doubleTapSlopSquare = slop * slop
118
+
119
+ // Reports the word, then returns false so the TextView still starts a
120
+ // selection as usual.
121
+ setOnLongClickListener {
122
+ longPressed = true
123
+ wordAt(lastDownX, lastDownY)?.let {
124
+ sendWordEvent("onWordLongPress", it.word, it.index)
125
+ }
126
+ false
127
+ }
128
+
96
129
  customSelectionActionModeCallback = object : ActionMode.Callback {
97
130
  override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
98
131
  customActionMode = mode
@@ -107,6 +140,7 @@ class AdvancedTextView : TextView {
107
140
 
108
141
  if (selectionStart >= 0 && selectionEnd >= 0 && selectionStart != selectionEnd) {
109
142
  lastSelectedText = text.subSequence(selectionStart, selectionEnd).toString()
143
+ lastSelectedWords = wordIndexRange(selectionStart, selectionEnd)
110
144
 
111
145
  menuOptions.forEachIndexed { index, option ->
112
146
  menu?.add(0, index, index, option)
@@ -525,6 +559,8 @@ class AdvancedTextView : TextView {
525
559
  val event = Arguments.createMap().apply {
526
560
  putString("selectedText", selectedText)
527
561
  putString("event", eventType)
562
+ putInt("startWordIndex", lastSelectedWords.first)
563
+ putInt("endWordIndex", lastSelectedWords.second)
528
564
  }
529
565
 
530
566
  reactContext.getJSModule(RCTEventEmitter::class.java)
@@ -541,7 +577,18 @@ class AdvancedTextView : TextView {
541
577
 
542
578
  override fun onClick(widget: View) {
543
579
  Log.d(TAG, "Word clicked: '$word' (index=$wordIndex)")
544
- sendWordPressEvent(word, wordIndex)
580
+ if (!wordDoublePressEnabled) {
581
+ sendWordEvent("onWordPress", word, wordIndex)
582
+ return
583
+ }
584
+ // Wait to see whether this is the first tap of a double tap.
585
+ cancelPendingWordPress()
586
+ val press = Runnable {
587
+ pendingWordPress = null
588
+ sendWordEvent("onWordPress", word, wordIndex)
589
+ }
590
+ pendingWordPress = press
591
+ postDelayed(press, doubleTapTimeout)
545
592
  }
546
593
 
547
594
  // Skips super, which would paint the word in the link color and
@@ -551,7 +598,8 @@ class AdvancedTextView : TextView {
551
598
  }
552
599
  }
553
600
 
554
- private fun sendWordPressEvent(word: String, index: Int) {
601
+ // onWordPress, onWordDoublePress or onWordLongPress.
602
+ private fun sendWordEvent(eventName: String, word: String, index: Int) {
555
603
  try {
556
604
  val reactContext = context as? ReactContext ?: return
557
605
  val event = Arguments.createMap().apply {
@@ -560,10 +608,102 @@ class AdvancedTextView : TextView {
560
608
  }
561
609
 
562
610
  reactContext.getJSModule(RCTEventEmitter::class.java)
563
- .receiveEvent(id, "onWordPress", event)
611
+ .receiveEvent(id, eventName, event)
564
612
  } catch (e: Exception) {
565
- Log.e(TAG, "Error sending word press event", e)
613
+ Log.e(TAG, "Error sending $eventName event", e)
614
+ }
615
+ }
616
+
617
+ fun setWordDoublePressEnabled(enabled: Boolean) {
618
+ if (wordDoublePressEnabled == enabled) return
619
+ wordDoublePressEnabled = enabled
620
+ lastTapUpTime = 0L
621
+ // A tap held back for a double tap fires now instead of being lost.
622
+ pendingWordPress?.let {
623
+ removeCallbacks(it)
624
+ it.run()
625
+ }
626
+ }
627
+
628
+ override fun onTouchEvent(event: MotionEvent): Boolean {
629
+ when (event.actionMasked) {
630
+ MotionEvent.ACTION_DOWN -> {
631
+ lastDownX = event.x
632
+ lastDownY = event.y
633
+ longPressed = false
634
+ if (wordDoublePressEnabled && isSecondTap(event)) {
635
+ cancelPendingWordPress()
636
+ handlingDoubleTap = true
637
+ return true
638
+ }
639
+ }
640
+ MotionEvent.ACTION_UP -> if (handlingDoubleTap) {
641
+ handlingDoubleTap = false
642
+ lastTapUpTime = 0L
643
+ wordAt(event.x, event.y)?.let {
644
+ sendWordEvent("onWordDoublePress", it.word, it.index)
645
+ }
646
+ return true
647
+ }
648
+ MotionEvent.ACTION_CANCEL -> if (handlingDoubleTap) {
649
+ handlingDoubleTap = false
650
+ lastTapUpTime = 0L
651
+ return true
652
+ }
653
+ }
654
+ if (handlingDoubleTap) return true
655
+
656
+ val handled = super.onTouchEvent(event)
657
+ if (event.actionMasked == MotionEvent.ACTION_UP) {
658
+ // A long press isn't the first tap of a double tap.
659
+ if (longPressed) {
660
+ lastTapUpTime = 0L
661
+ } else {
662
+ lastTapUpTime = event.eventTime
663
+ lastTapUpX = event.x
664
+ lastTapUpY = event.y
665
+ }
566
666
  }
667
+ return handled
668
+ }
669
+
670
+ override fun onDetachedFromWindow() {
671
+ cancelPendingWordPress()
672
+ super.onDetachedFromWindow()
673
+ }
674
+
675
+ private fun isSecondTap(down: MotionEvent): Boolean {
676
+ if (lastTapUpTime == 0L || down.eventTime - lastTapUpTime > doubleTapTimeout) {
677
+ return false
678
+ }
679
+ val dx = down.x - lastTapUpX
680
+ val dy = down.y - lastTapUpY
681
+ return dx * dx + dy * dy < doubleTapSlopSquare
682
+ }
683
+
684
+ private fun cancelPendingWordPress() {
685
+ pendingWordPress?.let { removeCallbacks(it) }
686
+ pendingWordPress = null
687
+ }
688
+
689
+ // The word under a point in view coordinates, or null for blank space,
690
+ // including the space past the end of a line.
691
+ private fun wordAt(x: Float, y: Float): WordPosition? {
692
+ val currentLayout = layout ?: return null
693
+ val line = currentLayout.getLineForVertical((y - totalPaddingTop + scrollY).toInt())
694
+ val lineX = x - totalPaddingLeft + scrollX
695
+ if (lineX < currentLayout.getLineLeft(line) || lineX > currentLayout.getLineRight(line)) {
696
+ return null
697
+ }
698
+ val offset = getOffsetForPosition(x, y)
699
+ return wordPositions.find { offset >= it.start && offset <= it.end }
700
+ }
701
+
702
+ // First and last word text[start, end) touches, or -1 to -1 for none.
703
+ private fun wordIndexRange(start: Int, end: Int): Pair<Int, Int> {
704
+ val touched = wordPositions.filter { it.start < end && it.end > start }
705
+ if (touched.isEmpty()) return -1 to -1
706
+ return touched.first().index to touched.last().index
567
707
  }
568
708
 
569
709
  fun clearSelection() {
@@ -161,9 +161,16 @@ class AdvancedTextViewManager : SimpleViewManager<AdvancedTextView>() {
161
161
  view?.setAdvancedLetterSpacing(letterSpacing)
162
162
  }
163
163
 
164
+ @ReactProp(name = "wordDoublePressEnabled")
165
+ fun setWordDoublePressEnabled(view: AdvancedTextView?, enabled: Boolean) {
166
+ view?.setWordDoublePressEnabled(enabled)
167
+ }
168
+
164
169
  override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any> {
165
170
  return mapOf(
166
171
  "onWordPress" to mapOf("registrationName" to "onWordPress"),
172
+ "onWordDoublePress" to mapOf("registrationName" to "onWordDoublePress"),
173
+ "onWordLongPress" to mapOf("registrationName" to "onWordLongPress"),
167
174
  "onSelection" to mapOf("registrationName" to "onSelection")
168
175
  )
169
176
  }
@@ -91,6 +91,11 @@ static UIFont *ATResolveFont(NSString *family, CGFloat size)
91
91
  @interface AdvancedTextView () <RCTAdvancedTextViewViewProtocol, UIGestureRecognizerDelegate, UITextViewDelegate>
92
92
 
93
93
  @property (nonatomic, strong) CustomTextView *textView;
94
+ @property (nonatomic, strong) UITapGestureRecognizer *singleTapGesture;
95
+ @property (nonatomic, strong) UITapGestureRecognizer *doubleTapGesture;
96
+ @property (nonatomic, strong) UILongPressGestureRecognizer *longPressGesture;
97
+ // On while the app handles double taps (onWordDoublePress).
98
+ @property (nonatomic, assign) BOOL wordDoublePressEnabled;
94
99
 
95
100
  @end
96
101
 
@@ -177,6 +182,24 @@ static UIFont *ATResolveFont(NSString *family, CGFloat size)
177
182
  action:@selector(handleTap:)];
178
183
  tapGesture.delegate = self;
179
184
  [_textView addGestureRecognizer:tapGesture];
185
+ _singleTapGesture = tapGesture;
186
+
187
+ // Off until the app handles double taps, so taps aren't delayed.
188
+ UITapGestureRecognizer *doubleTapGesture = [[UITapGestureRecognizer alloc]
189
+ initWithTarget:self
190
+ action:@selector(handleDoubleTap:)];
191
+ doubleTapGesture.numberOfTapsRequired = 2;
192
+ doubleTapGesture.delegate = self;
193
+ doubleTapGesture.enabled = NO;
194
+ [_textView addGestureRecognizer:doubleTapGesture];
195
+ _doubleTapGesture = doubleTapGesture;
196
+
197
+ UILongPressGestureRecognizer *longPressGesture = [[UILongPressGestureRecognizer alloc]
198
+ initWithTarget:self
199
+ action:@selector(handleLongPress:)];
200
+ longPressGesture.delegate = self;
201
+ [_textView addGestureRecognizer:longPressGesture];
202
+ _longPressGesture = longPressGesture;
180
203
 
181
204
  NSLog(@"[AdvancedTextView] Gesture recognizers setup successful");
182
205
  } @catch (NSException *exception) {
@@ -228,6 +251,11 @@ static UIFont *ATResolveFont(NSString *family, CGFloat size)
228
251
  styleChanged = YES;
229
252
  }
230
253
 
254
+ if (oldViewProps.wordDoublePressEnabled != newViewProps.wordDoublePressEnabled) {
255
+ _wordDoublePressEnabled = newViewProps.wordDoublePressEnabled;
256
+ _doubleTapGesture.enabled = _wordDoublePressEnabled;
257
+ }
258
+
231
259
  if (oldViewProps.text != newViewProps.text) {
232
260
  textChanged = YES;
233
261
  NSLog(@"[AdvancedTextView] Text changed");
@@ -693,6 +721,89 @@ static UIFont *ATResolveFont(NSString *family, CGFloat size)
693
721
  }
694
722
  }
695
723
 
724
+ - (void)handleDoubleTap:(UITapGestureRecognizer *)gesture
725
+ {
726
+ @try {
727
+ if (gesture.state != UIGestureRecognizerStateEnded) return;
728
+
729
+ // In case the text view selected anything before giving way.
730
+ _textView.selectedTextRange = nil;
731
+
732
+ NSInteger wordIndex = [self wordIndexAtPoint:[gesture locationInView:_textView]];
733
+ if (wordIndex >= 0 && wordIndex < _wordRanges.count) {
734
+ [self emitWordEvent:@"doublePress" word:_wordRanges[wordIndex][@"word"] index:wordIndex];
735
+ }
736
+ } @catch (NSException *exception) {
737
+ NSLog(@"[AdvancedTextView] Exception in handleDoubleTap: %@", exception);
738
+ }
739
+ }
740
+
741
+ // Only reports the word: the text view's own long press still starts a selection.
742
+ - (void)handleLongPress:(UILongPressGestureRecognizer *)gesture
743
+ {
744
+ @try {
745
+ if (gesture.state != UIGestureRecognizerStateBegan) return;
746
+
747
+ NSInteger wordIndex = [self wordIndexAtPoint:[gesture locationInView:_textView]];
748
+ if (wordIndex >= 0 && wordIndex < _wordRanges.count) {
749
+ [self emitWordEvent:@"longPress" word:_wordRanges[wordIndex][@"word"] index:wordIndex];
750
+ }
751
+ } @catch (NSException *exception) {
752
+ NSLog(@"[AdvancedTextView] Exception in handleLongPress: %@", exception);
753
+ }
754
+ }
755
+
756
+ #pragma mark - UIGestureRecognizerDelegate
757
+
758
+ // While double taps are handled, a single tap waits to see whether a second
759
+ // one follows, so a double tap doesn't also fire onWordPress.
760
+ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
761
+ shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
762
+ {
763
+ return _wordDoublePressEnabled &&
764
+ gestureRecognizer == _singleTapGesture &&
765
+ otherGestureRecognizer == _doubleTapGesture;
766
+ }
767
+
768
+ // The text view's own double tap selects the word. It waits for ours, which
769
+ // wins on every double tap, so a double tap never selects text.
770
+ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
771
+ shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
772
+ {
773
+ if (!_wordDoublePressEnabled || gestureRecognizer != _doubleTapGesture) {
774
+ return NO;
775
+ }
776
+ if (![otherGestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
777
+ return NO;
778
+ }
779
+ return ((UITapGestureRecognizer *)otherGestureRecognizer).numberOfTapsRequired >= 2;
780
+ }
781
+
782
+ // The long press runs alongside the text view's own, which starts the selection.
783
+ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
784
+ shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
785
+ {
786
+ return gestureRecognizer == _longPressGesture || otherGestureRecognizer == _longPressGesture;
787
+ }
788
+
789
+ // The first and last word a character range touches, or -1 for none.
790
+ - (void)wordIndexesForRange:(NSRange)range first:(NSInteger *)first last:(NSInteger *)last
791
+ {
792
+ *first = -1;
793
+ *last = -1;
794
+ for (NSDictionary *wordInfo in _wordRanges) {
795
+ NSRange wordRange = [wordInfo[@"range"] rangeValue];
796
+ if (NSIntersectionRange(wordRange, range).length == 0) {
797
+ continue;
798
+ }
799
+ NSInteger index = [wordInfo[@"index"] integerValue];
800
+ if (*first < 0) {
801
+ *first = index;
802
+ }
803
+ *last = index;
804
+ }
805
+ }
806
+
696
807
  #pragma mark - UITextViewDelegate
697
808
 
698
809
  - (void)textViewDidChangeSelection:(UITextView *)textView
@@ -815,6 +926,29 @@ static UIFont *ATResolveFont(NSString *family, CGFloat size)
815
926
  }
816
927
  }
817
928
 
929
+ // onWordDoublePress ("doublePress") or onWordLongPress ("longPress").
930
+ - (void)emitWordEvent:(NSString *)type word:(NSString *)word index:(NSInteger)index
931
+ {
932
+ @try {
933
+ if (!_eventEmitter) return;
934
+ auto emitter = std::static_pointer_cast<const AdvancedTextViewEventEmitter>(_eventEmitter);
935
+
936
+ if ([type isEqualToString:@"doublePress"]) {
937
+ AdvancedTextViewEventEmitter::OnWordDoublePress event;
938
+ event.word = [word UTF8String];
939
+ event.index = static_cast<int>(index);
940
+ emitter->onWordDoublePress(event);
941
+ } else {
942
+ AdvancedTextViewEventEmitter::OnWordLongPress event;
943
+ event.word = [word UTF8String];
944
+ event.index = static_cast<int>(index);
945
+ emitter->onWordLongPress(event);
946
+ }
947
+ } @catch (NSException *exception) {
948
+ NSLog(@"[AdvancedTextView] Exception in emitWordEvent: %@", exception);
949
+ }
950
+ }
951
+
818
952
  - (void)emitSelectionEvent:(NSString *)selectedText menuOption:(NSString *)option
819
953
  {
820
954
  NSLog(@"[AdvancedTextView] emitSelectionEvent: %@ with option: %@", selectedText, option);
@@ -822,9 +956,15 @@ static UIFont *ATResolveFont(NSString *family, CGFloat size)
822
956
  if (_eventEmitter) {
823
957
  auto emitter = std::static_pointer_cast<const AdvancedTextViewEventEmitter>(_eventEmitter);
824
958
 
959
+ NSInteger firstWord = -1;
960
+ NSInteger lastWord = -1;
961
+ [self wordIndexesForRange:_textView.selectedRange first:&firstWord last:&lastWord];
962
+
825
963
  AdvancedTextViewEventEmitter::OnSelection event;
826
964
  event.selectedText = [selectedText UTF8String];
827
965
  event.event = [option UTF8String];
966
+ event.startWordIndex = static_cast<int>(firstWord);
967
+ event.endWordIndex = static_cast<int>(lastWord);
828
968
 
829
969
  emitter->onSelection(event);
830
970
  }
@@ -9,6 +9,8 @@ export const AdvancedText = ({
9
9
  highlightedWords,
10
10
  menuOptions,
11
11
  onWordPress,
12
+ onWordDoublePress,
13
+ onWordLongPress,
12
14
  onSelection,
13
15
  ...restProps
14
16
  }) => {
@@ -19,7 +21,12 @@ export const AdvancedText = ({
19
21
  highlightedWords: highlightedWords,
20
22
  menuOptions: menuOptions,
21
23
  onWordPress: onWordPress,
24
+ onWordDoublePress: onWordDoublePress,
25
+ onWordLongPress: onWordLongPress,
22
26
  onSelection: onSelection
27
+ // Only apps that handle double taps pay the single-tap delay.
28
+ ,
29
+ wordDoublePressEnabled: !!onWordDoublePress
23
30
  });
24
31
  };
25
32
  //# sourceMappingURL=AdvancedText.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["React","AdvancedTextViewNativeComponent","jsx","_jsx","AdvancedText","text","style","highlightedWords","menuOptions","onWordPress","onSelection","restProps"],"sourceRoot":"../../src","sources":["AdvancedText.tsx"],"mappings":";;AAAA,OAAOA,KAAK,MAAM,OAAO;AACzB,OAAOC,+BAA+B,MAE/B,mCAAmC;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAE3C,OAAO,MAAMC,YAAmC,GAAGA,CAAC;EAClDC,IAAI;EACJC,KAAK;EACLC,gBAAgB;EAChBC,WAAW;EACXC,WAAW;EACXC,WAAW;EACX,GAAGC;AACL,CAAC,KAAK;EACJ,oBACER,IAAA,CAACF,+BAA+B;IAAA,GAC1BU,SAAS;IACbL,KAAK,EAAEA,KAAM;IACbD,IAAI,EAAEA,IAAK;IACXE,gBAAgB,EAAEA,gBAAiB;IACnCC,WAAW,EAAEA,WAAY;IACzBC,WAAW,EAAEA,WAAY;IACzBC,WAAW,EAAEA;EAAY,CAC1B,CAAC;AAEN,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["React","AdvancedTextViewNativeComponent","jsx","_jsx","AdvancedText","text","style","highlightedWords","menuOptions","onWordPress","onWordDoublePress","onWordLongPress","onSelection","restProps","wordDoublePressEnabled"],"sourceRoot":"../../src","sources":["AdvancedText.tsx"],"mappings":";;AAAA,OAAOA,KAAK,MAAM,OAAO;AACzB,OAAOC,+BAA+B,MAE/B,mCAAmC;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAE3C,OAAO,MAAMC,YAAmC,GAAGA,CAAC;EAClDC,IAAI;EACJC,KAAK;EACLC,gBAAgB;EAChBC,WAAW;EACXC,WAAW;EACXC,iBAAiB;EACjBC,eAAe;EACfC,WAAW;EACX,GAAGC;AACL,CAAC,KAAK;EACJ,oBACEV,IAAA,CAACF,+BAA+B;IAAA,GAC1BY,SAAS;IACbP,KAAK,EAAEA,KAAM;IACbD,IAAI,EAAEA,IAAK;IACXE,gBAAgB,EAAEA,gBAAiB;IACnCC,WAAW,EAAEA,WAAY;IACzBC,WAAW,EAAEA,WAAY;IACzBC,iBAAiB,EAAEA,iBAAkB;IACrCC,eAAe,EAAEA,eAAgB;IACjCC,WAAW,EAAEA;IACb;IAAA;IACAE,sBAAsB,EAAE,CAAC,CAACJ;EAAkB,CAC7C,CAAC;AAEN,CAAC","ignoreList":[]}
@@ -18,7 +18,21 @@ export interface NativeProps extends ViewProps {
18
18
  highlightedWords?: ReadonlyArray<HighlightedWord>;
19
19
  menuOptions?: ReadonlyArray<string>;
20
20
  onWordPress?: DirectEventHandler<{ word: string; index: Int32 }>;
21
- onSelection?: DirectEventHandler<{ selectedText: string; event: string }>;
21
+ // A double tap on a word. It never selects text.
22
+ onWordDoublePress?: DirectEventHandler<{ word: string; index: Int32 }>;
23
+ // A long press on a word. Text selection still starts as usual.
24
+ onWordLongPress?: DirectEventHandler<{ word: string; index: Int32 }>;
25
+ // startWordIndex/endWordIndex: the first and last word the selection
26
+ // touches, or -1 when it touches none.
27
+ onSelection?: DirectEventHandler<{
28
+ selectedText: string;
29
+ event: string;
30
+ startWordIndex: Int32;
31
+ endWordIndex: Int32;
32
+ }>;
33
+ // Set by AdvancedText when onWordDoublePress is given. While on, a single
34
+ // tap waits out the double-tap delay before firing onWordPress.
35
+ wordDoublePressEnabled?: boolean;
22
36
  fontSize?: Int32;
23
37
  fontWeight?: string;
24
38
  color?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"AdvancedText.d.ts","sourceRoot":"","sources":["../../../src/AdvancedText.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAwC,EACtC,KAAK,WAAW,EACjB,MAAM,mCAAmC,CAAC;AAE3C,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,CAoB9C,CAAC"}
1
+ {"version":3,"file":"AdvancedText.d.ts","sourceRoot":"","sources":["../../../src/AdvancedText.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAwC,EACtC,KAAK,WAAW,EACjB,MAAM,mCAAmC,CAAC;AAE3C,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,CA0B9C,CAAC"}
@@ -14,10 +14,21 @@ export interface NativeProps extends ViewProps {
14
14
  word: string;
15
15
  index: Int32;
16
16
  }>;
17
+ onWordDoublePress?: DirectEventHandler<{
18
+ word: string;
19
+ index: Int32;
20
+ }>;
21
+ onWordLongPress?: DirectEventHandler<{
22
+ word: string;
23
+ index: Int32;
24
+ }>;
17
25
  onSelection?: DirectEventHandler<{
18
26
  selectedText: string;
19
27
  event: string;
28
+ startWordIndex: Int32;
29
+ endWordIndex: Int32;
20
30
  }>;
31
+ wordDoublePressEnabled?: boolean;
21
32
  fontSize?: Int32;
22
33
  fontWeight?: string;
23
34
  color?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"AdvancedTextViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../src/AdvancedTextViewNativeComponent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAG9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAG,KAAK,EAAG,MAAM,2CAA2C,CAAC;AAEpG,UAAU,eAAe;IACvB,KAAK,EAAE,KAAK,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,KAAK,CAAC;IAGrB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAClD,WAAW,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACpC,WAAW,CAAC,EAAE,kBAAkB,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,CAAC,CAAC;IACjE,WAAW,CAAC,EAAE,kBAAkB,CAAC;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1E,QAAQ,CAAC,EAAE,KAAK,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,KAAK,CAAC;IACnB,aAAa,CAAC,EAAE,KAAK,CAAC;IACtB,kBAAkB,CAAC,EAAE,KAAK,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;;AAED,wBAAuE"}
1
+ {"version":3,"file":"AdvancedTextViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../src/AdvancedTextViewNativeComponent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAG9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAG,KAAK,EAAG,MAAM,2CAA2C,CAAC;AAEpG,UAAU,eAAe;IACvB,KAAK,EAAE,KAAK,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,KAAK,CAAC;IAGrB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAClD,WAAW,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACpC,WAAW,CAAC,EAAE,kBAAkB,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,CAAC,CAAC;IAEjE,iBAAiB,CAAC,EAAE,kBAAkB,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,CAAC,CAAC;IAEvE,eAAe,CAAC,EAAE,kBAAkB,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,CAAC,CAAC;IAGrE,WAAW,CAAC,EAAE,kBAAkB,CAAC;QAC/B,YAAY,EAAE,MAAM,CAAC;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,cAAc,EAAE,KAAK,CAAC;QACtB,YAAY,EAAE,KAAK,CAAC;KACrB,CAAC,CAAC;IAGH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,EAAE,KAAK,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,KAAK,CAAC;IACnB,aAAa,CAAC,EAAE,KAAK,CAAC;IACtB,kBAAkB,CAAC,EAAE,KAAK,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;;AAED,wBAAuE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-advanced-text",
3
- "version": "0.2.16",
3
+ "version": "0.2.17",
4
4
  "description": " Advanced text component for React Native with custom select options.",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -9,6 +9,8 @@ export const AdvancedText: React.FC<NativeProps> = ({
9
9
  highlightedWords,
10
10
  menuOptions,
11
11
  onWordPress,
12
+ onWordDoublePress,
13
+ onWordLongPress,
12
14
  onSelection,
13
15
  ...restProps
14
16
  }) => {
@@ -20,7 +22,11 @@ export const AdvancedText: React.FC<NativeProps> = ({
20
22
  highlightedWords={highlightedWords}
21
23
  menuOptions={menuOptions}
22
24
  onWordPress={onWordPress}
25
+ onWordDoublePress={onWordDoublePress}
26
+ onWordLongPress={onWordLongPress}
23
27
  onSelection={onSelection}
28
+ // Only apps that handle double taps pay the single-tap delay.
29
+ wordDoublePressEnabled={!!onWordDoublePress}
24
30
  />
25
31
  );
26
32
  };
@@ -18,7 +18,21 @@ export interface NativeProps extends ViewProps {
18
18
  highlightedWords?: ReadonlyArray<HighlightedWord>;
19
19
  menuOptions?: ReadonlyArray<string>;
20
20
  onWordPress?: DirectEventHandler<{ word: string; index: Int32 }>;
21
- onSelection?: DirectEventHandler<{ selectedText: string; event: string }>;
21
+ // A double tap on a word. It never selects text.
22
+ onWordDoublePress?: DirectEventHandler<{ word: string; index: Int32 }>;
23
+ // A long press on a word. Text selection still starts as usual.
24
+ onWordLongPress?: DirectEventHandler<{ word: string; index: Int32 }>;
25
+ // startWordIndex/endWordIndex: the first and last word the selection
26
+ // touches, or -1 when it touches none.
27
+ onSelection?: DirectEventHandler<{
28
+ selectedText: string;
29
+ event: string;
30
+ startWordIndex: Int32;
31
+ endWordIndex: Int32;
32
+ }>;
33
+ // Set by AdvancedText when onWordDoublePress is given. While on, a single
34
+ // tap waits out the double-tap delay before firing onWordPress.
35
+ wordDoublePressEnabled?: boolean;
22
36
  fontSize?: Int32;
23
37
  fontWeight?: string;
24
38
  color?: string;