react-native-advanced-text 0.1.28 → 0.1.29
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
|
@@ -1,37 +1,56 @@
|
|
|
1
|
-
# react-native-advanced-text
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
```sh
|
|
9
|
-
npm install react-native-advanced-text
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
## Usage
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
```js
|
|
17
|
-
import { AdvancedTextView } from "react-native-advanced-text";
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
1
|
+
# react-native-advanced-text
|
|
2
|
+
|
|
3
|
+
react-native-advanced-text is a powerful cross-platform text component for React Native that enables word-level interaction, dynamic highlighting, and custom selection actions.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
npm install react-native-advanced-text
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
import { AdvancedTextView } from "react-native-advanced-text";
|
|
18
|
+
|
|
19
|
+
<AdvancedText
|
|
20
|
+
text={'This is an example of AdvancedText component. Tap on any word to see the event in action.'}
|
|
21
|
+
style={[styles.AdvancedText, { minHeight }]}
|
|
22
|
+
indicatorWordIndex={2}
|
|
23
|
+
onWordPress={(event) => {
|
|
24
|
+
console.log({event})
|
|
25
|
+
}}
|
|
26
|
+
menuOptions={['Highlight', 'Copy', 'Translate']}
|
|
27
|
+
onSelection={(event) => {
|
|
28
|
+
console.log({event})
|
|
29
|
+
}}
|
|
30
|
+
highlightedWords={[
|
|
31
|
+
{
|
|
32
|
+
index: 4,
|
|
33
|
+
highlightColor: '#6baeffb5',
|
|
34
|
+
},
|
|
35
|
+
]}
|
|
36
|
+
fontSize={24}
|
|
37
|
+
color={'#FFFFFF'}
|
|
38
|
+
fontWeight="normal"
|
|
39
|
+
fontFamily={'monospace'}
|
|
40
|
+
/>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
## Contributing
|
|
45
|
+
|
|
46
|
+
- [Development workflow](CONTRIBUTING.md#development-workflow)
|
|
47
|
+
- [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
|
|
48
|
+
- [Code of conduct](CODE_OF_CONDUCT.md)
|
|
49
|
+
|
|
50
|
+
## License
|
|
51
|
+
|
|
52
|
+
MIT
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
|
|
@@ -2,10 +2,12 @@ package com.advancedtext
|
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import android.graphics.Color
|
|
5
|
+
import android.graphics.Point
|
|
5
6
|
import android.text.SpannableString
|
|
7
|
+
import android.text.Spannable
|
|
6
8
|
import android.text.Spanned
|
|
7
9
|
import android.text.TextPaint
|
|
8
|
-
import android.text.method.
|
|
10
|
+
import android.text.method.ArrowKeyMovementMethod
|
|
9
11
|
import android.text.style.ClickableSpan
|
|
10
12
|
import android.text.style.BackgroundColorSpan
|
|
11
13
|
import android.text.style.ForegroundColorSpan
|
|
@@ -15,12 +17,14 @@ import android.view.ActionMode
|
|
|
15
17
|
import android.view.Menu
|
|
16
18
|
import android.view.MenuItem
|
|
17
19
|
import android.view.View
|
|
20
|
+
import android.view.MotionEvent
|
|
18
21
|
import android.widget.TextView
|
|
19
22
|
import com.facebook.react.bridge.Arguments
|
|
20
23
|
import com.facebook.react.bridge.ReactContext
|
|
21
24
|
import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
22
25
|
import android.text.Selection
|
|
23
26
|
import android.graphics.Typeface
|
|
27
|
+
import androidx.core.text.getSpans
|
|
24
28
|
|
|
25
29
|
class AdvancedTextView : TextView {
|
|
26
30
|
|
|
@@ -49,9 +53,9 @@ class AdvancedTextView : TextView {
|
|
|
49
53
|
|
|
50
54
|
textSize = 16f
|
|
51
55
|
setPadding(16, 16, 16, 16)
|
|
52
|
-
movementMethod = LinkMovementMethod.getInstance()
|
|
53
56
|
setTextIsSelectable(true)
|
|
54
57
|
|
|
58
|
+
movementMethod = SmartMovementMethod
|
|
55
59
|
|
|
56
60
|
customSelectionActionModeCallback = object : ActionMode.Callback {
|
|
57
61
|
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
|
@@ -94,8 +98,6 @@ class AdvancedTextView : TextView {
|
|
|
94
98
|
}
|
|
95
99
|
}
|
|
96
100
|
|
|
97
|
-
|
|
98
|
-
|
|
99
101
|
fun setAdvancedText(text: String) {
|
|
100
102
|
if (currentText == text) {
|
|
101
103
|
Log.d(TAG, "Text unchanged, skipping update")
|
|
@@ -113,7 +115,6 @@ class AdvancedTextView : TextView {
|
|
|
113
115
|
updateTextWithHighlights()
|
|
114
116
|
}
|
|
115
117
|
|
|
116
|
-
|
|
117
118
|
fun setAdvancedTextSize(size: Float) {
|
|
118
119
|
if (fontSize == size) return
|
|
119
120
|
fontSize = size
|
|
@@ -210,6 +211,7 @@ class AdvancedTextView : TextView {
|
|
|
210
211
|
)
|
|
211
212
|
}
|
|
212
213
|
|
|
214
|
+
// Add clickable span for word clicks
|
|
213
215
|
spannableString.setSpan(
|
|
214
216
|
WordClickableSpan(wordPos.index, wordPos.word),
|
|
215
217
|
wordPos.start,
|
|
@@ -264,16 +266,13 @@ class AdvancedTextView : TextView {
|
|
|
264
266
|
}
|
|
265
267
|
|
|
266
268
|
private inner class WordClickableSpan(
|
|
267
|
-
|
|
268
|
-
|
|
269
|
+
private val wordIndex: Int,
|
|
270
|
+
private val word: String
|
|
269
271
|
) : ClickableSpan() {
|
|
270
272
|
|
|
271
273
|
override fun onClick(widget: View) {
|
|
272
|
-
Log.d(TAG, "
|
|
273
|
-
|
|
274
|
-
widget.post {
|
|
275
|
-
sendWordPressEvent(word, wordIndex)
|
|
276
|
-
}
|
|
274
|
+
Log.d(TAG, "Word clicked: '$word' (index=$wordIndex)")
|
|
275
|
+
sendWordPressEvent(word, wordIndex)
|
|
277
276
|
}
|
|
278
277
|
|
|
279
278
|
override fun updateDrawState(ds: TextPaint) {
|
|
@@ -304,6 +303,56 @@ class AdvancedTextView : TextView {
|
|
|
304
303
|
}
|
|
305
304
|
}
|
|
306
305
|
|
|
306
|
+
|
|
307
|
+
private object SmartMovementMethod : ArrowKeyMovementMethod() {
|
|
308
|
+
|
|
309
|
+
override fun onTouchEvent(widget: TextView?, buffer: Spannable?, event: MotionEvent?): Boolean {
|
|
310
|
+
if (event != null && widget != null && buffer != null) {
|
|
311
|
+
if (handleMotion(event, widget, buffer)) {
|
|
312
|
+
return true
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return super.onTouchEvent(widget, buffer, event)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private fun handleMotion(event: MotionEvent, widget: TextView, buffer: Spannable): Boolean {
|
|
319
|
+
var handled = false
|
|
320
|
+
|
|
321
|
+
if (event.action == MotionEvent.ACTION_DOWN || event.action == MotionEvent.ACTION_UP) {
|
|
322
|
+
val target = Point().apply {
|
|
323
|
+
x = event.x.toInt() - widget.totalPaddingLeft + widget.scrollX
|
|
324
|
+
y = event.y.toInt() - widget.totalPaddingTop + widget.scrollY
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
val line = widget.layout.getLineForVertical(target.y)
|
|
328
|
+
val offset = widget.layout.getOffsetForHorizontal(line, target.x.toFloat())
|
|
329
|
+
|
|
330
|
+
if (event.action == MotionEvent.ACTION_DOWN) {
|
|
331
|
+
handled = handled || buffer.execute<ClickableSpan>(offset) {
|
|
332
|
+
Selection.setSelection(buffer, buffer.getSpanStart(it), buffer.getSpanEnd(it))
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (event.action == MotionEvent.ACTION_UP) {
|
|
337
|
+
handled = handled || buffer.execute<ClickableSpan>(offset) {
|
|
338
|
+
it.onClick(widget)
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return handled
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
private inline fun <reified T : Any> Spannable.execute(offset: Int, fn: (T) -> Unit): Boolean {
|
|
347
|
+
val spans = this.getSpans<T>(offset, offset)
|
|
348
|
+
if (spans.isNotEmpty()) {
|
|
349
|
+
spans.forEach(fn)
|
|
350
|
+
return true
|
|
351
|
+
}
|
|
352
|
+
return false
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
307
356
|
data class WordPosition(
|
|
308
357
|
val index: Int,
|
|
309
358
|
val start: Int,
|
package/package.json
CHANGED