react-native-enriched 0.1.4 → 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/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +31 -18
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewLayoutManager.kt +12 -2
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBlockQuoteSpan.kt +6 -2
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedCodeBlockSpan.kt +9 -5
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedInlineCodeSpan.kt +10 -5
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedOrderedListSpan.kt +11 -1
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnorderedListSpan.kt +11 -1
- package/android/src/main/java/com/swmansion/enriched/styles/ListStyles.kt +1 -2
- package/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt +11 -10
- package/ios/EnrichedTextInputView.mm +8 -3
- package/package.json +3 -2
|
@@ -7,6 +7,7 @@ import android.graphics.BlendMode
|
|
|
7
7
|
import android.graphics.BlendModeColorFilter
|
|
8
8
|
import android.graphics.Color
|
|
9
9
|
import android.graphics.Rect
|
|
10
|
+
import android.graphics.text.LineBreaker
|
|
10
11
|
import android.os.Build
|
|
11
12
|
import android.text.InputType
|
|
12
13
|
import android.text.Spannable
|
|
@@ -51,9 +52,6 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
51
52
|
val paragraphStyles: ParagraphStyles? = ParagraphStyles(this)
|
|
52
53
|
val listStyles: ListStyles? = ListStyles(this)
|
|
53
54
|
val parametrizedStyles: ParametrizedStyles? = ParametrizedStyles(this)
|
|
54
|
-
// Sometimes setting up style triggers many changes in sequence
|
|
55
|
-
// Eg. removing conflicting styles -> changing text -> applying spans
|
|
56
|
-
// In such scenario we want to prevent from handling side effects (eg. onTextChanged)
|
|
57
55
|
var isDuringTransaction: Boolean = false
|
|
58
56
|
var isRemovingMany: Boolean = false
|
|
59
57
|
|
|
@@ -102,6 +100,10 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
102
100
|
gravity = Gravity.TOP or Gravity.START
|
|
103
101
|
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
|
104
102
|
|
|
103
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
104
|
+
breakStrategy = LineBreaker.BREAK_STRATEGY_HIGH_QUALITY
|
|
105
|
+
}
|
|
106
|
+
|
|
105
107
|
setPadding(0, 0, 0, 0)
|
|
106
108
|
setBackgroundColor(Color.TRANSPARENT)
|
|
107
109
|
|
|
@@ -236,18 +238,17 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
236
238
|
|
|
237
239
|
fun setValue(value: CharSequence?) {
|
|
238
240
|
if (value == null) return
|
|
239
|
-
isDuringTransaction = true
|
|
240
241
|
|
|
241
|
-
|
|
242
|
-
|
|
242
|
+
runAsATransaction {
|
|
243
|
+
val newText = parseText(value)
|
|
244
|
+
setText(newText)
|
|
243
245
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
// Scroll to the last line of text
|
|
248
|
-
setSelection(text?.length ?: 0)
|
|
246
|
+
// Assign SpanWatcher one more time as our previous spannable has been replaced
|
|
247
|
+
addSpanWatcher(EnrichedSpanWatcher(this))
|
|
249
248
|
|
|
250
|
-
|
|
249
|
+
// Scroll to the last line of text
|
|
250
|
+
setSelection(text?.length ?: 0)
|
|
251
|
+
}
|
|
251
252
|
}
|
|
252
253
|
|
|
253
254
|
fun setAutoFocus(autoFocus: Boolean) {
|
|
@@ -455,13 +456,13 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
455
456
|
val end = selection?.end ?: 0
|
|
456
457
|
val lengthBefore = text?.length ?: 0
|
|
457
458
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
459
|
+
runAsATransaction {
|
|
460
|
+
val targetRange = getTargetRange(name)
|
|
461
|
+
val removed = removeStyle(style, targetRange.first, targetRange.second)
|
|
462
|
+
if (removed) {
|
|
463
|
+
spanState?.setStart(style, null)
|
|
464
|
+
}
|
|
463
465
|
}
|
|
464
|
-
isDuringTransaction = false
|
|
465
466
|
|
|
466
467
|
val lengthAfter = text?.length ?: 0
|
|
467
468
|
val charactersRemoved = lengthBefore - lengthAfter
|
|
@@ -519,6 +520,18 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
519
520
|
parametrizedStyles?.setMentionSpan(text, indicator, attributes)
|
|
520
521
|
}
|
|
521
522
|
|
|
523
|
+
// Sometimes setting up style triggers many changes in sequence
|
|
524
|
+
// Eg. removing conflicting styles -> changing text -> applying spans
|
|
525
|
+
// In such scenario we want to prevent from handling side effects (eg. onTextChanged)
|
|
526
|
+
fun runAsATransaction(block: () -> Unit) {
|
|
527
|
+
try {
|
|
528
|
+
isDuringTransaction = true
|
|
529
|
+
block()
|
|
530
|
+
} finally {
|
|
531
|
+
isDuringTransaction = false
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
522
535
|
override fun onAttachedToWindow() {
|
|
523
536
|
super.onAttachedToWindow()
|
|
524
537
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
package com.swmansion.enriched
|
|
2
2
|
|
|
3
|
+
import android.graphics.text.LineBreaker
|
|
4
|
+
import android.os.Build
|
|
3
5
|
import android.text.Editable
|
|
4
6
|
import android.text.StaticLayout
|
|
5
7
|
import com.facebook.react.bridge.Arguments
|
|
@@ -50,12 +52,20 @@ class EnrichedTextInputViewLayoutManager(private val view: EnrichedTextInputView
|
|
|
50
52
|
val paint = view.paint
|
|
51
53
|
val textLength = text.length
|
|
52
54
|
|
|
53
|
-
val
|
|
55
|
+
val builder = StaticLayout.Builder
|
|
54
56
|
.obtain(text, 0, textLength, paint, maxWidth.toInt())
|
|
55
57
|
.setIncludePad(true)
|
|
56
58
|
.setLineSpacing(0f, 1f)
|
|
57
|
-
.build()
|
|
58
59
|
|
|
60
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
61
|
+
builder.setBreakStrategy(LineBreaker.BREAK_STRATEGY_HIGH_QUALITY)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
65
|
+
builder.setUseLineSpacingFromFallbacks(true)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
val staticLayout = builder.build()
|
|
59
69
|
val heightInSP = PixelUtil.toDIPFromPixel(staticLayout.height.toFloat())
|
|
60
70
|
val widthInSP = PixelUtil.toDIPFromPixel(maxWidth)
|
|
61
71
|
|
|
@@ -4,13 +4,17 @@ import android.graphics.Canvas
|
|
|
4
4
|
import android.graphics.Paint
|
|
5
5
|
import android.text.Layout
|
|
6
6
|
import android.text.TextPaint
|
|
7
|
-
import android.text.style.CharacterStyle
|
|
8
7
|
import android.text.style.LeadingMarginSpan
|
|
8
|
+
import android.text.style.MetricAffectingSpan
|
|
9
9
|
import com.swmansion.enriched.spans.interfaces.EnrichedBlockSpan
|
|
10
10
|
import com.swmansion.enriched.styles.HtmlStyle
|
|
11
11
|
|
|
12
12
|
// https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/java/android/text/style/QuoteSpan.java
|
|
13
|
-
class EnrichedBlockQuoteSpan(private val htmlStyle: HtmlStyle) :
|
|
13
|
+
class EnrichedBlockQuoteSpan(private val htmlStyle: HtmlStyle) : MetricAffectingSpan(), LeadingMarginSpan, EnrichedBlockSpan {
|
|
14
|
+
override fun updateMeasureState(p0: TextPaint) {
|
|
15
|
+
// Do nothing, but inform layout that this span affects text metrics
|
|
16
|
+
}
|
|
17
|
+
|
|
14
18
|
override fun getLeadingMargin(p0: Boolean): Int {
|
|
15
19
|
return htmlStyle.blockquoteStripeWidth + htmlStyle.blockquoteGapWidth
|
|
16
20
|
}
|
|
@@ -5,15 +5,19 @@ import android.graphics.Paint
|
|
|
5
5
|
import android.graphics.RectF
|
|
6
6
|
import android.graphics.Typeface
|
|
7
7
|
import android.text.TextPaint
|
|
8
|
-
import android.text.style.CharacterStyle
|
|
9
8
|
import android.text.style.LineBackgroundSpan
|
|
9
|
+
import android.text.style.MetricAffectingSpan
|
|
10
10
|
import com.swmansion.enriched.spans.interfaces.EnrichedBlockSpan
|
|
11
11
|
import com.swmansion.enriched.styles.HtmlStyle
|
|
12
12
|
|
|
13
|
-
class EnrichedCodeBlockSpan(private val htmlStyle: HtmlStyle) :
|
|
14
|
-
override fun updateDrawState(paint: TextPaint
|
|
15
|
-
paint
|
|
16
|
-
paint
|
|
13
|
+
class EnrichedCodeBlockSpan(private val htmlStyle: HtmlStyle) : MetricAffectingSpan(), LineBackgroundSpan, EnrichedBlockSpan {
|
|
14
|
+
override fun updateDrawState(paint: TextPaint) {
|
|
15
|
+
paint.typeface = Typeface.MONOSPACE
|
|
16
|
+
paint.color = htmlStyle.codeBlockColor
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
override fun updateMeasureState(paint: TextPaint) {
|
|
20
|
+
paint.typeface = Typeface.MONOSPACE
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
override fun drawBackground(
|
|
@@ -2,15 +2,20 @@ package com.swmansion.enriched.spans
|
|
|
2
2
|
|
|
3
3
|
import android.graphics.Typeface
|
|
4
4
|
import android.text.TextPaint
|
|
5
|
-
import android.text.style.
|
|
5
|
+
import android.text.style.MetricAffectingSpan
|
|
6
6
|
import com.swmansion.enriched.spans.interfaces.EnrichedInlineSpan
|
|
7
7
|
import com.swmansion.enriched.styles.HtmlStyle
|
|
8
8
|
|
|
9
|
-
class EnrichedInlineCodeSpan(private val htmlStyle: HtmlStyle) :
|
|
9
|
+
class EnrichedInlineCodeSpan(private val htmlStyle: HtmlStyle) : MetricAffectingSpan(), EnrichedInlineSpan {
|
|
10
10
|
override fun updateDrawState(textPaint: TextPaint) {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
val typeface = Typeface.create(Typeface.MONOSPACE, Typeface.NORMAL)
|
|
12
|
+
textPaint.typeface = typeface
|
|
13
13
|
textPaint.color = htmlStyle.inlineCodeColor
|
|
14
|
-
textPaint.
|
|
14
|
+
textPaint.bgColor = htmlStyle.inlineCodeBackgroundColor
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
override fun updateMeasureState(textPaint: TextPaint) {
|
|
18
|
+
val typeface = Typeface.create(Typeface.MONOSPACE, Typeface.NORMAL)
|
|
19
|
+
textPaint.typeface = typeface
|
|
15
20
|
}
|
|
16
21
|
}
|
|
@@ -4,11 +4,21 @@ import android.graphics.Canvas
|
|
|
4
4
|
import android.graphics.Paint
|
|
5
5
|
import android.graphics.Typeface
|
|
6
6
|
import android.text.Layout
|
|
7
|
+
import android.text.TextPaint
|
|
7
8
|
import android.text.style.LeadingMarginSpan
|
|
9
|
+
import android.text.style.MetricAffectingSpan
|
|
8
10
|
import com.swmansion.enriched.spans.interfaces.EnrichedParagraphSpan
|
|
9
11
|
import com.swmansion.enriched.styles.HtmlStyle
|
|
10
12
|
|
|
11
|
-
class EnrichedOrderedListSpan(private var index: Int, private val htmlStyle: HtmlStyle) : LeadingMarginSpan, EnrichedParagraphSpan {
|
|
13
|
+
class EnrichedOrderedListSpan(private var index: Int, private val htmlStyle: HtmlStyle) : MetricAffectingSpan(), LeadingMarginSpan, EnrichedParagraphSpan {
|
|
14
|
+
override fun updateMeasureState(p0: TextPaint) {
|
|
15
|
+
// Do nothing, but inform layout that this span affects text metrics
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
override fun updateDrawState(p0: TextPaint?) {
|
|
19
|
+
// Do nothing, but inform layout that this span affects text metrics
|
|
20
|
+
}
|
|
21
|
+
|
|
12
22
|
override fun getLeadingMargin(first: Boolean): Int {
|
|
13
23
|
return htmlStyle.olMarginLeft + htmlStyle.olGapWidth
|
|
14
24
|
}
|
|
@@ -4,12 +4,22 @@ import android.graphics.Canvas
|
|
|
4
4
|
import android.graphics.Paint
|
|
5
5
|
import android.text.Layout
|
|
6
6
|
import android.text.Spanned
|
|
7
|
+
import android.text.TextPaint
|
|
7
8
|
import android.text.style.LeadingMarginSpan
|
|
9
|
+
import android.text.style.MetricAffectingSpan
|
|
8
10
|
import com.swmansion.enriched.spans.interfaces.EnrichedParagraphSpan
|
|
9
11
|
import com.swmansion.enriched.styles.HtmlStyle
|
|
10
12
|
|
|
11
13
|
// https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/java/android/text/style/BulletSpan.java
|
|
12
|
-
class EnrichedUnorderedListSpan(private val htmlStyle: HtmlStyle) : LeadingMarginSpan, EnrichedParagraphSpan {
|
|
14
|
+
class EnrichedUnorderedListSpan(private val htmlStyle: HtmlStyle) : MetricAffectingSpan(), LeadingMarginSpan, EnrichedParagraphSpan {
|
|
15
|
+
override fun updateMeasureState(p0: TextPaint) {
|
|
16
|
+
// Do nothing, but inform layout that this span affects text metrics
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
override fun updateDrawState(p0: TextPaint?) {
|
|
20
|
+
// Do nothing, but inform layout that this span affects text metrics
|
|
21
|
+
}
|
|
22
|
+
|
|
13
23
|
override fun getLeadingMargin(p0: Boolean): Int {
|
|
14
24
|
return htmlStyle.ulBulletSize + htmlStyle.ulGapWidth + htmlStyle.ulMarginLeft
|
|
15
25
|
}
|
|
@@ -58,12 +58,11 @@ class ListStyles(private val view: EnrichedTextInputView) {
|
|
|
58
58
|
val spans = ssb.getSpans(start, end, clazz)
|
|
59
59
|
if (spans.isEmpty()) return false
|
|
60
60
|
|
|
61
|
-
ssb.replace(start, end, ssb.substring(start, end).replace("\u200B", ""))
|
|
62
|
-
|
|
63
61
|
for (span in spans) {
|
|
64
62
|
ssb.removeSpan(span)
|
|
65
63
|
}
|
|
66
64
|
|
|
65
|
+
ssb.replace(start, end, ssb.substring(start, end).replace("\u200B", ""))
|
|
67
66
|
return true
|
|
68
67
|
}
|
|
69
68
|
|
|
@@ -201,20 +201,21 @@ class ParametrizedStyles(private val view: EnrichedTextInputView) {
|
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
val start = mentionStart ?: return
|
|
204
|
-
view.isDuringTransaction = true
|
|
205
|
-
spannable.replace(start, selectionEnd, text)
|
|
206
204
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, spanEnd)
|
|
210
|
-
spannable.setSpan(span, safeStart, safeEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
205
|
+
view.runAsATransaction {
|
|
206
|
+
spannable.replace(start, selectionEnd, text)
|
|
211
207
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
spannable.
|
|
208
|
+
val span = EnrichedMentionSpan(text, indicator, attributes, view.htmlStyle)
|
|
209
|
+
val spanEnd = start + text.length
|
|
210
|
+
val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, spanEnd)
|
|
211
|
+
spannable.setSpan(span, safeStart, safeEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
212
|
+
|
|
213
|
+
val hasSpaceAtTheEnd = spannable.length > safeEnd && spannable[safeEnd] == ' '
|
|
214
|
+
if (!hasSpaceAtTheEnd) {
|
|
215
|
+
spannable.insert(safeEnd, " ")
|
|
216
|
+
}
|
|
215
217
|
}
|
|
216
218
|
|
|
217
|
-
view.isDuringTransaction = false
|
|
218
219
|
view.mentionHandler?.reset()
|
|
219
220
|
view.selection.validateStyles()
|
|
220
221
|
}
|
|
@@ -1091,10 +1091,15 @@ Class<RCTComponentViewProtocol> EnrichedTextInputViewCls(void) {
|
|
|
1091
1091
|
[self tryUpdatingHeight];
|
|
1092
1092
|
// update active styles as well
|
|
1093
1093
|
[self tryUpdatingActiveStyles];
|
|
1094
|
+
|
|
1094
1095
|
// update drawing
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1096
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
1097
|
+
NSRange wholeRange = NSMakeRange(0, textView.textStorage.string.length);
|
|
1098
|
+
NSRange actualRange = NSMakeRange(0, 0);
|
|
1099
|
+
[textView.layoutManager invalidateLayoutForCharacterRange:wholeRange actualCharacterRange:&actualRange];
|
|
1100
|
+
[textView.layoutManager ensureLayoutForCharacterRange:actualRange];
|
|
1101
|
+
[textView.layoutManager invalidateDisplayForCharacterRange:wholeRange];
|
|
1102
|
+
});
|
|
1098
1103
|
}
|
|
1099
1104
|
|
|
1100
1105
|
// MARK: - UITextView delegate methods
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-enriched",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Rich Text Editor component for React Native",
|
|
5
5
|
"source": "./src/index.tsx",
|
|
6
6
|
"main": "./lib/module/index.js",
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
"clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
|
|
40
40
|
"prepare": "bob build",
|
|
41
41
|
"release": "release-it",
|
|
42
|
-
"android-studio": "open -a 'Android Studio' example/android"
|
|
42
|
+
"android-studio": "open -a 'Android Studio' example/android",
|
|
43
|
+
"xcode": "open -a 'Xcode' example/ios/EnrichedTextInputExample.xcworkspace"
|
|
43
44
|
},
|
|
44
45
|
"keywords": [
|
|
45
46
|
"react-native",
|