react-native-enriched-markdown-scaffold 0.6.0-scaffold.8 → 0.7.4-scaffold.2
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/ReactNativeEnrichedMarkdown.podspec +62 -11
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerDelegate.java +3 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerInterface.java +1 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextInputManagerDelegate.java +9 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextInputManagerInterface.java +3 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +3 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +1 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +20 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +744 -0
- package/android/src/main/baseline-prof.txt +75 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +62 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +10 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +10 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +9 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +10 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/accessibility/AccessibilityLabels.kt +15 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/accessibility/MarkdownAccessibilityHelper.kt +44 -7
- package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputManager.kt +52 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputView.kt +89 -11
- package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/FormattingStore.kt +19 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/InputParser.kt +22 -3
- package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/InputRemend.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/layout/InputMeasurementStore.kt +7 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/input/toolbar/InputContextMenu.kt +54 -13
- package/android/src/main/java/com/swmansion/enriched/markdown/parser/MarkdownASTNode.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +4 -4
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +30 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkEvents.kt +22 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt +36 -7
- package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +73 -3
- package/android/src/main/jni/CMakeLists.txt +15 -1
- package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +24 -2
- package/app.plugin.js +2 -1
- package/cpp/parser/MD4CParser.cpp +1 -1
- package/cpp/wasm/build.sh +9 -9
- package/docs/ACCESSIBILITY.md +172 -0
- package/docs/API_REFERENCE.md +948 -0
- package/docs/COPY_OPTIONS.md +127 -0
- package/docs/ELEMENTS_STRUCTURE.md +112 -0
- package/docs/IMAGE_CACHING.md +25 -0
- package/docs/INPUT.md +233 -0
- package/docs/LATEX_MATH.md +136 -0
- package/docs/MACOS.md +19 -0
- package/docs/MARKDOWN_STREAMING.md +36 -0
- package/docs/MENTIONS.md +97 -0
- package/docs/RTL.md +82 -0
- package/docs/STYLES.md +519 -0
- package/docs/TEXT.md +110 -0
- package/docs/WEB.md +49 -0
- package/ios/EnrichedMarkdown.mm +82 -25
- package/ios/EnrichedMarkdownText.mm +37 -5
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp +20 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +744 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +15 -0
- package/ios/input/ENRMFormattingStore.h +5 -0
- package/ios/input/ENRMFormattingStore.mm +34 -0
- package/ios/input/ENRMInputTextView.h +2 -0
- package/ios/input/ENRMInputTextView.mm +8 -17
- package/ios/input/ENRMMarkdownSerializer.mm +1 -1
- package/ios/input/EnrichedMarkdownTextInput+ContextMenu.mm +71 -53
- package/ios/input/EnrichedMarkdownTextInput+Internal.h +26 -0
- package/ios/input/EnrichedMarkdownTextInput.h +1 -0
- package/ios/input/EnrichedMarkdownTextInput.mm +230 -29
- package/ios/input/styles/ENRMBoldStyleHandler.mm +3 -2
- package/ios/input/styles/ENRMItalicStyleHandler.mm +3 -2
- package/ios/renderer/AttributedRenderer.m +51 -6
- package/ios/renderer/ENRMMathInlineRenderer.m +5 -1
- package/ios/styles/ENRMFontSlot.h +1 -1
- package/ios/utils/ENRMAccessibilityLabels.h +36 -0
- package/ios/utils/ENRMAccessibilityLabels.m +5 -0
- package/ios/utils/EditMenuUtils+macOS.m +4 -5
- package/ios/utils/EditMenuUtils.h +70 -0
- package/ios/utils/EditMenuUtils.m +14 -11
- package/ios/utils/LastElementUtils.h +39 -2
- package/ios/utils/MarkdownAccessibilityElementBuilder.h +18 -16
- package/ios/utils/MarkdownAccessibilityElementBuilder.m +64 -32
- package/ios/utils/ParagraphStyleUtils.m +21 -2
- package/ios/views/ENRMMathContainerView.h +8 -0
- package/ios/views/ENRMMathContainerView.m +7 -5
- package/ios/views/ENRMTableIOSGridView.h +40 -0
- package/ios/views/ENRMTableIOSGridView.m +216 -0
- package/ios/views/EnrichedMarkdownInternalText.h +2 -0
- package/ios/views/EnrichedMarkdownInternalText.m +2 -0
- package/ios/views/TableContainerView.h +8 -0
- package/ios/views/TableContainerView.m +72 -128
- package/lib/module/EnrichedMarkdownNativeComponent.ts +37 -0
- package/lib/module/EnrichedMarkdownTextInput.js +48 -1
- package/lib/module/EnrichedMarkdownTextInput.js.map +1 -1
- package/lib/module/EnrichedMarkdownTextInputNativeComponent.ts +35 -0
- package/lib/module/EnrichedMarkdownTextNativeComponent.ts +37 -0
- package/lib/module/accessibilityLabelDefaults.js +64 -0
- package/lib/module/accessibilityLabelDefaults.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/native/EnrichedMarkdownText.js +72 -4
- package/lib/module/native/EnrichedMarkdownText.js.map +1 -1
- package/lib/module/normalizeMenuItem.js +38 -0
- package/lib/module/normalizeMenuItem.js.map +1 -0
- package/lib/module/types/AccessibilityLabels.js +2 -0
- package/lib/module/types/AccessibilityLabels.js.map +1 -0
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +34 -0
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextInput.d.ts +55 -1
- package/lib/typescript/src/EnrichedMarkdownTextInput.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextInputNativeComponent.d.ts +30 -0
- package/lib/typescript/src/EnrichedMarkdownTextInputNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +34 -0
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/accessibilityLabelDefaults.d.ts +16 -0
- package/lib/typescript/src/accessibilityLabelDefaults.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +4 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/native/EnrichedMarkdownText.d.ts +3 -3
- package/lib/typescript/src/native/EnrichedMarkdownText.d.ts.map +1 -1
- package/lib/typescript/src/normalizeMenuItem.d.ts +7 -0
- package/lib/typescript/src/normalizeMenuItem.d.ts.map +1 -0
- package/lib/typescript/src/types/AccessibilityLabels.d.ts +114 -0
- package/lib/typescript/src/types/AccessibilityLabels.d.ts.map +1 -0
- package/lib/typescript/src/types/MarkdownTextProps.d.ts +78 -9
- package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
- package/package.json +4 -2
- package/plugin/build/withAndroidMath.js +19 -0
- package/plugin/build/withIosMath.js +28 -0
- package/plugin/build/withReactNativeEnrichedMarkdown.js +11 -0
- package/plugin/tsconfig.build.json +16 -0
- package/src/EnrichedMarkdownNativeComponent.ts +37 -0
- package/src/EnrichedMarkdownTextInput.tsx +117 -1
- package/src/EnrichedMarkdownTextInputNativeComponent.ts +35 -0
- package/src/EnrichedMarkdownTextNativeComponent.ts +37 -0
- package/src/accessibilityLabelDefaults.ts +83 -0
- package/src/index.tsx +11 -0
- package/src/native/EnrichedMarkdownText.tsx +120 -6
- package/src/normalizeMenuItem.ts +52 -0
- package/src/types/AccessibilityLabels.ts +118 -0
- package/src/types/MarkdownTextProps.ts +74 -9
- package/lib/module/plugin/withAndroidMath.js +0 -20
- package/lib/module/plugin/withAndroidMath.js.map +0 -1
- package/lib/module/plugin/withIosMath.js +0 -23
- package/lib/module/plugin/withIosMath.js.map +0 -1
- package/lib/module/plugin/withReactNativeEnrichedMarkdown.js +0 -16
- package/lib/module/plugin/withReactNativeEnrichedMarkdown.js.map +0 -1
- package/lib/typescript/src/plugin/withAndroidMath.d.ts +0 -5
- package/lib/typescript/src/plugin/withAndroidMath.d.ts.map +0 -1
- package/lib/typescript/src/plugin/withIosMath.d.ts +0 -5
- package/lib/typescript/src/plugin/withIosMath.d.ts.map +0 -1
- package/lib/typescript/src/plugin/withReactNativeEnrichedMarkdown.d.ts +0 -6
- package/lib/typescript/src/plugin/withReactNativeEnrichedMarkdown.d.ts.map +0 -1
- /package/{src/plugin → plugin/src}/withAndroidMath.ts +0 -0
- /package/{src/plugin → plugin/src}/withIosMath.ts +0 -0
- /package/{src/plugin → plugin/src}/withReactNativeEnrichedMarkdown.ts +0 -0
|
@@ -32,6 +32,8 @@ import com.swmansion.enriched.markdown.input.events.OnRequestMarkdownResultEvent
|
|
|
32
32
|
import com.swmansion.enriched.markdown.input.events.OnStartMentionEvent
|
|
33
33
|
import com.swmansion.enriched.markdown.input.layout.InputMeasurementStore
|
|
34
34
|
import com.swmansion.enriched.markdown.input.model.StyleType
|
|
35
|
+
import com.swmansion.enriched.markdown.input.toolbar.FormatMenuConfig
|
|
36
|
+
import com.swmansion.enriched.markdown.input.toolbar.InputSelectionMenuConfig
|
|
35
37
|
import com.swmansion.enriched.markdown.utils.input.BorderPropsApplicator
|
|
36
38
|
import com.swmansion.enriched.markdown.utils.input.MarkdownStyleParser
|
|
37
39
|
|
|
@@ -266,6 +268,52 @@ class EnrichedMarkdownTextInputManager :
|
|
|
266
268
|
view.setContextMenuItems(items)
|
|
267
269
|
}
|
|
268
270
|
|
|
271
|
+
@ReactProp(name = "selectionMenuConfig")
|
|
272
|
+
override fun setSelectionMenuConfig(
|
|
273
|
+
view: EnrichedMarkdownTextInputView?,
|
|
274
|
+
value: ReadableMap?,
|
|
275
|
+
) {
|
|
276
|
+
if (view == null) return
|
|
277
|
+
view.contextMenu.selectionMenuConfig =
|
|
278
|
+
if (value == null) {
|
|
279
|
+
InputSelectionMenuConfig()
|
|
280
|
+
} else {
|
|
281
|
+
InputSelectionMenuConfig(
|
|
282
|
+
format = value.getBoolean("format"),
|
|
283
|
+
formatLabel = value.getString("formatLabel") ?: "",
|
|
284
|
+
copyAsMarkdown = value.getBoolean("copyAsMarkdown"),
|
|
285
|
+
copyAsMarkdownLabel = value.getString("copyAsMarkdownLabel") ?: "",
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
@ReactProp(name = "formatMenuConfig")
|
|
291
|
+
override fun setFormatMenuConfig(
|
|
292
|
+
view: EnrichedMarkdownTextInputView?,
|
|
293
|
+
value: ReadableMap?,
|
|
294
|
+
) {
|
|
295
|
+
if (view == null) return
|
|
296
|
+
view.contextMenu.formatMenuConfig =
|
|
297
|
+
if (value == null) {
|
|
298
|
+
FormatMenuConfig()
|
|
299
|
+
} else {
|
|
300
|
+
FormatMenuConfig(
|
|
301
|
+
bold = value.getBoolean("bold"),
|
|
302
|
+
boldLabel = value.getString("boldLabel") ?: "",
|
|
303
|
+
italic = value.getBoolean("italic"),
|
|
304
|
+
italicLabel = value.getString("italicLabel") ?: "",
|
|
305
|
+
underline = value.getBoolean("underline"),
|
|
306
|
+
underlineLabel = value.getString("underlineLabel") ?: "",
|
|
307
|
+
strikethrough = value.getBoolean("strikethrough"),
|
|
308
|
+
strikethroughLabel = value.getString("strikethroughLabel") ?: "",
|
|
309
|
+
spoiler = value.getBoolean("spoiler"),
|
|
310
|
+
spoilerLabel = value.getString("spoilerLabel") ?: "",
|
|
311
|
+
link = value.getBoolean("link"),
|
|
312
|
+
linkLabel = value.getString("linkLabel") ?: "",
|
|
313
|
+
)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
269
317
|
@ReactProp(name = "linkRegex")
|
|
270
318
|
override fun setLinkRegex(
|
|
271
319
|
view: EnrichedMarkdownTextInputView?,
|
|
@@ -445,6 +493,10 @@ class EnrichedMarkdownTextInputManager :
|
|
|
445
493
|
view?.removeMath(start)
|
|
446
494
|
}
|
|
447
495
|
|
|
496
|
+
override fun copyToClipboard(view: EnrichedMarkdownTextInputView?) {
|
|
497
|
+
view?.copyToClipboard()
|
|
498
|
+
}
|
|
499
|
+
|
|
448
500
|
override fun requestMarkdown(
|
|
449
501
|
view: EnrichedMarkdownTextInputView?,
|
|
450
502
|
requestId: Int,
|
package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputView.kt
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
package com.swmansion.enriched.markdown.input
|
|
2
2
|
|
|
3
|
+
import android.content.ClipData
|
|
4
|
+
import android.content.ClipboardManager
|
|
3
5
|
import android.content.Context
|
|
4
6
|
import android.graphics.BlendMode
|
|
5
7
|
import android.graphics.BlendModeColorFilter
|
|
@@ -44,6 +46,8 @@ import com.swmansion.enriched.markdown.input.toolbar.InputContextMenu
|
|
|
44
46
|
import com.swmansion.enriched.markdown.utils.input.AutoCapitalizeUtils
|
|
45
47
|
import kotlin.math.ceil
|
|
46
48
|
|
|
49
|
+
private fun Char.isLineBreak(): Boolean = this == '\n' || this == '\r' || this == '\u0085' || this == '\u2028' || this == '\u2029'
|
|
50
|
+
|
|
47
51
|
class EnrichedMarkdownTextInputView(
|
|
48
52
|
context: Context,
|
|
49
53
|
) : AppCompatEditText(context) {
|
|
@@ -71,6 +75,7 @@ class EnrichedMarkdownTextInputView(
|
|
|
71
75
|
var autoFocusRequested = false
|
|
72
76
|
var stateWrapper: StateWrapper? = null
|
|
73
77
|
val layoutManager = InputLayoutManager(this)
|
|
78
|
+
private var pendingAutoFocusKeyboard = false
|
|
74
79
|
|
|
75
80
|
private var typefaceDirty = false
|
|
76
81
|
private var fontFamilyValue: String? = null
|
|
@@ -176,6 +181,13 @@ class EnrichedMarkdownTextInputView(
|
|
|
176
181
|
refreshAllMathChips()
|
|
177
182
|
}
|
|
178
183
|
|
|
184
|
+
override fun onWindowFocusChanged(hasWindowFocus: Boolean) {
|
|
185
|
+
super.onWindowFocusChanged(hasWindowFocus)
|
|
186
|
+
// The autofocus keyboard request may run before the window has IME focus (e.g. while a modal is
|
|
187
|
+
// still presenting), where showSoftInput() is dropped.
|
|
188
|
+
showAutoFocusKeyboardIfPending()
|
|
189
|
+
}
|
|
190
|
+
|
|
179
191
|
override fun clearFocus() {
|
|
180
192
|
super.clearFocus()
|
|
181
193
|
inputMethodManager?.hideSoftInputFromWindow(windowToken, 0)
|
|
@@ -312,6 +324,12 @@ class EnrichedMarkdownTextInputView(
|
|
|
312
324
|
if (!isComponentReady || isDuringTransaction) return
|
|
313
325
|
|
|
314
326
|
if (!isTextChanging) {
|
|
327
|
+
// Links (e.g. mentions) are atomic: snap a partial selection to the whole link, and a caret
|
|
328
|
+
// inside a link to its end. Returning lets the recursive onSelectionChanged emit for the result.
|
|
329
|
+
formattingStore.selectionAdjustedForAtomicLinks(selStart, selEnd)?.let { (newStart, newEnd) ->
|
|
330
|
+
setSelection(newStart, newEnd)
|
|
331
|
+
return
|
|
332
|
+
}
|
|
315
333
|
if (didTextChangeRecently) {
|
|
316
334
|
didTextChangeRecently = false
|
|
317
335
|
} else {
|
|
@@ -345,8 +363,18 @@ class EnrichedMarkdownTextInputView(
|
|
|
345
363
|
val rangeStart = if (preEditSelectionStart != preEditSelectionEnd) preEditSelectionStart else editStart
|
|
346
364
|
val rangeEnd = rangeStart + insertedLength
|
|
347
365
|
|
|
348
|
-
|
|
349
|
-
|
|
366
|
+
// Skip applying pending styles when the insertion is only line breaks —
|
|
367
|
+
// a phantom range over a bare newline corrupts isStyleActive() at the boundary.
|
|
368
|
+
val currentText = text
|
|
369
|
+
val insertedHasGlyphContent =
|
|
370
|
+
currentText != null &&
|
|
371
|
+
rangeEnd <= currentText.length &&
|
|
372
|
+
(rangeStart until rangeEnd).any { !currentText[it].isLineBreak() }
|
|
373
|
+
|
|
374
|
+
if (insertedHasGlyphContent) {
|
|
375
|
+
for (style in pendingStyles) {
|
|
376
|
+
formattingStore.addRange(FormattingRange(style, rangeStart, rangeEnd))
|
|
377
|
+
}
|
|
350
378
|
}
|
|
351
379
|
|
|
352
380
|
for (style in pendingStyleRemovals) {
|
|
@@ -439,6 +467,16 @@ class EnrichedMarkdownTextInputView(
|
|
|
439
467
|
}
|
|
440
468
|
}
|
|
441
469
|
|
|
470
|
+
// Copies the input's plain text to the system clipboard without disturbing
|
|
471
|
+
// the current selection. Android's system clipboard round-trip is plain text
|
|
472
|
+
// only — inline styles are not preserved for external paste targets.
|
|
473
|
+
fun copyToClipboard() {
|
|
474
|
+
val content = text
|
|
475
|
+
if (content.isNullOrEmpty()) return
|
|
476
|
+
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager ?: return
|
|
477
|
+
clipboard.setPrimaryClip(ClipData.newPlainText(null, content))
|
|
478
|
+
}
|
|
479
|
+
|
|
442
480
|
fun setLinkForSelection(url: String) {
|
|
443
481
|
val selStart = selectionStart
|
|
444
482
|
val selEnd = selectionEnd
|
|
@@ -810,18 +848,36 @@ class EnrichedMarkdownTextInputView(
|
|
|
810
848
|
return true
|
|
811
849
|
}
|
|
812
850
|
|
|
813
|
-
|
|
851
|
+
/** Chip glyph rect in [Layout] coordinates — no padding, no scroll offset. */
|
|
852
|
+
private fun chipBoundsInLayout(
|
|
814
853
|
layout: Layout,
|
|
815
854
|
start: Int,
|
|
816
855
|
): Rect {
|
|
817
856
|
val line = layout.getLineForOffset(start)
|
|
818
|
-
val xStart =
|
|
819
|
-
val xEnd =
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
857
|
+
val xStart = layout.getPrimaryHorizontal(start).toInt()
|
|
858
|
+
val xEnd = layout.getPrimaryHorizontal(start + 1).toInt()
|
|
859
|
+
// Ordered, not raw start/end: in an RTL run xStart > xEnd, which would make an
|
|
860
|
+
// inverted Rect that contains() always rejects.
|
|
861
|
+
val left = xStart.coerceAtMost(xEnd)
|
|
862
|
+
var right = xStart.coerceAtLeast(xEnd)
|
|
863
|
+
if (right <= left) {
|
|
864
|
+
// A chip that is the last character has start + 1 == length, where some layouts
|
|
865
|
+
// report the same horizontal for both offsets. Fall back to the line's content edge
|
|
866
|
+
// so the rect keeps a real width instead of collapsing.
|
|
867
|
+
right = layout.getLineRight(line).toInt()
|
|
868
|
+
}
|
|
869
|
+
return Rect(left, layout.getLineTop(line), right, layout.getLineBottom(line))
|
|
823
870
|
}
|
|
824
871
|
|
|
872
|
+
/** Chip glyph rect in view coordinates, for reporting to JS. */
|
|
873
|
+
private fun computeChipBounds(
|
|
874
|
+
layout: Layout,
|
|
875
|
+
start: Int,
|
|
876
|
+
): Rect =
|
|
877
|
+
chipBoundsInLayout(layout, start).apply {
|
|
878
|
+
offset(totalPaddingLeft - scrollX, totalPaddingTop - scrollY)
|
|
879
|
+
}
|
|
880
|
+
|
|
825
881
|
fun mathChipPositionAtTouch(
|
|
826
882
|
x: Float,
|
|
827
883
|
y: Float,
|
|
@@ -831,13 +887,21 @@ class EnrichedMarkdownTextInputView(
|
|
|
831
887
|
val layoutNow = layout ?: return -1
|
|
832
888
|
val adjustedX = (x - totalPaddingLeft + scrollX).toInt()
|
|
833
889
|
val adjustedY = (y - totalPaddingTop + scrollY).toInt()
|
|
834
|
-
|
|
890
|
+
// getLineForVertical and getOffsetForHorizontal both clamp into the laid-out text, so
|
|
891
|
+
// a touch in the padding or in the blank space below the last line resolves onto the
|
|
892
|
+
// nearest character. Bound the touch to the text box, then require it to land inside
|
|
893
|
+
// the chip's own rect — otherwise a tap beside a chip reads as a tap on it.
|
|
894
|
+
if (adjustedY < 0 || adjustedY > layoutNow.height) return -1
|
|
835
895
|
val line = layoutNow.getLineForVertical(adjustedY)
|
|
836
896
|
val offset = layoutNow.getOffsetForHorizontal(line, adjustedX.toFloat())
|
|
837
|
-
if (offset < 0
|
|
897
|
+
if (offset < 0) return -1
|
|
838
898
|
val candidates = intArrayOf(offset, offset - 1).filter { it in 0 until editable.length }
|
|
839
899
|
for (candidate in candidates) {
|
|
840
900
|
val range = controller.chipRangeAt(editable, candidate) ?: continue
|
|
901
|
+
// An empty rect means the layout could not give us the chip's extent; accept the
|
|
902
|
+
// candidate then, rather than making the chip untappable.
|
|
903
|
+
val bounds = chipBoundsInLayout(layoutNow, range.first)
|
|
904
|
+
if (!bounds.isEmpty && !bounds.contains(adjustedX, adjustedY)) continue
|
|
841
905
|
return range.first
|
|
842
906
|
}
|
|
843
907
|
return -1
|
|
@@ -954,11 +1018,25 @@ class EnrichedMarkdownTextInputView(
|
|
|
954
1018
|
setSelection(selectionStart.coerceAtLeast(0))
|
|
955
1019
|
}
|
|
956
1020
|
|
|
1021
|
+
private fun showAutoFocusKeyboardIfPending() {
|
|
1022
|
+
if (!pendingAutoFocusKeyboard || !hasWindowFocus()) return
|
|
1023
|
+
pendingAutoFocusKeyboard = false
|
|
1024
|
+
inputMethodManager?.showSoftInput(this, 0)
|
|
1025
|
+
}
|
|
1026
|
+
|
|
957
1027
|
fun afterUpdateTransaction() {
|
|
958
1028
|
updateTypeface()
|
|
959
1029
|
if (autoFocusRequested) {
|
|
960
1030
|
autoFocusRequested = false
|
|
961
|
-
|
|
1031
|
+
pendingAutoFocusKeyboard = true
|
|
1032
|
+
post {
|
|
1033
|
+
// afterUpdateTransaction runs before onAttachedToWindow, where requestFocus()/showSoftInput()
|
|
1034
|
+
// are dropped and setTextIsSelectable(true) would reset the caret to 0. Defer to the next loop
|
|
1035
|
+
// so focus sticks and the caret lands at end (matching iOS).
|
|
1036
|
+
requestFocus()
|
|
1037
|
+
setSelection(text?.length ?: 0)
|
|
1038
|
+
showAutoFocusKeyboardIfPending()
|
|
1039
|
+
}
|
|
962
1040
|
}
|
|
963
1041
|
}
|
|
964
1042
|
|
package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/FormattingStore.kt
CHANGED
|
@@ -34,6 +34,25 @@ class FormattingStore {
|
|
|
34
34
|
end: Int,
|
|
35
35
|
): Boolean = ranges.any { it.type == type && it.start < end && it.end > start }
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Snaps a selection so it never partially overlaps an atomic link: a partial selection expands to
|
|
39
|
+
* the whole link, a caret inside a link moves to its end. Returns the adjusted (start, end) or null.
|
|
40
|
+
*/
|
|
41
|
+
fun selectionAdjustedForAtomicLinks(
|
|
42
|
+
start: Int,
|
|
43
|
+
end: Int,
|
|
44
|
+
): Pair<Int, Int>? {
|
|
45
|
+
if (start != end) {
|
|
46
|
+
var newStart = start
|
|
47
|
+
var newEnd = end
|
|
48
|
+
rangeOfType(StyleType.LINK, newStart)?.let { newStart = minOf(newStart, it.start) }
|
|
49
|
+
if (newEnd > 0) rangeOfType(StyleType.LINK, newEnd - 1)?.let { newEnd = maxOf(newEnd, it.end) }
|
|
50
|
+
return if (newStart != start || newEnd != end) Pair(newStart, newEnd) else null
|
|
51
|
+
}
|
|
52
|
+
val link = rangeOfType(StyleType.LINK, start)
|
|
53
|
+
return if (link != null && start > link.start && start < link.end) Pair(link.end, link.end) else null
|
|
54
|
+
}
|
|
55
|
+
|
|
37
56
|
fun addRange(newRange: FormattingRange) {
|
|
38
57
|
var mergedStart = newRange.start
|
|
39
58
|
var mergedEnd = newRange.end
|
package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/InputParser.kt
CHANGED
|
@@ -7,6 +7,7 @@ import com.swmansion.enriched.markdown.parser.MarkdownASTNode
|
|
|
7
7
|
import com.swmansion.enriched.markdown.parser.MarkdownASTNode.NodeType
|
|
8
8
|
import com.swmansion.enriched.markdown.parser.Md4cFlags
|
|
9
9
|
import com.swmansion.enriched.markdown.parser.Parser
|
|
10
|
+
import com.swmansion.enriched.markdown.parser.isTopLevelBlock
|
|
10
11
|
import com.swmansion.enriched.markdown.utils.common.FeatureFlags
|
|
11
12
|
|
|
12
13
|
data class ParseResult(
|
|
@@ -18,6 +19,9 @@ data class ParseResult(
|
|
|
18
19
|
object InputParser {
|
|
19
20
|
private const val ATTACHMENT_CHAR = ''
|
|
20
21
|
|
|
22
|
+
// A source paragraph break: a newline followed by one or more blank lines.
|
|
23
|
+
private val PARAGRAPH_BREAK_RE = Regex("\\n[ \\t]*(?:\\n[ \\t]*)+")
|
|
24
|
+
|
|
21
25
|
fun parseToPlainTextAndRanges(markdown: String): ParseResult {
|
|
22
26
|
if (markdown.isEmpty()) {
|
|
23
27
|
return ParseResult("", emptyList())
|
|
@@ -36,7 +40,17 @@ object InputParser {
|
|
|
36
40
|
val ranges = mutableListOf<FormattingRange>()
|
|
37
41
|
val mathAtoms = mutableListOf<MathAtom>()
|
|
38
42
|
|
|
39
|
-
|
|
43
|
+
// md4c collapses any blank-line run into a single break, unlike iOS which keeps them.
|
|
44
|
+
// Re-read the real runs so each break replays its original number of newlines.
|
|
45
|
+
val blankRuns =
|
|
46
|
+
ArrayDeque(
|
|
47
|
+
PARAGRAPH_BREAK_RE
|
|
48
|
+
.findAll(completed.trim())
|
|
49
|
+
.map { match -> match.value.count { it == '\n' } }
|
|
50
|
+
.toList(),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
walkNode(ast, plainText, ranges, mathAtoms, ArrayDeque(), blankRuns)
|
|
40
54
|
|
|
41
55
|
return ParseResult(plainText.toString(), ranges, mathAtoms)
|
|
42
56
|
}
|
|
@@ -47,6 +61,7 @@ object InputParser {
|
|
|
47
61
|
ranges: MutableList<FormattingRange>,
|
|
48
62
|
mathAtoms: MutableList<MathAtom>,
|
|
49
63
|
activeStyles: ArrayDeque<ActiveStyle>,
|
|
64
|
+
blankRuns: ArrayDeque<Int>,
|
|
50
65
|
) {
|
|
51
66
|
if (node.type == NodeType.LatexMathInline || node.type == NodeType.LatexMathDisplay) {
|
|
52
67
|
val latex = extractLatex(node)
|
|
@@ -75,8 +90,12 @@ object InputParser {
|
|
|
75
90
|
plainText.append("\n")
|
|
76
91
|
}
|
|
77
92
|
|
|
78
|
-
for (child in node.children) {
|
|
79
|
-
|
|
93
|
+
for ((index, child) in node.children.withIndex()) {
|
|
94
|
+
// Keep the source's blank lines between top-level blocks (md4c drops them, iOS keeps them).
|
|
95
|
+
if (index > 0 && child.type.isTopLevelBlock() && plainText.isNotEmpty()) {
|
|
96
|
+
plainText.append("\n".repeat(blankRuns.removeFirstOrNull() ?: 2))
|
|
97
|
+
}
|
|
98
|
+
walkNode(child, plainText, ranges, mathAtoms, activeStyles, blankRuns)
|
|
80
99
|
}
|
|
81
100
|
|
|
82
101
|
if (styleType != null) {
|
package/android/src/main/java/com/swmansion/enriched/markdown/input/layout/InputMeasurementStore.kt
CHANGED
|
@@ -9,6 +9,7 @@ import com.facebook.react.bridge.ReadableMap
|
|
|
9
9
|
import com.facebook.react.uimanager.PixelUtil
|
|
10
10
|
import com.facebook.yoga.YogaMeasureMode
|
|
11
11
|
import com.facebook.yoga.YogaMeasureOutput
|
|
12
|
+
import com.swmansion.enriched.markdown.input.formatting.InputParser
|
|
12
13
|
import java.util.concurrent.ConcurrentHashMap
|
|
13
14
|
|
|
14
15
|
object InputMeasurementStore {
|
|
@@ -93,7 +94,12 @@ object InputMeasurementStore {
|
|
|
93
94
|
width: Float,
|
|
94
95
|
props: ReadableMap?,
|
|
95
96
|
): Long {
|
|
96
|
-
|
|
97
|
+
// Measure the rendered plain text, not the raw markdown. A mention link such as
|
|
98
|
+
// [Label](placeholder://x) hides its URL in the source, so measuring the markdown counts those
|
|
99
|
+
// invisible characters and over-estimates the height into extra lines, until the next text
|
|
100
|
+
// change forces a re-measure. Parsing first matches what the editor actually renders.
|
|
101
|
+
val markdown = props?.getString("defaultValue") ?: props?.getString("placeholder") ?: "I"
|
|
102
|
+
val text = InputParser.parseToPlainTextAndRanges(markdown).plainText
|
|
97
103
|
val fontSize = props?.getDouble("fontSize")?.toFloat() ?: 16f
|
|
98
104
|
val spSize = kotlin.math.ceil(PixelUtil.toPixelFromSP(fontSize).toDouble()).toFloat()
|
|
99
105
|
|
package/android/src/main/java/com/swmansion/enriched/markdown/input/toolbar/InputContextMenu.kt
CHANGED
|
@@ -14,12 +14,34 @@ import com.swmansion.enriched.markdown.input.formatting.MarkdownSerializer
|
|
|
14
14
|
import com.swmansion.enriched.markdown.input.model.FormattingRange
|
|
15
15
|
import com.swmansion.enriched.markdown.input.model.StyleType
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
data class InputSelectionMenuConfig(
|
|
18
|
+
val format: Boolean = true,
|
|
19
|
+
val formatLabel: String = "",
|
|
20
|
+
val copyAsMarkdown: Boolean = true,
|
|
21
|
+
val copyAsMarkdownLabel: String = "",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
data class FormatMenuConfig(
|
|
25
|
+
val bold: Boolean = true,
|
|
26
|
+
val boldLabel: String = "",
|
|
27
|
+
val italic: Boolean = true,
|
|
28
|
+
val italicLabel: String = "",
|
|
29
|
+
val underline: Boolean = true,
|
|
30
|
+
val underlineLabel: String = "",
|
|
31
|
+
val strikethrough: Boolean = true,
|
|
32
|
+
val strikethroughLabel: String = "",
|
|
33
|
+
val spoiler: Boolean = true,
|
|
34
|
+
val spoilerLabel: String = "",
|
|
35
|
+
val link: Boolean = true,
|
|
36
|
+
val linkLabel: String = "",
|
|
37
|
+
)
|
|
18
38
|
|
|
19
39
|
class InputContextMenu(
|
|
20
40
|
private val view: EnrichedMarkdownTextInputView,
|
|
21
41
|
) {
|
|
22
42
|
private var customItemTexts: List<String> = emptyList()
|
|
43
|
+
var selectionMenuConfig: InputSelectionMenuConfig = InputSelectionMenuConfig()
|
|
44
|
+
var formatMenuConfig: FormatMenuConfig = FormatMenuConfig()
|
|
23
45
|
|
|
24
46
|
fun setContextMenuItems(items: List<String>) {
|
|
25
47
|
customItemTexts = items
|
|
@@ -42,13 +64,20 @@ class InputContextMenu(
|
|
|
42
64
|
menu.removeGroup(FORMAT_MENU_GROUP_ID)
|
|
43
65
|
menu.removeGroup(CUSTOM_MENU_GROUP_ID)
|
|
44
66
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
67
|
+
if (selectionMenuConfig.format) {
|
|
68
|
+
val formatSubMenu =
|
|
69
|
+
menu.addSubMenu(FORMAT_MENU_GROUP_ID, MENU_FORMAT_ID, 100, selectionMenuConfig.formatLabel)
|
|
70
|
+
FORMAT_ITEMS.forEachIndexed { index, (styleType, labelOf) ->
|
|
71
|
+
if (isFormatItemVisible(styleType)) {
|
|
72
|
+
formatSubMenu.add(Menu.NONE, MENU_FORMAT_ITEM_BASE + index, index, labelOf(formatMenuConfig))
|
|
73
|
+
}
|
|
74
|
+
}
|
|
48
75
|
}
|
|
49
76
|
|
|
50
77
|
if (view.selectionStart < view.selectionEnd) {
|
|
51
|
-
|
|
78
|
+
if (selectionMenuConfig.copyAsMarkdown) {
|
|
79
|
+
menu.add(FORMAT_MENU_GROUP_ID, MENU_COPY_MARKDOWN_ID, 101, selectionMenuConfig.copyAsMarkdownLabel)
|
|
80
|
+
}
|
|
52
81
|
|
|
53
82
|
customItemTexts.forEachIndexed { index, text ->
|
|
54
83
|
menu
|
|
@@ -74,7 +103,7 @@ class InputContextMenu(
|
|
|
74
103
|
|
|
75
104
|
val formatIndex = itemId - MENU_FORMAT_ITEM_BASE
|
|
76
105
|
if (formatIndex in FORMAT_ITEMS.indices) {
|
|
77
|
-
applyFormat(FORMAT_ITEMS[formatIndex].
|
|
106
|
+
applyFormat(FORMAT_ITEMS[formatIndex].first)
|
|
78
107
|
mode.finish()
|
|
79
108
|
return true
|
|
80
109
|
}
|
|
@@ -96,6 +125,16 @@ class InputContextMenu(
|
|
|
96
125
|
}
|
|
97
126
|
}
|
|
98
127
|
|
|
128
|
+
private fun isFormatItemVisible(styleType: StyleType): Boolean =
|
|
129
|
+
when (styleType) {
|
|
130
|
+
StyleType.BOLD -> formatMenuConfig.bold
|
|
131
|
+
StyleType.ITALIC -> formatMenuConfig.italic
|
|
132
|
+
StyleType.UNDERLINE -> formatMenuConfig.underline
|
|
133
|
+
StyleType.STRIKETHROUGH -> formatMenuConfig.strikethrough
|
|
134
|
+
StyleType.SPOILER -> formatMenuConfig.spoiler
|
|
135
|
+
StyleType.LINK -> formatMenuConfig.link
|
|
136
|
+
}
|
|
137
|
+
|
|
99
138
|
private fun applyFormat(styleType: StyleType) {
|
|
100
139
|
val start = view.selectionStart
|
|
101
140
|
val end = view.selectionEnd
|
|
@@ -168,14 +207,16 @@ class InputContextMenu(
|
|
|
168
207
|
private const val CUSTOM_MENU_GROUP_ID = 2000
|
|
169
208
|
private const val MENU_CUSTOM_BASE = 2001
|
|
170
209
|
|
|
171
|
-
|
|
210
|
+
// Order here defines submenu order and the index used by
|
|
211
|
+
// `MENU_FORMAT_ITEM_BASE + index` when handling clicks.
|
|
212
|
+
private val FORMAT_ITEMS: List<Pair<StyleType, (FormatMenuConfig) -> String>> =
|
|
172
213
|
listOf(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
214
|
+
StyleType.BOLD to { it.boldLabel },
|
|
215
|
+
StyleType.ITALIC to { it.italicLabel },
|
|
216
|
+
StyleType.UNDERLINE to { it.underlineLabel },
|
|
217
|
+
StyleType.STRIKETHROUGH to { it.strikethroughLabel },
|
|
218
|
+
StyleType.SPOILER to { it.spoilerLabel },
|
|
219
|
+
StyleType.LINK to { it.linkLabel },
|
|
179
220
|
)
|
|
180
221
|
}
|
|
181
222
|
}
|
|
@@ -41,3 +41,20 @@ data class MarkdownASTNode(
|
|
|
41
41
|
|
|
42
42
|
fun getAttribute(key: String): String? = attributes[key]
|
|
43
43
|
}
|
|
44
|
+
|
|
45
|
+
// A node type that md4c emits as a standalone block stacked vertically, as opposed to an inline span.
|
|
46
|
+
internal fun MarkdownASTNode.NodeType.isTopLevelBlock(): Boolean =
|
|
47
|
+
when (this) {
|
|
48
|
+
MarkdownASTNode.NodeType.Paragraph,
|
|
49
|
+
MarkdownASTNode.NodeType.Heading,
|
|
50
|
+
MarkdownASTNode.NodeType.Blockquote,
|
|
51
|
+
MarkdownASTNode.NodeType.UnorderedList,
|
|
52
|
+
MarkdownASTNode.NodeType.OrderedList,
|
|
53
|
+
MarkdownASTNode.NodeType.CodeBlock,
|
|
54
|
+
MarkdownASTNode.NodeType.ThematicBreak,
|
|
55
|
+
MarkdownASTNode.NodeType.Table,
|
|
56
|
+
MarkdownASTNode.NodeType.LatexMathDisplay,
|
|
57
|
+
-> true
|
|
58
|
+
|
|
59
|
+
else -> false
|
|
60
|
+
}
|
|
@@ -52,10 +52,10 @@ class SpanStyleCache(
|
|
|
52
52
|
val paragraphColor = style.paragraphStyle.color
|
|
53
53
|
return buildList {
|
|
54
54
|
style.strongStyle.color
|
|
55
|
-
?.takeIf { it != 0 }
|
|
55
|
+
?.takeIf { it != 0 && it != paragraphColor }
|
|
56
56
|
?.let { add(it) }
|
|
57
57
|
style.emphasisStyle.color
|
|
58
|
-
?.takeIf { it != 0 }
|
|
58
|
+
?.takeIf { it != 0 && it != paragraphColor }
|
|
59
59
|
?.let { add(it) }
|
|
60
60
|
style.linkStyle.color
|
|
61
61
|
.takeIf { it != 0 && it != paragraphColor }
|
|
@@ -68,10 +68,10 @@ class SpanStyleCache(
|
|
|
68
68
|
style
|
|
69
69
|
.codeStyle
|
|
70
70
|
.color
|
|
71
|
-
.takeIf { it != 0 }
|
|
71
|
+
.takeIf { it != 0 && it != paragraphColor }
|
|
72
72
|
?.let { add(it) }
|
|
73
73
|
style.taskListStyle.checkedTextColor
|
|
74
|
-
.takeIf { it != 0 }
|
|
74
|
+
.takeIf { it != 0 && it != paragraphColor }
|
|
75
75
|
?.let { add(it) }
|
|
76
76
|
}.toIntArray()
|
|
77
77
|
}
|
|
@@ -4,6 +4,7 @@ import android.view.View
|
|
|
4
4
|
import com.facebook.react.bridge.ReadableArray
|
|
5
5
|
import com.facebook.react.bridge.ReadableMap
|
|
6
6
|
import com.facebook.react.uimanager.UIManagerHelper
|
|
7
|
+
import com.swmansion.enriched.markdown.accessibility.AccessibilityLabels
|
|
7
8
|
import com.swmansion.enriched.markdown.events.ContextMenuItemPressEvent
|
|
8
9
|
import com.swmansion.enriched.markdown.events.LinkLongPressEvent
|
|
9
10
|
import com.swmansion.enriched.markdown.events.LinkPressEvent
|
|
@@ -96,5 +97,34 @@ fun parseSelectionMenuConfig(value: ReadableMap?): SelectionMenuConfig {
|
|
|
96
97
|
return SelectionMenuConfig(
|
|
97
98
|
copyAsMarkdown = value.getBoolean("copyAsMarkdown"),
|
|
98
99
|
copyImageUrl = value.getBoolean("copyImageUrl"),
|
|
100
|
+
copyLabel = value.getString("copyLabel") ?: "",
|
|
101
|
+
copyAsMarkdownLabel = value.getString("copyAsMarkdownLabel") ?: "",
|
|
102
|
+
copyImageUrlLabel = value.getString("copyImageUrlLabel") ?: "",
|
|
103
|
+
copyImageUrlsLabel = value.getString("copyImageUrlsLabel") ?: "",
|
|
104
|
+
copyImageUrlPluralTemplates = parseStringList(value.getArray("copyImageUrlPluralTemplates")),
|
|
99
105
|
)
|
|
100
106
|
}
|
|
107
|
+
|
|
108
|
+
fun parseAccessibilityLabels(value: ReadableMap?): AccessibilityLabels {
|
|
109
|
+
if (value == null) return AccessibilityLabels()
|
|
110
|
+
val list = value.getMap("list")
|
|
111
|
+
val blockquote = value.getMap("blockquote")
|
|
112
|
+
val table = value.getMap("table")
|
|
113
|
+
val math = value.getMap("math")
|
|
114
|
+
val rotor = value.getMap("rotor")
|
|
115
|
+
return AccessibilityLabels(
|
|
116
|
+
bulletPoint = list?.getString("bulletPoint") ?: "",
|
|
117
|
+
nestedBulletPoint = list?.getString("nestedBulletPoint") ?: "",
|
|
118
|
+
orderedItem = list?.getString("orderedItem") ?: "",
|
|
119
|
+
nestedOrderedItem = list?.getString("nestedOrderedItem") ?: "",
|
|
120
|
+
blockquote = blockquote?.getString("quote") ?: "",
|
|
121
|
+
nestedBlockquote = blockquote?.getString("nestedQuote") ?: "",
|
|
122
|
+
tableRow = table?.getString("row") ?: "",
|
|
123
|
+
mathEquation = math?.getString("equation") ?: "",
|
|
124
|
+
rotorHeadings = rotor?.getString("headings") ?: "",
|
|
125
|
+
rotorLinks = rotor?.getString("links") ?: "",
|
|
126
|
+
rotorImages = rotor?.getString("images") ?: "",
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private fun parseStringList(value: ReadableArray?): List<String> = (0 until (value?.size() ?: 0)).mapNotNull { value?.getString(it) }
|
|
@@ -26,18 +26,40 @@ fun View.emitLinkLongPressEvent(url: String) {
|
|
|
26
26
|
/**
|
|
27
27
|
* Cancels the JS touch for an active link tap, preventing parent
|
|
28
28
|
* Pressable/TouchableOpacity from firing onPress for the same tap.
|
|
29
|
+
*
|
|
30
|
+
* Also requests that parent ViewGroups do not intercept subsequent touch
|
|
31
|
+
* events in this gesture. This is necessary for react-native-gesture-handler's
|
|
32
|
+
* Pressable, which intercepts at the native ViewGroup level and would otherwise
|
|
33
|
+
* steal the gesture before ACTION_UP reaches the TextView.
|
|
29
34
|
*/
|
|
30
35
|
fun TextView.cancelJSTouchForLinkTap(event: MotionEvent) {
|
|
31
36
|
val currentMovementMethod = movementMethod
|
|
32
37
|
if (currentMovementMethod is LinkLongPressMovementMethod && currentMovementMethod.isLinkTouchActive) {
|
|
38
|
+
parent?.requestDisallowInterceptTouchEvent(true)
|
|
33
39
|
NativeGestureUtil.notifyNativeGestureStarted(this, event)
|
|
34
40
|
}
|
|
35
41
|
}
|
|
36
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Re-allows parent ViewGroups to intercept touch events once the link
|
|
45
|
+
* touch is no longer active (slop exceeded, gesture ended, or cancelled).
|
|
46
|
+
* This restores normal scrolling behavior for parent ScrollView/RecyclerView.
|
|
47
|
+
*
|
|
48
|
+
* Must be called after [LinkLongPressMovementMethod.onTouchEvent] has
|
|
49
|
+
* processed the event, since that is where [isLinkTouchActive] is updated.
|
|
50
|
+
*/
|
|
51
|
+
fun TextView.reallowParentInterceptIfLinkReleased() {
|
|
52
|
+
val currentMovementMethod = movementMethod
|
|
53
|
+
if (currentMovementMethod is LinkLongPressMovementMethod && !currentMovementMethod.isLinkTouchActive) {
|
|
54
|
+
parent?.requestDisallowInterceptTouchEvent(false)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
37
58
|
/**
|
|
38
59
|
* Cancels the JS touch unconditionally, preventing parent
|
|
39
60
|
* Pressable/TouchableOpacity from firing onPress for the same gesture.
|
|
40
61
|
*/
|
|
41
62
|
fun View.cancelJSTouchForCheckboxTap(event: MotionEvent) {
|
|
63
|
+
parent?.requestDisallowInterceptTouchEvent(true)
|
|
42
64
|
NativeGestureUtil.notifyNativeGestureStarted(this, event)
|
|
43
65
|
}
|