react-native-enriched 0.1.3 → 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/README.md +36 -648
- package/ReactNativeEnriched.podspec +1 -1
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +32 -16
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewLayoutManager.kt +12 -2
- package/android/src/main/java/com/swmansion/enriched/events/MentionHandler.kt +5 -0
- 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 +12 -8
- package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSelection.kt +1 -1
- package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedTextWatcher.kt +2 -2
- package/android/src/main/new_arch/CMakeLists.txt +13 -9
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.h +2 -2
- package/ios/EnrichedTextInputView.mm +64 -56
- package/ios/internals/EnrichedTextInputViewShadowNode.mm +4 -1
- package/ios/styles/BlockQuoteStyle.mm +18 -23
- package/ios/styles/BoldStyle.mm +5 -2
- package/ios/styles/HeadingStyleBase.mm +37 -5
- package/ios/styles/InlineCodeStyle.mm +5 -2
- package/ios/styles/ItalicStyle.mm +5 -5
- package/ios/styles/OrderedListStyle.mm +18 -23
- package/ios/styles/StrikethroughStyle.mm +5 -2
- package/ios/styles/UnderlineStyle.mm +5 -2
- package/ios/styles/UnorderedListStyle.mm +18 -23
- package/ios/utils/OccurenceUtils.h +6 -0
- package/ios/utils/OccurenceUtils.mm +31 -0
- package/ios/utils/ParagraphAttributesUtils.h +6 -0
- package/ios/utils/ParagraphAttributesUtils.mm +67 -0
- package/ios/utils/StyleHeaders.h +2 -0
- package/ios/utils/ZeroWidthSpaceUtils.mm +14 -6
- package/package.json +6 -5
|
@@ -11,7 +11,7 @@ Pod::Spec.new do |s|
|
|
|
11
11
|
s.authors = package["author"]
|
|
12
12
|
|
|
13
13
|
s.platforms = { :ios => min_ios_version_supported }
|
|
14
|
-
s.source = { :git => "https://github.com/software-mansion
|
|
14
|
+
s.source = { :git => "https://github.com/software-mansion/react-native-enriched.git", :tag => "#{s.version}" }
|
|
15
15
|
|
|
16
16
|
s.source_files = "ios/**/*.{h,m,mm,cpp}"
|
|
17
17
|
s.private_header_files = "ios/**/*.h"
|
|
@@ -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,7 +52,7 @@ 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
|
-
var
|
|
55
|
+
var isDuringTransaction: Boolean = false
|
|
55
56
|
var isRemovingMany: Boolean = false
|
|
56
57
|
|
|
57
58
|
val mentionHandler: MentionHandler? = MentionHandler(this)
|
|
@@ -99,6 +100,10 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
99
100
|
gravity = Gravity.TOP or Gravity.START
|
|
100
101
|
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
|
101
102
|
|
|
103
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
104
|
+
breakStrategy = LineBreaker.BREAK_STRATEGY_HIGH_QUALITY
|
|
105
|
+
}
|
|
106
|
+
|
|
102
107
|
setPadding(0, 0, 0, 0)
|
|
103
108
|
setBackgroundColor(Color.TRANSPARENT)
|
|
104
109
|
|
|
@@ -233,18 +238,17 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
233
238
|
|
|
234
239
|
fun setValue(value: CharSequence?) {
|
|
235
240
|
if (value == null) return
|
|
236
|
-
isSettingValue = true
|
|
237
241
|
|
|
238
|
-
|
|
239
|
-
|
|
242
|
+
runAsATransaction {
|
|
243
|
+
val newText = parseText(value)
|
|
244
|
+
setText(newText)
|
|
240
245
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
// Scroll to the last line of text
|
|
245
|
-
setSelection(text?.length ?: 0)
|
|
246
|
+
// Assign SpanWatcher one more time as our previous spannable has been replaced
|
|
247
|
+
addSpanWatcher(EnrichedSpanWatcher(this))
|
|
246
248
|
|
|
247
|
-
|
|
249
|
+
// Scroll to the last line of text
|
|
250
|
+
setSelection(text?.length ?: 0)
|
|
251
|
+
}
|
|
248
252
|
}
|
|
249
253
|
|
|
250
254
|
fun setAutoFocus(autoFocus: Boolean) {
|
|
@@ -452,13 +456,13 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
452
456
|
val end = selection?.end ?: 0
|
|
453
457
|
val lengthBefore = text?.length ?: 0
|
|
454
458
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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
|
+
}
|
|
460
465
|
}
|
|
461
|
-
isSettingValue = false
|
|
462
466
|
|
|
463
467
|
val lengthAfter = text?.length ?: 0
|
|
464
468
|
val charactersRemoved = lengthBefore - lengthAfter
|
|
@@ -516,6 +520,18 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
516
520
|
parametrizedStyles?.setMentionSpan(text, indicator, attributes)
|
|
517
521
|
}
|
|
518
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
|
+
|
|
519
535
|
override fun onAttachedToWindow() {
|
|
520
536
|
super.onAttachedToWindow()
|
|
521
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
|
|
|
@@ -8,6 +8,11 @@ class MentionHandler(private val view: EnrichedTextInputView) {
|
|
|
8
8
|
private var previousText: String? = null
|
|
9
9
|
private var previousIndicator: String? = null
|
|
10
10
|
|
|
11
|
+
fun reset() {
|
|
12
|
+
previousText = null
|
|
13
|
+
previousIndicator = null
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
fun endMention() {
|
|
12
17
|
val indicator = previousIndicator
|
|
13
18
|
if (indicator == null) return
|
|
@@ -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,18 +201,22 @@ class ParametrizedStyles(private val view: EnrichedTextInputView) {
|
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
val start = mentionStart ?: return
|
|
204
|
-
spannable.replace(start, selectionEnd, text)
|
|
205
204
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, spanEnd)
|
|
209
|
-
spannable.setSpan(span, safeStart, safeEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
205
|
+
view.runAsATransaction {
|
|
206
|
+
spannable.replace(start, selectionEnd, text)
|
|
210
207
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
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
|
+
}
|
|
214
217
|
}
|
|
215
218
|
|
|
219
|
+
view.mentionHandler?.reset()
|
|
216
220
|
view.selection.validateStyles()
|
|
217
221
|
}
|
|
218
222
|
|
|
@@ -39,7 +39,7 @@ class EnrichedSelection(private val view: EnrichedTextInputView) {
|
|
|
39
39
|
val finalStart = newStart.coerceAtMost(newEnd).coerceAtLeast(0).coerceAtMost(textLength)
|
|
40
40
|
val finalEnd = newEnd.coerceAtLeast(newStart).coerceAtLeast(0).coerceAtMost(textLength)
|
|
41
41
|
|
|
42
|
-
if (isZeroWidthSelection(finalStart, finalEnd) && !view.
|
|
42
|
+
if (isZeroWidthSelection(finalStart, finalEnd) && !view.isDuringTransaction) {
|
|
43
43
|
view.setSelection(finalStart + 1)
|
|
44
44
|
shouldValidateStyles = false
|
|
45
45
|
}
|
|
@@ -18,14 +18,14 @@ class EnrichedTextWatcher(private val view: EnrichedTextInputView) : TextWatcher
|
|
|
18
18
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
|
19
19
|
endCursorPosition = start + count
|
|
20
20
|
view.layoutManager.measureSize(s ?: "")
|
|
21
|
-
view.isRemovingMany = !view.
|
|
21
|
+
view.isRemovingMany = !view.isDuringTransaction && before > count + 1
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
override fun afterTextChanged(s: Editable?) {
|
|
25
25
|
if (s == null) return
|
|
26
26
|
emitEvents(s)
|
|
27
27
|
|
|
28
|
-
if (view.
|
|
28
|
+
if (view.isDuringTransaction) return
|
|
29
29
|
applyStyles(s)
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -39,15 +39,19 @@ target_link_libraries(
|
|
|
39
39
|
ReactAndroid::reactnative
|
|
40
40
|
)
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
42
|
+
if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 80)
|
|
43
|
+
target_compile_reactnative_options(${LIB_TARGET_NAME} PRIVATE)
|
|
44
|
+
else()
|
|
45
|
+
target_compile_options(
|
|
46
|
+
${LIB_TARGET_NAME}
|
|
47
|
+
PRIVATE
|
|
48
|
+
-DLOG_TAG=\"ReactNative\"
|
|
49
|
+
-fexceptions
|
|
50
|
+
-frtti
|
|
51
|
+
-Wall
|
|
52
|
+
-std=c++20
|
|
53
|
+
)
|
|
54
|
+
endif()
|
|
51
55
|
|
|
52
56
|
target_include_directories(
|
|
53
57
|
${CMAKE_PROJECT_NAME}
|
|
@@ -11,7 +11,7 @@ namespace facebook::react {
|
|
|
11
11
|
class EnrichedTextInputMeasurementManager {
|
|
12
12
|
public:
|
|
13
13
|
EnrichedTextInputMeasurementManager(
|
|
14
|
-
const
|
|
14
|
+
const std::shared_ptr<const ContextContainer>& contextContainer)
|
|
15
15
|
: contextContainer_(contextContainer) {}
|
|
16
16
|
|
|
17
17
|
Size measure(
|
|
@@ -20,7 +20,7 @@ namespace facebook::react {
|
|
|
20
20
|
LayoutConstraints layoutConstraints) const;
|
|
21
21
|
|
|
22
22
|
private:
|
|
23
|
-
const
|
|
23
|
+
const std::shared_ptr<const ContextContainer> contextContainer_;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
} // namespace facebook::react
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
#import "WordsUtils.h"
|
|
15
15
|
#import "LayoutManagerExtension.h"
|
|
16
16
|
#import "ZeroWidthSpaceUtils.h"
|
|
17
|
+
#import "ParagraphAttributesUtils.h"
|
|
17
18
|
|
|
18
19
|
using namespace facebook::react;
|
|
19
20
|
|
|
@@ -422,7 +423,7 @@ Class<RCTComponentViewProtocol> EnrichedTextInputViewCls(void) {
|
|
|
422
423
|
}
|
|
423
424
|
|
|
424
425
|
// editable
|
|
425
|
-
if(newViewProps.editable !=
|
|
426
|
+
if(newViewProps.editable != textView.editable) {
|
|
426
427
|
textView.editable = newViewProps.editable;
|
|
427
428
|
}
|
|
428
429
|
|
|
@@ -564,7 +565,7 @@ Class<RCTComponentViewProtocol> EnrichedTextInputViewCls(void) {
|
|
|
564
565
|
unichar lastChar = [currentStr.string characterAtIndex:currentStr.length-1];
|
|
565
566
|
if([[NSCharacterSet newlineCharacterSet] characterIsMember:lastChar]) {
|
|
566
567
|
[currentStr appendAttributedString:
|
|
567
|
-
[[NSAttributedString alloc] initWithString:@"I" attributes:
|
|
568
|
+
[[NSAttributedString alloc] initWithString:@"I" attributes:defaultTypingAttributes]
|
|
568
569
|
];
|
|
569
570
|
}
|
|
570
571
|
}
|
|
@@ -977,6 +978,22 @@ Class<RCTComponentViewProtocol> EnrichedTextInputViewCls(void) {
|
|
|
977
978
|
[mentionStyleClass manageMentionTypingAttributes];
|
|
978
979
|
[mentionStyleClass manageMentionEditing];
|
|
979
980
|
}
|
|
981
|
+
|
|
982
|
+
// typing attributes for empty lines selection reset
|
|
983
|
+
NSString *currentString = [textView.textStorage.string copy];
|
|
984
|
+
if(textView.selectedRange.length == 0 && [_recentlyEmittedString isEqualToString:currentString]) {
|
|
985
|
+
// no string change means only a selection changed with no character changes
|
|
986
|
+
NSRange paragraphRange = [textView.textStorage.string paragraphRangeForRange:textView.selectedRange];
|
|
987
|
+
if(
|
|
988
|
+
paragraphRange.length == 0 ||
|
|
989
|
+
(paragraphRange.length == 1 &&
|
|
990
|
+
[[NSCharacterSet newlineCharacterSet] characterIsMember:[textView.textStorage.string characterAtIndex:paragraphRange.location]])
|
|
991
|
+
) {
|
|
992
|
+
// user changed selection to an empty line (or empty line with a newline)
|
|
993
|
+
// typing attributes need to be reset
|
|
994
|
+
textView.typingAttributes = defaultTypingAttributes;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
980
997
|
}
|
|
981
998
|
|
|
982
999
|
- (void)handleWordModificationBasedChanges:(NSString*)word inRange:(NSRange)range {
|
|
@@ -995,15 +1012,15 @@ Class<RCTComponentViewProtocol> EnrichedTextInputViewCls(void) {
|
|
|
995
1012
|
return;
|
|
996
1013
|
}
|
|
997
1014
|
|
|
1015
|
+
// zero width space adding or removal
|
|
1016
|
+
[ZeroWidthSpaceUtils handleZeroWidthSpacesInInput:self];
|
|
1017
|
+
|
|
998
1018
|
// emptying input typing attributes management
|
|
999
1019
|
if(textView.textStorage.string.length == 0 && _recentlyEmittedString.length > 0) {
|
|
1000
1020
|
// reset typing attribtues
|
|
1001
1021
|
textView.typingAttributes = defaultTypingAttributes;
|
|
1002
1022
|
}
|
|
1003
1023
|
|
|
1004
|
-
// zero width space removal
|
|
1005
|
-
[ZeroWidthSpaceUtils handleZeroWidthSpacesInInput:self];
|
|
1006
|
-
|
|
1007
1024
|
// inline code on newlines fix
|
|
1008
1025
|
InlineCodeStyle *codeStyle = stylesDict[@([InlineCodeStyle getStyleType])];
|
|
1009
1026
|
if(codeStyle != nullptr) {
|
|
@@ -1016,6 +1033,16 @@ Class<RCTComponentViewProtocol> EnrichedTextInputViewCls(void) {
|
|
|
1016
1033
|
[bqStyle manageBlockquoteColor];
|
|
1017
1034
|
}
|
|
1018
1035
|
|
|
1036
|
+
// improper headings fix
|
|
1037
|
+
H1Style *h1Style = stylesDict[@([H1Style getStyleType])];
|
|
1038
|
+
H2Style *h2Style = stylesDict[@([H2Style getStyleType])];
|
|
1039
|
+
H3Style *h3Style = stylesDict[@([H3Style getStyleType])];
|
|
1040
|
+
if(h1Style != nullptr && h2Style != nullptr && h3Style != nullptr) {
|
|
1041
|
+
[h1Style handleImproperHeadings];
|
|
1042
|
+
[h2Style handleImproperHeadings];
|
|
1043
|
+
[h3Style handleImproperHeadings];
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1019
1046
|
// placholder management
|
|
1020
1047
|
if(!_placeholderLabel.hidden && textView.textStorage.string.length > 0) {
|
|
1021
1048
|
[self setPlaceholderLabelShown:NO];
|
|
@@ -1064,10 +1091,15 @@ Class<RCTComponentViewProtocol> EnrichedTextInputViewCls(void) {
|
|
|
1064
1091
|
[self tryUpdatingHeight];
|
|
1065
1092
|
// update active styles as well
|
|
1066
1093
|
[self tryUpdatingActiveStyles];
|
|
1094
|
+
|
|
1067
1095
|
// update drawing
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
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
|
+
});
|
|
1071
1103
|
}
|
|
1072
1104
|
|
|
1073
1105
|
// MARK: - UITextView delegate methods
|
|
@@ -1102,61 +1134,37 @@ Class<RCTComponentViewProtocol> EnrichedTextInputViewCls(void) {
|
|
|
1102
1134
|
- (bool)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
|
|
1103
1135
|
recentlyChangedRange = NSMakeRange(range.location, text.length);
|
|
1104
1136
|
|
|
1105
|
-
BOOL rejectTextChanges = NO;
|
|
1106
|
-
|
|
1107
1137
|
UnorderedListStyle *uStyle = stylesDict[@([UnorderedListStyle getStyleType])];
|
|
1108
|
-
if(uStyle != nullptr) {
|
|
1109
|
-
// removing first line list fix
|
|
1110
|
-
BOOL removedFirstLineList = [uStyle handleBackspaceInRange:range replacementText:text];
|
|
1111
|
-
|
|
1112
|
-
// creating unordered list from "- "
|
|
1113
|
-
BOOL addedShortcutList = [uStyle tryHandlingListShorcutInRange:range replacementText:text];
|
|
1114
|
-
|
|
1115
|
-
// any of these changes make us reject text changes
|
|
1116
|
-
rejectTextChanges = rejectTextChanges || removedFirstLineList || addedShortcutList;
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
1138
|
OrderedListStyle *oStyle = stylesDict[@([OrderedListStyle getStyleType])];
|
|
1120
|
-
if(oStyle != nullptr) {
|
|
1121
|
-
// removing first line list fix
|
|
1122
|
-
BOOL removedFirstLineList = [oStyle handleBackspaceInRange:range replacementText:text];
|
|
1123
|
-
|
|
1124
|
-
// creating ordered list from "1."
|
|
1125
|
-
BOOL addedShortcutList = [oStyle tryHandlingListShorcutInRange:range replacementText:text];
|
|
1126
|
-
|
|
1127
|
-
// any of these changes make us reject text changes
|
|
1128
|
-
rejectTextChanges = rejectTextChanges || removedFirstLineList || addedShortcutList;
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
1139
|
BlockQuoteStyle *bqStyle = stylesDict[@([BlockQuoteStyle getStyleType])];
|
|
1132
|
-
if(bqStyle != nullptr) {
|
|
1133
|
-
// removing first line quote fix
|
|
1134
|
-
BOOL removedFirstLineQuote = [bqStyle handleBackspaceInRange:range replacementText:text];
|
|
1135
|
-
rejectTextChanges = rejectTextChanges || removedFirstLineQuote;
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
1140
|
LinkStyle *linkStyle = stylesDict[@([LinkStyle getStyleType])];
|
|
1139
|
-
if(linkStyle != nullptr) {
|
|
1140
|
-
// persisting link attributes fix
|
|
1141
|
-
BOOL fixedLeadingAttributes = [linkStyle handleLeadingLinkReplacement:range replacementText:text];
|
|
1142
|
-
rejectTextChanges = rejectTextChanges || fixedLeadingAttributes;
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
1141
|
MentionStyle *mentionStyle = stylesDict[@([MentionStyle getStyleType])];
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
//
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1142
|
+
H1Style *h1Style = stylesDict[@([H1Style getStyleType])];
|
|
1143
|
+
H2Style *h2Style = stylesDict[@([H2Style getStyleType])];
|
|
1144
|
+
H3Style *h3Style = stylesDict[@([H3Style getStyleType])];
|
|
1145
|
+
|
|
1146
|
+
// some of the changes these checks do could interfere with later checks and cause a crash
|
|
1147
|
+
// so here I rely on short circuiting evaluation of the logical expression
|
|
1148
|
+
// either way it's not possible to have two of them come off at the same time
|
|
1149
|
+
if(
|
|
1150
|
+
[uStyle handleBackspaceInRange:range replacementText:text] ||
|
|
1151
|
+
[uStyle tryHandlingListShorcutInRange:range replacementText:text] ||
|
|
1152
|
+
[oStyle handleBackspaceInRange:range replacementText:text] ||
|
|
1153
|
+
[oStyle tryHandlingListShorcutInRange:range replacementText:text] ||
|
|
1154
|
+
[bqStyle handleBackspaceInRange:range replacementText:text] ||
|
|
1155
|
+
[linkStyle handleLeadingLinkReplacement:range replacementText:text] ||
|
|
1156
|
+
[mentionStyle handleLeadingMentionReplacement:range replacementText:text] ||
|
|
1157
|
+
[h1Style handleNewlinesInRange:range replacementText:text] ||
|
|
1158
|
+
[h2Style handleNewlinesInRange:range replacementText:text] ||
|
|
1159
|
+
[h3Style handleNewlinesInRange:range replacementText:text] ||
|
|
1160
|
+
[ZeroWidthSpaceUtils handleBackspaceInRange:range replacementText:text input:self] ||
|
|
1161
|
+
[ParagraphAttributesUtils handleBackspaceInRange:range replacementText:text input:self]
|
|
1162
|
+
) {
|
|
1156
1163
|
[self anyTextMayHaveBeenModified];
|
|
1164
|
+
return NO;
|
|
1157
1165
|
}
|
|
1158
|
-
|
|
1159
|
-
return
|
|
1166
|
+
|
|
1167
|
+
return YES;
|
|
1160
1168
|
}
|
|
1161
1169
|
|
|
1162
1170
|
- (void)textViewDidChangeSelection:(UITextView *)textView {
|
|
@@ -55,7 +55,10 @@ Size EnrichedTextInputViewShadowNode::measureContent(const LayoutContext& layout
|
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
return {
|
|
58
|
+
return {
|
|
59
|
+
estimatedSize.width,
|
|
60
|
+
MIN(estimatedSize.height, layoutConstraints.maximumSize.height)
|
|
61
|
+
};
|
|
59
62
|
}
|
|
60
63
|
} else {
|
|
61
64
|
// on the very first call there is no componentView that we can query for the component height
|
|
@@ -118,16 +118,22 @@
|
|
|
118
118
|
[self removeAttributes:_input->textView.selectedRange];
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
// removing first quote line by backspacing doesn't remove typing attributes because it doesn't run textViewDidChange
|
|
122
|
-
// so we try guessing that a point should be deleted here
|
|
123
121
|
- (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text {
|
|
124
|
-
if([self detectStyle:_input->textView.selectedRange] &&
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
) {
|
|
122
|
+
if([self detectStyle:_input->textView.selectedRange] && text.length == 0) {
|
|
123
|
+
// backspace while the style is active
|
|
124
|
+
|
|
128
125
|
NSRange paragraphRange = [_input->textView.textStorage.string paragraphRangeForRange:_input->textView.selectedRange];
|
|
129
|
-
|
|
130
|
-
|
|
126
|
+
|
|
127
|
+
if(NSEqualRanges(_input->textView.selectedRange, NSMakeRange(0, 0))) {
|
|
128
|
+
// a backspace on the very first input's line quote
|
|
129
|
+
// it doesn't run textVieDidChange so we need to manually remove attributes
|
|
130
|
+
[self removeAttributes:paragraphRange];
|
|
131
|
+
return YES;
|
|
132
|
+
} else if(range.location == paragraphRange.location - 1) {
|
|
133
|
+
// same case in other lines; here, the removed range location will be exactly 1 less than paragraph range location
|
|
134
|
+
[self removeAttributes:paragraphRange];
|
|
135
|
+
return YES;
|
|
136
|
+
}
|
|
131
137
|
}
|
|
132
138
|
return NO;
|
|
133
139
|
}
|
|
@@ -145,22 +151,11 @@
|
|
|
145
151
|
}
|
|
146
152
|
];
|
|
147
153
|
} else {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
NSRange paragraphRange = NSMakeRange(0, 0);
|
|
155
|
-
NSRange inputRange = NSMakeRange(0, _input->textView.textStorage.length);
|
|
156
|
-
NSParagraphStyle *paragraph = [_input->textView.textStorage
|
|
157
|
-
attribute:NSParagraphStyleAttributeName
|
|
158
|
-
atIndex:searchLocation
|
|
159
|
-
longestEffectiveRange: ¶graphRange
|
|
160
|
-
inRange:inputRange
|
|
154
|
+
return [OccurenceUtils detect:NSParagraphStyleAttributeName withInput:_input atIndex:range.location checkPrevious:YES
|
|
155
|
+
withCondition:^BOOL(id _Nullable value, NSRange range) {
|
|
156
|
+
return [self styleCondition:value :range];
|
|
157
|
+
}
|
|
161
158
|
];
|
|
162
|
-
|
|
163
|
-
return [self styleCondition:paragraph :NSMakeRange(0, 0)];
|
|
164
159
|
}
|
|
165
160
|
}
|
|
166
161
|
|