react-native-enriched 0.2.1 → 0.3.0
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 +15 -12
- package/android/build.gradle +77 -72
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerDelegate.java +18 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerInterface.java +6 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/EventEmitters.cpp +146 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/EventEmitters.h +140 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.cpp +10 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.h +194 -0
- package/android/lint.gradle +70 -0
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputConnectionWrapper.kt +140 -0
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +245 -116
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewLayoutManager.kt +3 -1
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt +162 -53
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewPackage.kt +1 -3
- package/android/src/main/java/com/swmansion/enriched/MeasurementStore.kt +70 -21
- package/android/src/main/java/com/swmansion/enriched/events/MentionHandler.kt +20 -10
- package/android/src/main/java/com/swmansion/enriched/events/OnChangeHtmlEvent.kt +8 -9
- package/android/src/main/java/com/swmansion/enriched/events/OnChangeSelectionEvent.kt +10 -9
- package/android/src/main/java/com/swmansion/enriched/events/OnChangeStateDeprecatedEvent.kt +21 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnChangeStateEvent.kt +9 -12
- package/android/src/main/java/com/swmansion/enriched/events/OnChangeTextEvent.kt +10 -10
- package/android/src/main/java/com/swmansion/enriched/events/OnInputBlurEvent.kt +7 -9
- package/android/src/main/java/com/swmansion/enriched/events/OnInputFocusEvent.kt +7 -9
- package/android/src/main/java/com/swmansion/enriched/events/OnInputKeyPressEvent.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnLinkDetectedEvent.kt +13 -11
- package/android/src/main/java/com/swmansion/enriched/events/OnMentionDetectedEvent.kt +10 -9
- package/android/src/main/java/com/swmansion/enriched/events/OnMentionEvent.kt +9 -8
- package/android/src/main/java/com/swmansion/enriched/events/OnRequestHtmlResultEvent.kt +1 -2
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBlockQuoteSpan.kt +21 -8
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBoldSpan.kt +5 -4
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedCodeBlockSpan.kt +7 -5
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH1Span.kt +5 -4
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH2Span.kt +5 -4
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH3Span.kt +5 -4
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH4Span.kt +24 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH5Span.kt +24 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH6Span.kt +24 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedImageSpan.kt +29 -17
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedInlineCodeSpan.kt +5 -4
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedItalicSpan.kt +5 -4
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedLinkSpan.kt +7 -7
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedMentionSpan.kt +11 -14
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedOrderedListSpan.kt +15 -14
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt +167 -71
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedStrikeThroughSpan.kt +5 -4
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnderlineSpan.kt +5 -4
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnorderedListSpan.kt +8 -8
- package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedBlockSpan.kt +3 -2
- package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedHeadingSpan.kt +1 -2
- package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedInlineSpan.kt +1 -2
- package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedParagraphSpan.kt +3 -2
- package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedSpan.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedZeroWidthSpaceSpan.kt +1 -2
- package/android/src/main/java/com/swmansion/enriched/spans/utils/ForceRedrawSpan.kt +2 -1
- package/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt +78 -21
- package/android/src/main/java/com/swmansion/enriched/styles/InlineStyles.kt +25 -8
- package/android/src/main/java/com/swmansion/enriched/styles/ListStyles.kt +60 -20
- package/android/src/main/java/com/swmansion/enriched/styles/ParagraphStyles.kt +86 -26
- package/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt +128 -52
- package/android/src/main/java/com/swmansion/enriched/utils/AsyncDrawable.kt +10 -7
- package/android/src/main/java/com/swmansion/enriched/utils/EnrichedConstants.kt +11 -0
- package/android/src/main/java/com/swmansion/enriched/utils/EnrichedEditableFactory.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java +128 -87
- package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSelection.kt +71 -42
- package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpanState.kt +183 -48
- package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpannable.kt +82 -0
- package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpannableStringBuilder.kt +15 -0
- package/android/src/main/java/com/swmansion/enriched/utils/Utils.kt +0 -70
- package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedSpanWatcher.kt +46 -14
- package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedTextWatcher.kt +34 -11
- package/android/src/main/new_arch/CMakeLists.txt +6 -0
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/conversions.h +21 -1
- package/ios/EnrichedTextInputView.h +1 -1
- package/ios/EnrichedTextInputView.mm +381 -49
- package/ios/config/InputConfig.h +18 -0
- package/ios/config/InputConfig.mm +118 -8
- package/ios/generated/RNEnrichedTextInputViewSpec/EventEmitters.cpp +146 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/EventEmitters.h +140 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/Props.cpp +10 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/Props.h +194 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/RCTComponentViewHelpers.h +74 -0
- package/ios/inputParser/InputParser.mm +83 -10
- package/ios/{attachments → interfaces}/ImageAttachment.mm +3 -1
- package/ios/interfaces/LinkRegexConfig.h +19 -0
- package/ios/interfaces/LinkRegexConfig.mm +37 -0
- package/ios/{utils → interfaces}/MentionStyleProps.mm +2 -2
- package/ios/{utils → interfaces}/StyleHeaders.h +10 -0
- package/ios/{utils → interfaces}/StyleTypeEnum.h +3 -0
- package/ios/styles/BlockQuoteStyle.mm +5 -5
- package/ios/styles/BoldStyle.mm +21 -6
- package/ios/styles/CodeBlockStyle.mm +5 -5
- package/ios/styles/H4Style.mm +17 -0
- package/ios/styles/H5Style.mm +17 -0
- package/ios/styles/H6Style.mm +17 -0
- package/ios/styles/HeadingStyleBase.mm +27 -10
- package/ios/styles/ImageStyle.mm +5 -5
- package/ios/styles/InlineCodeStyle.mm +30 -19
- package/ios/styles/ItalicStyle.mm +5 -5
- package/ios/styles/LinkStyle.mm +98 -40
- package/ios/styles/MentionStyle.mm +4 -4
- package/ios/styles/OrderedListStyle.mm +5 -5
- package/ios/styles/StrikethroughStyle.mm +5 -5
- package/ios/styles/UnderlineStyle.mm +5 -5
- package/ios/styles/UnorderedListStyle.mm +5 -5
- package/ios/utils/ParagraphAttributesUtils.h +4 -0
- package/ios/utils/ParagraphAttributesUtils.mm +67 -0
- package/ios/utils/ParagraphsUtils.mm +4 -4
- package/lib/module/EnrichedTextInput.js +22 -1
- package/lib/module/EnrichedTextInput.js.map +1 -1
- package/lib/module/EnrichedTextInputNativeComponent.ts +138 -12
- package/lib/module/{normalizeHtmlStyle.js → utils/normalizeHtmlStyle.js} +12 -0
- package/lib/module/utils/normalizeHtmlStyle.js.map +1 -0
- package/lib/module/utils/regexParser.js +46 -0
- package/lib/module/utils/regexParser.js.map +1 -0
- package/lib/typescript/src/EnrichedTextInput.d.ts +23 -14
- package/lib/typescript/src/EnrichedTextInput.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts +123 -12
- package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/normalizeHtmlStyle.d.ts +4 -0
- package/lib/typescript/src/utils/normalizeHtmlStyle.d.ts.map +1 -0
- package/lib/typescript/src/utils/regexParser.d.ts +3 -0
- package/lib/typescript/src/utils/regexParser.d.ts.map +1 -0
- package/package.json +10 -6
- package/src/EnrichedTextInput.tsx +51 -13
- package/src/EnrichedTextInputNativeComponent.ts +138 -12
- package/src/index.tsx +2 -0
- package/src/{normalizeHtmlStyle.ts → utils/normalizeHtmlStyle.ts} +14 -2
- package/src/utils/regexParser.ts +56 -0
- package/lib/module/normalizeHtmlStyle.js.map +0 -1
- package/lib/typescript/src/normalizeHtmlStyle.d.ts +0 -4
- package/lib/typescript/src/normalizeHtmlStyle.d.ts.map +0 -1
- /package/ios/{utils → extensions}/ColorExtension.h +0 -0
- /package/ios/{utils → extensions}/ColorExtension.mm +0 -0
- /package/ios/{utils → extensions}/FontExtension.h +0 -0
- /package/ios/{utils → extensions}/FontExtension.mm +0 -0
- /package/ios/{utils → extensions}/LayoutManagerExtension.h +0 -0
- /package/ios/{utils → extensions}/LayoutManagerExtension.mm +0 -0
- /package/ios/{utils → extensions}/StringExtension.h +0 -0
- /package/ios/{utils → extensions}/StringExtension.mm +0 -0
- /package/ios/{utils → interfaces}/BaseStyleProtocol.h +0 -0
- /package/ios/{attachments → interfaces}/ImageAttachment.h +0 -0
- /package/ios/{utils → interfaces}/ImageData.h +0 -0
- /package/ios/{utils → interfaces}/ImageData.mm +0 -0
- /package/ios/{utils → interfaces}/LinkData.h +0 -0
- /package/ios/{utils → interfaces}/LinkData.mm +0 -0
- /package/ios/{attachments → interfaces}/MediaAttachment.h +0 -0
- /package/ios/{attachments → interfaces}/MediaAttachment.mm +0 -0
- /package/ios/{utils → interfaces}/MentionParams.h +0 -0
- /package/ios/{utils → interfaces}/MentionParams.mm +0 -0
- /package/ios/{utils → interfaces}/MentionStyleProps.h +0 -0
- /package/ios/{utils → interfaces}/StylePair.h +0 -0
- /package/ios/{utils → interfaces}/StylePair.mm +0 -0
- /package/ios/{utils → interfaces}/TextDecorationLineEnum.h +0 -0
- /package/ios/{utils → interfaces}/TextDecorationLineEnum.mm +0 -0
|
@@ -13,12 +13,16 @@ import android.text.InputType
|
|
|
13
13
|
import android.text.Spannable
|
|
14
14
|
import android.util.AttributeSet
|
|
15
15
|
import android.util.Log
|
|
16
|
+
import android.util.Patterns
|
|
16
17
|
import android.util.TypedValue
|
|
17
18
|
import android.view.Gravity
|
|
18
19
|
import android.view.MotionEvent
|
|
20
|
+
import android.view.inputmethod.EditorInfo
|
|
21
|
+
import android.view.inputmethod.InputConnection
|
|
19
22
|
import android.view.inputmethod.InputMethodManager
|
|
20
23
|
import androidx.appcompat.widget.AppCompatEditText
|
|
21
24
|
import com.facebook.react.bridge.ReactContext
|
|
25
|
+
import com.facebook.react.bridge.ReadableMap
|
|
22
26
|
import com.facebook.react.common.ReactConstants
|
|
23
27
|
import com.facebook.react.uimanager.PixelUtil
|
|
24
28
|
import com.facebook.react.uimanager.StateWrapper
|
|
@@ -36,20 +40,23 @@ import com.swmansion.enriched.spans.EnrichedH3Span
|
|
|
36
40
|
import com.swmansion.enriched.spans.EnrichedImageSpan
|
|
37
41
|
import com.swmansion.enriched.spans.EnrichedSpans
|
|
38
42
|
import com.swmansion.enriched.spans.interfaces.EnrichedSpan
|
|
43
|
+
import com.swmansion.enriched.styles.HtmlStyle
|
|
39
44
|
import com.swmansion.enriched.styles.InlineStyles
|
|
40
45
|
import com.swmansion.enriched.styles.ListStyles
|
|
41
46
|
import com.swmansion.enriched.styles.ParagraphStyles
|
|
42
47
|
import com.swmansion.enriched.styles.ParametrizedStyles
|
|
43
|
-
import com.swmansion.enriched.
|
|
48
|
+
import com.swmansion.enriched.utils.EnrichedConstants
|
|
49
|
+
import com.swmansion.enriched.utils.EnrichedEditableFactory
|
|
44
50
|
import com.swmansion.enriched.utils.EnrichedParser
|
|
45
51
|
import com.swmansion.enriched.utils.EnrichedSelection
|
|
46
52
|
import com.swmansion.enriched.utils.EnrichedSpanState
|
|
47
53
|
import com.swmansion.enriched.utils.mergeSpannables
|
|
48
54
|
import com.swmansion.enriched.watchers.EnrichedSpanWatcher
|
|
49
55
|
import com.swmansion.enriched.watchers.EnrichedTextWatcher
|
|
56
|
+
import java.util.regex.Pattern
|
|
57
|
+
import java.util.regex.PatternSyntaxException
|
|
50
58
|
import kotlin.math.ceil
|
|
51
59
|
|
|
52
|
-
|
|
53
60
|
class EnrichedTextInputView : AppCompatEditText {
|
|
54
61
|
var stateWrapper: StateWrapper? = null
|
|
55
62
|
val selection: EnrichedSelection? = EnrichedSelection(this)
|
|
@@ -65,16 +72,19 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
65
72
|
val mentionHandler: MentionHandler? = MentionHandler(this)
|
|
66
73
|
var htmlStyle: HtmlStyle = HtmlStyle(this, null)
|
|
67
74
|
set(value) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
if (field != value) {
|
|
76
|
+
val prev = field
|
|
77
|
+
field = value
|
|
78
|
+
reApplyHtmlStyleForSpans(prev, value)
|
|
79
|
+
}
|
|
73
80
|
}
|
|
81
|
+
|
|
82
|
+
var linkRegex: Pattern? = Patterns.WEB_URL
|
|
74
83
|
var spanWatcher: EnrichedSpanWatcher? = null
|
|
75
84
|
var layoutManager: EnrichedTextInputViewLayoutManager = EnrichedTextInputViewLayoutManager(this)
|
|
76
85
|
|
|
77
|
-
var shouldEmitHtml: Boolean =
|
|
86
|
+
var shouldEmitHtml: Boolean = false
|
|
87
|
+
var shouldEmitOnChangeText: Boolean = false
|
|
78
88
|
var experimentalSynchronousEvents: Boolean = false
|
|
79
89
|
|
|
80
90
|
var fontSize: Float? = null
|
|
@@ -101,11 +111,26 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
101
111
|
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
|
|
102
112
|
context,
|
|
103
113
|
attrs,
|
|
104
|
-
defStyleAttr
|
|
114
|
+
defStyleAttr,
|
|
105
115
|
) {
|
|
106
116
|
prepareComponent()
|
|
107
117
|
}
|
|
108
118
|
|
|
119
|
+
override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection? {
|
|
120
|
+
var inputConnection = super.onCreateInputConnection(outAttrs)
|
|
121
|
+
if (inputConnection != null) {
|
|
122
|
+
inputConnection =
|
|
123
|
+
EnrichedTextInputConnectionWrapper(
|
|
124
|
+
inputConnection,
|
|
125
|
+
context as ReactContext,
|
|
126
|
+
this,
|
|
127
|
+
experimentalSynchronousEvents,
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return inputConnection
|
|
132
|
+
}
|
|
133
|
+
|
|
109
134
|
init {
|
|
110
135
|
inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
|
111
136
|
}
|
|
@@ -124,7 +149,11 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
124
149
|
setPadding(0, 0, 0, 0)
|
|
125
150
|
setBackgroundColor(Color.TRANSPARENT)
|
|
126
151
|
|
|
127
|
-
|
|
152
|
+
// Ensure that every time new editable is created, it has EnrichedSpanWatcher attached
|
|
153
|
+
val spanWatcher = EnrichedSpanWatcher(this)
|
|
154
|
+
this.spanWatcher = spanWatcher
|
|
155
|
+
setEditableFactory(EnrichedEditableFactory(spanWatcher))
|
|
156
|
+
|
|
128
157
|
addTextChangedListener(EnrichedTextWatcher(this))
|
|
129
158
|
}
|
|
130
159
|
|
|
@@ -138,31 +167,32 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
138
167
|
this.parent.requestDisallowInterceptTouchEvent(true)
|
|
139
168
|
}
|
|
140
169
|
|
|
141
|
-
MotionEvent.ACTION_MOVE ->
|
|
170
|
+
MotionEvent.ACTION_MOVE -> {
|
|
142
171
|
if (detectScrollMovement) {
|
|
143
172
|
if (!canScrollVertically(-1) &&
|
|
144
173
|
!canScrollVertically(1) &&
|
|
145
174
|
!canScrollHorizontally(-1) &&
|
|
146
|
-
!canScrollHorizontally(1)
|
|
175
|
+
!canScrollHorizontally(1)
|
|
176
|
+
) {
|
|
147
177
|
// We cannot scroll, let parent views take care of these touches.
|
|
148
178
|
this.parent.requestDisallowInterceptTouchEvent(false)
|
|
149
179
|
}
|
|
150
180
|
detectScrollMovement = false
|
|
151
181
|
}
|
|
182
|
+
}
|
|
152
183
|
}
|
|
153
184
|
|
|
154
185
|
return super.onTouchEvent(ev)
|
|
155
186
|
}
|
|
156
187
|
|
|
157
|
-
override fun canScrollVertically(direction: Int): Boolean
|
|
158
|
-
return scrollEnabled
|
|
159
|
-
}
|
|
188
|
+
override fun canScrollVertically(direction: Int): Boolean = scrollEnabled
|
|
160
189
|
|
|
161
|
-
override fun canScrollHorizontally(direction: Int): Boolean
|
|
162
|
-
return scrollEnabled
|
|
163
|
-
}
|
|
190
|
+
override fun canScrollHorizontally(direction: Int): Boolean = scrollEnabled
|
|
164
191
|
|
|
165
|
-
override fun onSelectionChanged(
|
|
192
|
+
override fun onSelectionChanged(
|
|
193
|
+
selStart: Int,
|
|
194
|
+
selEnd: Int,
|
|
195
|
+
) {
|
|
166
196
|
super.onSelectionChanged(selStart, selEnd)
|
|
167
197
|
selection?.onSelection(selStart, selEnd)
|
|
168
198
|
}
|
|
@@ -172,7 +202,11 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
172
202
|
inputMethodManager?.hideSoftInputFromWindow(windowToken, 0)
|
|
173
203
|
}
|
|
174
204
|
|
|
175
|
-
override fun onFocusChanged(
|
|
205
|
+
override fun onFocusChanged(
|
|
206
|
+
focused: Boolean,
|
|
207
|
+
direction: Int,
|
|
208
|
+
previouslyFocusedRect: Rect?,
|
|
209
|
+
) {
|
|
176
210
|
super.onFocusChanged(focused, direction, previouslyFocusedRect)
|
|
177
211
|
val context = context as ReactContext
|
|
178
212
|
val surfaceId = UIManagerHelper.getSurfaceId(context)
|
|
@@ -191,6 +225,7 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
191
225
|
handleCustomCopy()
|
|
192
226
|
return true
|
|
193
227
|
}
|
|
228
|
+
|
|
194
229
|
android.R.id.paste -> {
|
|
195
230
|
handleCustomPaste()
|
|
196
231
|
return true
|
|
@@ -236,9 +271,13 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
236
271
|
|
|
237
272
|
// Currently, we do not support pasting images
|
|
238
273
|
if (item?.text == null) return
|
|
274
|
+
val lengthBefore = currentText.length
|
|
239
275
|
val finalText = currentText.mergeSpannables(start, end, item.text.toString())
|
|
240
276
|
setValue(finalText)
|
|
241
|
-
|
|
277
|
+
|
|
278
|
+
// Detect links in the newly pasted range
|
|
279
|
+
val finalEndIndex = start + finalText.length - lengthBefore
|
|
280
|
+
parametrizedStyles?.detectLinksInRange(finalText, start, finalEndIndex)
|
|
242
281
|
}
|
|
243
282
|
|
|
244
283
|
fun requestFocusProgrammatically() {
|
|
@@ -269,14 +308,43 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
269
308
|
setText(newText)
|
|
270
309
|
|
|
271
310
|
observeAsyncImages()
|
|
272
|
-
// Assign SpanWatcher one more time as our previous spannable has been replaced
|
|
273
|
-
addSpanWatcher(EnrichedSpanWatcher(this))
|
|
274
311
|
|
|
275
312
|
// Scroll to the last line of text
|
|
276
313
|
setSelection(text?.length ?: 0)
|
|
277
314
|
}
|
|
278
315
|
}
|
|
279
316
|
|
|
317
|
+
fun setCustomSelection(
|
|
318
|
+
visibleStart: Int,
|
|
319
|
+
visibleEnd: Int,
|
|
320
|
+
) {
|
|
321
|
+
val actualStart = getActualIndex(visibleStart)
|
|
322
|
+
val actualEnd = getActualIndex(visibleEnd)
|
|
323
|
+
|
|
324
|
+
setSelection(actualStart, actualEnd)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Helper: Walks through the string skipping ZWSPs to find the Nth visible character
|
|
328
|
+
private fun getActualIndex(visibleIndex: Int): Int {
|
|
329
|
+
val currentText = text as Spannable
|
|
330
|
+
var currentVisibleCount = 0
|
|
331
|
+
var actualIndex = 0
|
|
332
|
+
|
|
333
|
+
while (actualIndex < currentText.length) {
|
|
334
|
+
if (currentVisibleCount == visibleIndex) {
|
|
335
|
+
return actualIndex
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// If the current char is not a hidden space, it counts towards our visible index
|
|
339
|
+
if (currentText[actualIndex] != EnrichedConstants.ZWS) {
|
|
340
|
+
currentVisibleCount++
|
|
341
|
+
}
|
|
342
|
+
actualIndex++
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return actualIndex
|
|
346
|
+
}
|
|
347
|
+
|
|
280
348
|
/**
|
|
281
349
|
* Finds all async images in the current text and sets up listeners
|
|
282
350
|
* to redraw the text layout when they finish downloading.
|
|
@@ -376,19 +444,50 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
376
444
|
}
|
|
377
445
|
|
|
378
446
|
fun setAutoCapitalize(flagName: String?) {
|
|
379
|
-
val flag =
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
447
|
+
val flag =
|
|
448
|
+
when (flagName) {
|
|
449
|
+
"none" -> InputType.TYPE_NULL
|
|
450
|
+
"sentences" -> InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
|
|
451
|
+
"words" -> InputType.TYPE_TEXT_FLAG_CAP_WORDS
|
|
452
|
+
"characters" -> InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS
|
|
453
|
+
else -> InputType.TYPE_NULL
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
inputType = (
|
|
457
|
+
inputType and
|
|
458
|
+
InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS.inv() and
|
|
459
|
+
InputType.TYPE_TEXT_FLAG_CAP_WORDS.inv() and
|
|
460
|
+
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES.inv()
|
|
461
|
+
) or if (flag == InputType.TYPE_NULL) 0 else flag
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
fun setLinkRegex(config: ReadableMap?) {
|
|
465
|
+
val patternStr = config?.getString("pattern")
|
|
466
|
+
if (patternStr == null) {
|
|
467
|
+
linkRegex = Patterns.WEB_URL
|
|
468
|
+
return
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (config.getBoolean("isDefault")) {
|
|
472
|
+
linkRegex = Patterns.WEB_URL
|
|
473
|
+
return
|
|
385
474
|
}
|
|
386
475
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
476
|
+
if (config.getBoolean("isDisabled")) {
|
|
477
|
+
linkRegex = null
|
|
478
|
+
return
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
var flags = 0
|
|
482
|
+
if (config.getBoolean("caseInsensitive")) flags = flags or Pattern.CASE_INSENSITIVE
|
|
483
|
+
if (config.getBoolean("dotAll")) flags = flags or Pattern.DOTALL
|
|
484
|
+
|
|
485
|
+
try {
|
|
486
|
+
linkRegex = Pattern.compile("(?s).*?($patternStr).*", flags)
|
|
487
|
+
} catch (e: PatternSyntaxException) {
|
|
488
|
+
Log.w("EnrichedTextInputView", "Invalid link regex pattern: $patternStr")
|
|
489
|
+
linkRegex = Patterns.WEB_URL
|
|
490
|
+
}
|
|
392
491
|
}
|
|
393
492
|
|
|
394
493
|
// https://github.com/facebook/react-native/blob/36df97f500aa0aa8031098caf7526db358b6ddc1/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt#L283C2-L284C1
|
|
@@ -397,9 +496,7 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
397
496
|
// next layout() to be called. However, we do not perform a layout() after a requestLayout(), so
|
|
398
497
|
// we need to override isLayoutRequested to force EditText to scroll to the end of the new text
|
|
399
498
|
// immediately.
|
|
400
|
-
override fun isLayoutRequested(): Boolean
|
|
401
|
-
return false
|
|
402
|
-
}
|
|
499
|
+
override fun isLayoutRequested(): Boolean = false
|
|
403
500
|
|
|
404
501
|
fun afterUpdateTransaction() {
|
|
405
502
|
updateTypeface()
|
|
@@ -439,6 +536,9 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
439
536
|
EnrichedSpans.H1 -> paragraphStyles?.toggleStyle(EnrichedSpans.H1)
|
|
440
537
|
EnrichedSpans.H2 -> paragraphStyles?.toggleStyle(EnrichedSpans.H2)
|
|
441
538
|
EnrichedSpans.H3 -> paragraphStyles?.toggleStyle(EnrichedSpans.H3)
|
|
539
|
+
EnrichedSpans.H4 -> paragraphStyles?.toggleStyle(EnrichedSpans.H4)
|
|
540
|
+
EnrichedSpans.H5 -> paragraphStyles?.toggleStyle(EnrichedSpans.H5)
|
|
541
|
+
EnrichedSpans.H6 -> paragraphStyles?.toggleStyle(EnrichedSpans.H6)
|
|
442
542
|
EnrichedSpans.CODE_BLOCK -> paragraphStyles?.toggleStyle(EnrichedSpans.CODE_BLOCK)
|
|
443
543
|
EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.toggleStyle(EnrichedSpans.BLOCK_QUOTE)
|
|
444
544
|
EnrichedSpans.ORDERED_LIST -> listStyles?.toggleStyle(EnrichedSpans.ORDERED_LIST)
|
|
@@ -449,48 +549,60 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
449
549
|
layoutManager.invalidateLayout()
|
|
450
550
|
}
|
|
451
551
|
|
|
452
|
-
private fun removeStyle(
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
552
|
+
private fun removeStyle(
|
|
553
|
+
name: String,
|
|
554
|
+
start: Int,
|
|
555
|
+
end: Int,
|
|
556
|
+
): Boolean {
|
|
557
|
+
val removed =
|
|
558
|
+
when (name) {
|
|
559
|
+
EnrichedSpans.BOLD -> inlineStyles?.removeStyle(EnrichedSpans.BOLD, start, end)
|
|
560
|
+
EnrichedSpans.ITALIC -> inlineStyles?.removeStyle(EnrichedSpans.ITALIC, start, end)
|
|
561
|
+
EnrichedSpans.UNDERLINE -> inlineStyles?.removeStyle(EnrichedSpans.UNDERLINE, start, end)
|
|
562
|
+
EnrichedSpans.STRIKETHROUGH -> inlineStyles?.removeStyle(EnrichedSpans.STRIKETHROUGH, start, end)
|
|
563
|
+
EnrichedSpans.INLINE_CODE -> inlineStyles?.removeStyle(EnrichedSpans.INLINE_CODE, start, end)
|
|
564
|
+
EnrichedSpans.H1 -> paragraphStyles?.removeStyle(EnrichedSpans.H1, start, end)
|
|
565
|
+
EnrichedSpans.H2 -> paragraphStyles?.removeStyle(EnrichedSpans.H2, start, end)
|
|
566
|
+
EnrichedSpans.H3 -> paragraphStyles?.removeStyle(EnrichedSpans.H3, start, end)
|
|
567
|
+
EnrichedSpans.H4 -> paragraphStyles?.removeStyle(EnrichedSpans.H4, start, end)
|
|
568
|
+
EnrichedSpans.H5 -> paragraphStyles?.removeStyle(EnrichedSpans.H5, start, end)
|
|
569
|
+
EnrichedSpans.H6 -> paragraphStyles?.removeStyle(EnrichedSpans.H6, start, end)
|
|
570
|
+
EnrichedSpans.CODE_BLOCK -> paragraphStyles?.removeStyle(EnrichedSpans.CODE_BLOCK, start, end)
|
|
571
|
+
EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.removeStyle(EnrichedSpans.BLOCK_QUOTE, start, end)
|
|
572
|
+
EnrichedSpans.ORDERED_LIST -> listStyles?.removeStyle(EnrichedSpans.ORDERED_LIST, start, end)
|
|
573
|
+
EnrichedSpans.UNORDERED_LIST -> listStyles?.removeStyle(EnrichedSpans.UNORDERED_LIST, start, end)
|
|
574
|
+
EnrichedSpans.LINK -> parametrizedStyles?.removeStyle(EnrichedSpans.LINK, start, end)
|
|
575
|
+
EnrichedSpans.IMAGE -> parametrizedStyles?.removeStyle(EnrichedSpans.IMAGE, start, end)
|
|
576
|
+
EnrichedSpans.MENTION -> parametrizedStyles?.removeStyle(EnrichedSpans.MENTION, start, end)
|
|
577
|
+
else -> false
|
|
578
|
+
}
|
|
471
579
|
|
|
472
580
|
return removed == true
|
|
473
581
|
}
|
|
474
582
|
|
|
475
583
|
private fun getTargetRange(name: String): Pair<Int, Int> {
|
|
476
|
-
val result =
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
584
|
+
val result =
|
|
585
|
+
when (name) {
|
|
586
|
+
EnrichedSpans.BOLD -> inlineStyles?.getStyleRange()
|
|
587
|
+
EnrichedSpans.ITALIC -> inlineStyles?.getStyleRange()
|
|
588
|
+
EnrichedSpans.UNDERLINE -> inlineStyles?.getStyleRange()
|
|
589
|
+
EnrichedSpans.STRIKETHROUGH -> inlineStyles?.getStyleRange()
|
|
590
|
+
EnrichedSpans.INLINE_CODE -> inlineStyles?.getStyleRange()
|
|
591
|
+
EnrichedSpans.H1 -> paragraphStyles?.getStyleRange()
|
|
592
|
+
EnrichedSpans.H2 -> paragraphStyles?.getStyleRange()
|
|
593
|
+
EnrichedSpans.H3 -> paragraphStyles?.getStyleRange()
|
|
594
|
+
EnrichedSpans.H4 -> paragraphStyles?.getStyleRange()
|
|
595
|
+
EnrichedSpans.H5 -> paragraphStyles?.getStyleRange()
|
|
596
|
+
EnrichedSpans.H6 -> paragraphStyles?.getStyleRange()
|
|
597
|
+
EnrichedSpans.CODE_BLOCK -> paragraphStyles?.getStyleRange()
|
|
598
|
+
EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.getStyleRange()
|
|
599
|
+
EnrichedSpans.ORDERED_LIST -> listStyles?.getStyleRange()
|
|
600
|
+
EnrichedSpans.UNORDERED_LIST -> listStyles?.getStyleRange()
|
|
601
|
+
EnrichedSpans.LINK -> parametrizedStyles?.getStyleRange()
|
|
602
|
+
EnrichedSpans.IMAGE -> parametrizedStyles?.getStyleRange()
|
|
603
|
+
EnrichedSpans.MENTION -> parametrizedStyles?.getStyleRange()
|
|
604
|
+
else -> Pair(0, 0)
|
|
605
|
+
}
|
|
494
606
|
|
|
495
607
|
return result ?: Pair(0, 0)
|
|
496
608
|
}
|
|
@@ -524,11 +636,12 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
524
636
|
|
|
525
637
|
val lengthAfter = text?.length ?: 0
|
|
526
638
|
val charactersRemoved = lengthBefore - lengthAfter
|
|
527
|
-
val finalEnd =
|
|
528
|
-
(
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
639
|
+
val finalEnd =
|
|
640
|
+
if (charactersRemoved > 0) {
|
|
641
|
+
(end - charactersRemoved).coerceAtLeast(0)
|
|
642
|
+
} else {
|
|
643
|
+
end
|
|
644
|
+
}
|
|
532
645
|
|
|
533
646
|
val finalStart = start.coerceAtLeast(0).coerceAtMost(finalEnd)
|
|
534
647
|
selection?.onSelection(finalStart, finalEnd)
|
|
@@ -537,12 +650,6 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
537
650
|
return true
|
|
538
651
|
}
|
|
539
652
|
|
|
540
|
-
private fun addSpanWatcher(watcher: EnrichedSpanWatcher) {
|
|
541
|
-
val spannable = text as Spannable
|
|
542
|
-
spannable.setSpan(watcher, 0, spannable.length, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
|
|
543
|
-
spanWatcher = watcher
|
|
544
|
-
}
|
|
545
|
-
|
|
546
653
|
fun verifyAndToggleStyle(name: String) {
|
|
547
654
|
val isValid = verifyStyle(name)
|
|
548
655
|
if (!isValid) return
|
|
@@ -550,14 +657,23 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
550
657
|
toggleStyle(name)
|
|
551
658
|
}
|
|
552
659
|
|
|
553
|
-
fun addLink(
|
|
660
|
+
fun addLink(
|
|
661
|
+
start: Int,
|
|
662
|
+
end: Int,
|
|
663
|
+
text: String,
|
|
664
|
+
url: String,
|
|
665
|
+
) {
|
|
554
666
|
val isValid = verifyStyle(EnrichedSpans.LINK)
|
|
555
667
|
if (!isValid) return
|
|
556
668
|
|
|
557
669
|
parametrizedStyles?.setLinkSpan(start, end, text, url)
|
|
558
670
|
}
|
|
559
671
|
|
|
560
|
-
fun addImage(
|
|
672
|
+
fun addImage(
|
|
673
|
+
src: String,
|
|
674
|
+
width: Float,
|
|
675
|
+
height: Float,
|
|
676
|
+
) {
|
|
561
677
|
val isValid = verifyStyle(EnrichedSpans.IMAGE)
|
|
562
678
|
if (!isValid) return
|
|
563
679
|
|
|
@@ -572,7 +688,11 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
572
688
|
parametrizedStyles?.startMention(indicator)
|
|
573
689
|
}
|
|
574
690
|
|
|
575
|
-
fun addMention(
|
|
691
|
+
fun addMention(
|
|
692
|
+
indicator: String,
|
|
693
|
+
text: String,
|
|
694
|
+
attributes: Map<String, String>,
|
|
695
|
+
) {
|
|
576
696
|
val isValid = verifyStyle(EnrichedSpans.MENTION)
|
|
577
697
|
if (!isValid) return
|
|
578
698
|
|
|
@@ -580,11 +700,12 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
580
700
|
}
|
|
581
701
|
|
|
582
702
|
fun requestHTML(requestId: Int) {
|
|
583
|
-
val html =
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
703
|
+
val html =
|
|
704
|
+
try {
|
|
705
|
+
EnrichedParser.toHtmlWithDefault(text)
|
|
706
|
+
} catch (e: Exception) {
|
|
707
|
+
null
|
|
708
|
+
}
|
|
588
709
|
|
|
589
710
|
val reactContext = context as ReactContext
|
|
590
711
|
val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
|
|
@@ -604,34 +725,37 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
604
725
|
}
|
|
605
726
|
}
|
|
606
727
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
728
|
+
private fun forceScrollToSelection() {
|
|
729
|
+
val textLayout = layout ?: return
|
|
730
|
+
val cursorOffset = selectionStart
|
|
731
|
+
if (cursorOffset <= 0) return
|
|
611
732
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
733
|
+
val selectedLineIndex = textLayout.getLineForOffset(cursorOffset)
|
|
734
|
+
val selectedLineTop = textLayout.getLineTop(selectedLineIndex)
|
|
735
|
+
val selectedLineBottom = textLayout.getLineBottom(selectedLineIndex)
|
|
736
|
+
val visibleTextHeight = height - paddingTop - paddingBottom
|
|
616
737
|
|
|
617
|
-
|
|
738
|
+
if (visibleTextHeight <= 0) return
|
|
618
739
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
if (selectedLineTop < visibleTop) {
|
|
624
|
-
targetScrollY = selectedLineTop
|
|
625
|
-
} else if (selectedLineBottom > visibleBottom) {
|
|
626
|
-
targetScrollY = selectedLineBottom - visibleTextHeight
|
|
627
|
-
}
|
|
740
|
+
val visibleTop = scrollY
|
|
741
|
+
val visibleBottom = scrollY + visibleTextHeight
|
|
742
|
+
var targetScrollY = scrollY
|
|
628
743
|
|
|
629
|
-
|
|
630
|
-
targetScrollY =
|
|
631
|
-
|
|
744
|
+
if (selectedLineTop < visibleTop) {
|
|
745
|
+
targetScrollY = selectedLineTop
|
|
746
|
+
} else if (selectedLineBottom > visibleBottom) {
|
|
747
|
+
targetScrollY = selectedLineBottom - visibleTextHeight
|
|
632
748
|
}
|
|
633
749
|
|
|
634
|
-
|
|
750
|
+
val maxScrollY = (textLayout.height - visibleTextHeight).coerceAtLeast(0)
|
|
751
|
+
targetScrollY = targetScrollY.coerceIn(0, maxScrollY)
|
|
752
|
+
scrollTo(scrollX, targetScrollY)
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
private fun reApplyHtmlStyleForSpans(
|
|
756
|
+
previousHtmlStyle: HtmlStyle,
|
|
757
|
+
nextHtmlStyle: HtmlStyle,
|
|
758
|
+
) {
|
|
635
759
|
val shouldRemoveBoldSpanFromH1Span = !previousHtmlStyle.h1Bold && nextHtmlStyle.h1Bold
|
|
636
760
|
val shouldRemoveBoldSpanFromH2Span = !previousHtmlStyle.h2Bold && nextHtmlStyle.h2Bold
|
|
637
761
|
val shouldRemoveBoldSpanFromH3Span = !previousHtmlStyle.h3Bold && nextHtmlStyle.h3Bold
|
|
@@ -652,7 +776,9 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
652
776
|
|
|
653
777
|
if (start == -1 || end == -1) continue
|
|
654
778
|
|
|
655
|
-
if ((span is EnrichedH1Span && shouldRemoveBoldSpanFromH1Span) || (span is EnrichedH2Span && shouldRemoveBoldSpanFromH2Span) ||
|
|
779
|
+
if ((span is EnrichedH1Span && shouldRemoveBoldSpanFromH1Span) || (span is EnrichedH2Span && shouldRemoveBoldSpanFromH2Span) ||
|
|
780
|
+
(span is EnrichedH3Span && shouldRemoveBoldSpanFromH3Span)
|
|
781
|
+
) {
|
|
656
782
|
val isRemoved = removeStyle(EnrichedSpans.BOLD, start, end)
|
|
657
783
|
if (isRemoved) shouldEmitStateChange = true
|
|
658
784
|
}
|
|
@@ -673,6 +799,9 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
673
799
|
override fun onAttachedToWindow() {
|
|
674
800
|
super.onAttachedToWindow()
|
|
675
801
|
|
|
802
|
+
// https://github.com/facebook/react-native/blob/36df97f500aa0aa8031098caf7526db358b6ddc1/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt#L946
|
|
803
|
+
super.setTextIsSelectable(true)
|
|
804
|
+
|
|
676
805
|
if (autoFocus && !didAttachToWindow) {
|
|
677
806
|
requestFocusProgrammatically()
|
|
678
807
|
}
|
|
@@ -2,7 +2,9 @@ package com.swmansion.enriched
|
|
|
2
2
|
|
|
3
3
|
import com.facebook.react.bridge.Arguments
|
|
4
4
|
|
|
5
|
-
class EnrichedTextInputViewLayoutManager(
|
|
5
|
+
class EnrichedTextInputViewLayoutManager(
|
|
6
|
+
private val view: EnrichedTextInputView,
|
|
7
|
+
) {
|
|
6
8
|
private var forceHeightRecalculationCounter: Int = 0
|
|
7
9
|
|
|
8
10
|
fun invalidateLayout() {
|