react-native-enriched-markdown-scaffold 1.0.2-scaffold.4 → 1.0.2-scaffold.6

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.
Files changed (71) hide show
  1. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerDelegate.java +15 -0
  2. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerInterface.java +3 -0
  3. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +15 -0
  4. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +3 -0
  5. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +74 -0
  6. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +68 -0
  7. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +20 -0
  8. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +4 -0
  9. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +107 -0
  10. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +19 -0
  11. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +22 -0
  12. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +48 -0
  13. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +22 -0
  14. package/android/src/main/java/com/swmansion/enriched/markdown/events/SelectionChangeEvent.kt +42 -0
  15. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +14 -0
  16. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionChangeReporter.kt +230 -0
  17. package/android/src/main/java/com/swmansion/enriched/markdown/views/CodeBlockContainerView.kt +11 -1
  18. package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +8 -1
  19. package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +8 -1
  20. package/docs/API_REFERENCE.md +68 -0
  21. package/docs/WEB.md +1 -0
  22. package/ios/EnrichedMarkdown.mm +188 -2
  23. package/ios/EnrichedMarkdownText.mm +117 -1
  24. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.cpp +74 -0
  25. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.h +68 -0
  26. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp +20 -0
  27. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +4 -0
  28. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +50 -2
  29. package/ios/utils/ENRMLinkChip.m +38 -20
  30. package/ios/utils/ENRMLinkLayout.m +12 -12
  31. package/ios/utils/ENRMSelectionChange.h +96 -0
  32. package/ios/utils/ENRMSelectionChange.m +204 -0
  33. package/ios/utils/EditMenuUtils+macOS.m +1 -1
  34. package/ios/utils/EditMenuUtils.h +3 -0
  35. package/ios/utils/EditMenuUtils.m +116 -0
  36. package/ios/utils/MarkdownAccessibilityElementBuilder.m +1 -2
  37. package/ios/views/ENRMCodeBlockContainerView.h +3 -0
  38. package/ios/views/ENRMCodeBlockContainerView.m +3 -0
  39. package/ios/views/ENRMMathContainerView.h +3 -0
  40. package/ios/views/ENRMMathContainerView.m +3 -0
  41. package/ios/views/TableContainerView.h +3 -0
  42. package/ios/views/TableContainerView.m +4 -1
  43. package/lib/module/EnrichedMarkdownNativeComponent.ts +70 -1
  44. package/lib/module/EnrichedMarkdownTextNativeComponent.ts +70 -1
  45. package/lib/module/index.js.map +1 -1
  46. package/lib/module/jest/index.js +14 -0
  47. package/lib/module/jest/index.js.map +1 -1
  48. package/lib/module/native/EnrichedMarkdownText.js +46 -4
  49. package/lib/module/native/EnrichedMarkdownText.js.map +1 -1
  50. package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +63 -2
  51. package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
  52. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +63 -2
  53. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
  54. package/lib/typescript/src/index.d.ts +2 -2
  55. package/lib/typescript/src/index.d.ts.map +1 -1
  56. package/lib/typescript/src/jest/index.d.ts +1 -1
  57. package/lib/typescript/src/jest/index.d.ts.map +1 -1
  58. package/lib/typescript/src/native/EnrichedMarkdownText.d.ts +5 -5
  59. package/lib/typescript/src/native/EnrichedMarkdownText.d.ts.map +1 -1
  60. package/lib/typescript/src/types/MarkdownTextProps.d.ts +46 -3
  61. package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
  62. package/lib/typescript/src/types/events.d.ts +36 -0
  63. package/lib/typescript/src/types/events.d.ts.map +1 -1
  64. package/package.json +1 -1
  65. package/src/EnrichedMarkdownNativeComponent.ts +70 -1
  66. package/src/EnrichedMarkdownTextNativeComponent.ts +70 -1
  67. package/src/index.tsx +3 -0
  68. package/src/jest/index.tsx +22 -1
  69. package/src/native/EnrichedMarkdownText.tsx +87 -9
  70. package/src/types/MarkdownTextProps.ts +53 -2
  71. package/src/types/events.ts +38 -0
@@ -35,12 +35,18 @@ public class EnrichedMarkdownManagerDelegate<T extends View, U extends BaseViewM
35
35
  case "emitLinkLayout":
36
36
  mViewManager.setEmitLinkLayout(view, value == null ? false : (boolean) value);
37
37
  break;
38
+ case "emitSelectionChange":
39
+ mViewManager.setEmitSelectionChange(view, value == null ? false : (boolean) value);
40
+ break;
38
41
  case "enableTaskListItemToggle":
39
42
  mViewManager.setEnableTaskListItemToggle(view, value == null ? true : (boolean) value);
40
43
  break;
41
44
  case "enableLinkPreview":
42
45
  mViewManager.setEnableLinkPreview(view, value == null ? true : (boolean) value);
43
46
  break;
47
+ case "linkLongPressSelects":
48
+ mViewManager.setLinkLongPressSelects(view, value == null ? false : (boolean) value);
49
+ break;
44
50
  case "selectable":
45
51
  mViewManager.setSelectable(view, value == null ? false : (boolean) value);
46
52
  break;
@@ -99,4 +105,13 @@ public class EnrichedMarkdownManagerDelegate<T extends View, U extends BaseViewM
99
105
  super.setProperty(view, propName, value);
100
106
  }
101
107
  }
108
+
109
+ @Override
110
+ public void receiveCommand(T view, String commandName, ReadableArray args) {
111
+ switch (commandName) {
112
+ case "clearSelection":
113
+ mViewManager.clearSelection(view);
114
+ break;
115
+ }
116
+ }
102
117
  }
@@ -19,8 +19,10 @@ public interface EnrichedMarkdownManagerInterface<T extends View> extends ViewMa
19
19
  void setMarkdown(T view, @Nullable String value);
20
20
  void setMarkdownStyle(T view, @Nullable ReadableMap value);
21
21
  void setEmitLinkLayout(T view, boolean value);
22
+ void setEmitSelectionChange(T view, boolean value);
22
23
  void setEnableTaskListItemToggle(T view, boolean value);
23
24
  void setEnableLinkPreview(T view, boolean value);
25
+ void setLinkLongPressSelects(T view, boolean value);
24
26
  void setSelectable(T view, boolean value);
25
27
  void setSelectionColor(T view, @Nullable Integer value);
26
28
  void setSelectionHandleColor(T view, @Nullable Integer value);
@@ -39,4 +41,5 @@ public interface EnrichedMarkdownManagerInterface<T extends View> extends ViewMa
39
41
  void setTextBreakStrategy(T view, @Nullable String value);
40
42
  void setLineBreakStrategyIOS(T view, @Nullable String value);
41
43
  void setWritingDirection(T view, @Nullable String value);
44
+ void clearSelection(T view);
42
45
  }
@@ -35,12 +35,18 @@ public class EnrichedMarkdownTextManagerDelegate<T extends View, U extends BaseV
35
35
  case "emitLinkLayout":
36
36
  mViewManager.setEmitLinkLayout(view, value == null ? false : (boolean) value);
37
37
  break;
38
+ case "emitSelectionChange":
39
+ mViewManager.setEmitSelectionChange(view, value == null ? false : (boolean) value);
40
+ break;
38
41
  case "enableTaskListItemToggle":
39
42
  mViewManager.setEnableTaskListItemToggle(view, value == null ? true : (boolean) value);
40
43
  break;
41
44
  case "enableLinkPreview":
42
45
  mViewManager.setEnableLinkPreview(view, value == null ? true : (boolean) value);
43
46
  break;
47
+ case "linkLongPressSelects":
48
+ mViewManager.setLinkLongPressSelects(view, value == null ? false : (boolean) value);
49
+ break;
44
50
  case "selectable":
45
51
  mViewManager.setSelectable(view, value == null ? false : (boolean) value);
46
52
  break;
@@ -99,4 +105,13 @@ public class EnrichedMarkdownTextManagerDelegate<T extends View, U extends BaseV
99
105
  super.setProperty(view, propName, value);
100
106
  }
101
107
  }
108
+
109
+ @Override
110
+ public void receiveCommand(T view, String commandName, ReadableArray args) {
111
+ switch (commandName) {
112
+ case "clearSelection":
113
+ mViewManager.clearSelection(view);
114
+ break;
115
+ }
116
+ }
102
117
  }
@@ -19,8 +19,10 @@ public interface EnrichedMarkdownTextManagerInterface<T extends View> extends Vi
19
19
  void setMarkdown(T view, @Nullable String value);
20
20
  void setMarkdownStyle(T view, @Nullable ReadableMap value);
21
21
  void setEmitLinkLayout(T view, boolean value);
22
+ void setEmitSelectionChange(T view, boolean value);
22
23
  void setEnableTaskListItemToggle(T view, boolean value);
23
24
  void setEnableLinkPreview(T view, boolean value);
25
+ void setLinkLongPressSelects(T view, boolean value);
24
26
  void setSelectable(T view, boolean value);
25
27
  void setSelectionColor(T view, @Nullable Integer value);
26
28
  void setSelectionHandleColor(T view, @Nullable Integer value);
@@ -39,4 +41,5 @@ public interface EnrichedMarkdownTextManagerInterface<T extends View> extends Vi
39
41
  void setTextBreakStrategy(T view, @Nullable String value);
40
42
  void setLineBreakStrategyIOS(T view, @Nullable String value);
41
43
  void setWritingDirection(T view, @Nullable String value);
44
+ void clearSelection(T view);
42
45
  }
@@ -55,6 +55,43 @@ fragmentsObject.setProperty(runtime, "height", fragmentsValue.height);
55
55
  }
56
56
 
57
57
 
58
+ void EnrichedMarkdownEventEmitter::onSelectionChange(OnSelectionChange event) const {
59
+ dispatchEvent("selectionChange", [event=std::move(event)](jsi::Runtime &runtime) {
60
+ auto payload = jsi::Object(runtime);
61
+ payload.setProperty(runtime, "active", event.active);
62
+ payload.setProperty(runtime, "source", event.source);
63
+ payload.setProperty(runtime, "text", event.text);
64
+ payload.setProperty(runtime, "markdown", event.markdown);
65
+ payload.setProperty(runtime, "whole", event.whole);
66
+ {
67
+ auto rect = jsi::Object(runtime);
68
+ rect.setProperty(runtime, "x", event.rect.x);
69
+ rect.setProperty(runtime, "y", event.rect.y);
70
+ rect.setProperty(runtime, "width", event.rect.width);
71
+ rect.setProperty(runtime, "height", event.rect.height);
72
+ payload.setProperty(runtime, "rect", rect);
73
+ }
74
+ {
75
+ auto startRect = jsi::Object(runtime);
76
+ startRect.setProperty(runtime, "x", event.startRect.x);
77
+ startRect.setProperty(runtime, "y", event.startRect.y);
78
+ startRect.setProperty(runtime, "width", event.startRect.width);
79
+ startRect.setProperty(runtime, "height", event.startRect.height);
80
+ payload.setProperty(runtime, "startRect", startRect);
81
+ }
82
+ {
83
+ auto endRect = jsi::Object(runtime);
84
+ endRect.setProperty(runtime, "x", event.endRect.x);
85
+ endRect.setProperty(runtime, "y", event.endRect.y);
86
+ endRect.setProperty(runtime, "width", event.endRect.width);
87
+ endRect.setProperty(runtime, "height", event.endRect.height);
88
+ payload.setProperty(runtime, "endRect", endRect);
89
+ }
90
+ return payload;
91
+ });
92
+ }
93
+
94
+
58
95
  void EnrichedMarkdownEventEmitter::onTaskListItemPress(OnTaskListItemPress event) const {
59
96
  dispatchEvent("taskListItemPress", [event=std::move(event)](jsi::Runtime &runtime) {
60
97
  auto payload = jsi::Object(runtime);
@@ -418,6 +455,43 @@ fragmentsObject.setProperty(runtime, "height", fragmentsValue.height);
418
455
  }
419
456
 
420
457
 
458
+ void EnrichedMarkdownTextEventEmitter::onSelectionChange(OnSelectionChange event) const {
459
+ dispatchEvent("selectionChange", [event=std::move(event)](jsi::Runtime &runtime) {
460
+ auto payload = jsi::Object(runtime);
461
+ payload.setProperty(runtime, "active", event.active);
462
+ payload.setProperty(runtime, "source", event.source);
463
+ payload.setProperty(runtime, "text", event.text);
464
+ payload.setProperty(runtime, "markdown", event.markdown);
465
+ payload.setProperty(runtime, "whole", event.whole);
466
+ {
467
+ auto rect = jsi::Object(runtime);
468
+ rect.setProperty(runtime, "x", event.rect.x);
469
+ rect.setProperty(runtime, "y", event.rect.y);
470
+ rect.setProperty(runtime, "width", event.rect.width);
471
+ rect.setProperty(runtime, "height", event.rect.height);
472
+ payload.setProperty(runtime, "rect", rect);
473
+ }
474
+ {
475
+ auto startRect = jsi::Object(runtime);
476
+ startRect.setProperty(runtime, "x", event.startRect.x);
477
+ startRect.setProperty(runtime, "y", event.startRect.y);
478
+ startRect.setProperty(runtime, "width", event.startRect.width);
479
+ startRect.setProperty(runtime, "height", event.startRect.height);
480
+ payload.setProperty(runtime, "startRect", startRect);
481
+ }
482
+ {
483
+ auto endRect = jsi::Object(runtime);
484
+ endRect.setProperty(runtime, "x", event.endRect.x);
485
+ endRect.setProperty(runtime, "y", event.endRect.y);
486
+ endRect.setProperty(runtime, "width", event.endRect.width);
487
+ endRect.setProperty(runtime, "height", event.endRect.height);
488
+ payload.setProperty(runtime, "endRect", endRect);
489
+ }
490
+ return payload;
491
+ });
492
+ }
493
+
494
+
421
495
  void EnrichedMarkdownTextEventEmitter::onTaskListItemPress(OnTaskListItemPress event) const {
422
496
  dispatchEvent("taskListItemPress", [event=std::move(event)](jsi::Runtime &runtime) {
423
497
  auto payload = jsi::Object(runtime);
@@ -39,6 +39,38 @@ class EnrichedMarkdownEventEmitter : public ViewEventEmitter {
39
39
  std::vector<OnLinkLayoutFragments> fragments;
40
40
  };
41
41
 
42
+ struct OnSelectionChangeRect {
43
+ double x;
44
+ double y;
45
+ double width;
46
+ double height;
47
+ };
48
+
49
+ struct OnSelectionChangeStartRect {
50
+ double x;
51
+ double y;
52
+ double width;
53
+ double height;
54
+ };
55
+
56
+ struct OnSelectionChangeEndRect {
57
+ double x;
58
+ double y;
59
+ double width;
60
+ double height;
61
+ };
62
+
63
+ struct OnSelectionChange {
64
+ bool active;
65
+ std::string source;
66
+ std::string text;
67
+ std::string markdown;
68
+ bool whole;
69
+ OnSelectionChangeRect rect;
70
+ OnSelectionChangeStartRect startRect;
71
+ OnSelectionChangeEndRect endRect;
72
+ };
73
+
42
74
  struct OnTaskListItemPress {
43
75
  int index;
44
76
  bool checked;
@@ -69,6 +101,8 @@ class EnrichedMarkdownEventEmitter : public ViewEventEmitter {
69
101
 
70
102
  void onLinkLayout(OnLinkLayout value) const;
71
103
 
104
+ void onSelectionChange(OnSelectionChange value) const;
105
+
72
106
  void onTaskListItemPress(OnTaskListItemPress value) const;
73
107
 
74
108
  void onCopyPress(OnCopyPress value) const;
@@ -331,6 +365,38 @@ class EnrichedMarkdownTextEventEmitter : public ViewEventEmitter {
331
365
  std::vector<OnLinkLayoutFragments> fragments;
332
366
  };
333
367
 
368
+ struct OnSelectionChangeRect {
369
+ double x;
370
+ double y;
371
+ double width;
372
+ double height;
373
+ };
374
+
375
+ struct OnSelectionChangeStartRect {
376
+ double x;
377
+ double y;
378
+ double width;
379
+ double height;
380
+ };
381
+
382
+ struct OnSelectionChangeEndRect {
383
+ double x;
384
+ double y;
385
+ double width;
386
+ double height;
387
+ };
388
+
389
+ struct OnSelectionChange {
390
+ bool active;
391
+ std::string source;
392
+ std::string text;
393
+ std::string markdown;
394
+ bool whole;
395
+ OnSelectionChangeRect rect;
396
+ OnSelectionChangeStartRect startRect;
397
+ OnSelectionChangeEndRect endRect;
398
+ };
399
+
334
400
  struct OnTaskListItemPress {
335
401
  int index;
336
402
  bool checked;
@@ -361,6 +427,8 @@ class EnrichedMarkdownTextEventEmitter : public ViewEventEmitter {
361
427
 
362
428
  void onLinkLayout(OnLinkLayout value) const;
363
429
 
430
+ void onSelectionChange(OnSelectionChange value) const;
431
+
364
432
  void onTaskListItemPress(OnTaskListItemPress value) const;
365
433
 
366
434
  void onCopyPress(OnCopyPress value) const;
@@ -22,8 +22,10 @@ EnrichedMarkdownProps::EnrichedMarkdownProps(
22
22
  markdown(convertRawProp(context, rawProps, "markdown", sourceProps.markdown, {})),
23
23
  markdownStyle(convertRawProp(context, rawProps, "markdownStyle", sourceProps.markdownStyle, {})),
24
24
  emitLinkLayout(convertRawProp(context, rawProps, "emitLinkLayout", sourceProps.emitLinkLayout, {false})),
25
+ emitSelectionChange(convertRawProp(context, rawProps, "emitSelectionChange", sourceProps.emitSelectionChange, {false})),
25
26
  enableTaskListItemToggle(convertRawProp(context, rawProps, "enableTaskListItemToggle", sourceProps.enableTaskListItemToggle, {true})),
26
27
  enableLinkPreview(convertRawProp(context, rawProps, "enableLinkPreview", sourceProps.enableLinkPreview, {true})),
28
+ linkLongPressSelects(convertRawProp(context, rawProps, "linkLongPressSelects", sourceProps.linkLongPressSelects, {false})),
27
29
  selectable(convertRawProp(context, rawProps, "selectable", sourceProps.selectable, {false})),
28
30
  selectionColor(convertRawProp(context, rawProps, "selectionColor", sourceProps.selectionColor, {})),
29
31
  selectionHandleColor(convertRawProp(context, rawProps, "selectionHandleColor", sourceProps.selectionHandleColor, {})),
@@ -71,6 +73,10 @@ folly::dynamic EnrichedMarkdownProps::getDiffProps(
71
73
  result["emitLinkLayout"] = emitLinkLayout;
72
74
  }
73
75
 
76
+ if (emitSelectionChange != oldProps->emitSelectionChange) {
77
+ result["emitSelectionChange"] = emitSelectionChange;
78
+ }
79
+
74
80
  if (enableTaskListItemToggle != oldProps->enableTaskListItemToggle) {
75
81
  result["enableTaskListItemToggle"] = enableTaskListItemToggle;
76
82
  }
@@ -79,6 +85,10 @@ folly::dynamic EnrichedMarkdownProps::getDiffProps(
79
85
  result["enableLinkPreview"] = enableLinkPreview;
80
86
  }
81
87
 
88
+ if (linkLongPressSelects != oldProps->linkLongPressSelects) {
89
+ result["linkLongPressSelects"] = linkLongPressSelects;
90
+ }
91
+
82
92
  if (selectable != oldProps->selectable) {
83
93
  result["selectable"] = selectable;
84
94
  }
@@ -310,8 +320,10 @@ EnrichedMarkdownTextProps::EnrichedMarkdownTextProps(
310
320
  markdown(convertRawProp(context, rawProps, "markdown", sourceProps.markdown, {})),
311
321
  markdownStyle(convertRawProp(context, rawProps, "markdownStyle", sourceProps.markdownStyle, {})),
312
322
  emitLinkLayout(convertRawProp(context, rawProps, "emitLinkLayout", sourceProps.emitLinkLayout, {false})),
323
+ emitSelectionChange(convertRawProp(context, rawProps, "emitSelectionChange", sourceProps.emitSelectionChange, {false})),
313
324
  enableTaskListItemToggle(convertRawProp(context, rawProps, "enableTaskListItemToggle", sourceProps.enableTaskListItemToggle, {true})),
314
325
  enableLinkPreview(convertRawProp(context, rawProps, "enableLinkPreview", sourceProps.enableLinkPreview, {true})),
326
+ linkLongPressSelects(convertRawProp(context, rawProps, "linkLongPressSelects", sourceProps.linkLongPressSelects, {false})),
315
327
  selectable(convertRawProp(context, rawProps, "selectable", sourceProps.selectable, {false})),
316
328
  selectionColor(convertRawProp(context, rawProps, "selectionColor", sourceProps.selectionColor, {})),
317
329
  selectionHandleColor(convertRawProp(context, rawProps, "selectionHandleColor", sourceProps.selectionHandleColor, {})),
@@ -359,6 +371,10 @@ folly::dynamic EnrichedMarkdownTextProps::getDiffProps(
359
371
  result["emitLinkLayout"] = emitLinkLayout;
360
372
  }
361
373
 
374
+ if (emitSelectionChange != oldProps->emitSelectionChange) {
375
+ result["emitSelectionChange"] = emitSelectionChange;
376
+ }
377
+
362
378
  if (enableTaskListItemToggle != oldProps->enableTaskListItemToggle) {
363
379
  result["enableTaskListItemToggle"] = enableTaskListItemToggle;
364
380
  }
@@ -367,6 +383,10 @@ folly::dynamic EnrichedMarkdownTextProps::getDiffProps(
367
383
  result["enableLinkPreview"] = enableLinkPreview;
368
384
  }
369
385
 
386
+ if (linkLongPressSelects != oldProps->linkLongPressSelects) {
387
+ result["linkLongPressSelects"] = linkLongPressSelects;
388
+ }
389
+
370
390
  if (selectable != oldProps->selectable) {
371
391
  result["selectable"] = selectable;
372
392
  }
@@ -2887,8 +2887,10 @@ class EnrichedMarkdownProps final : public ViewProps {
2887
2887
  std::string markdown{};
2888
2888
  EnrichedMarkdownMarkdownStyleStruct markdownStyle{};
2889
2889
  bool emitLinkLayout{false};
2890
+ bool emitSelectionChange{false};
2890
2891
  bool enableTaskListItemToggle{true};
2891
2892
  bool enableLinkPreview{true};
2893
+ bool linkLongPressSelects{false};
2892
2894
  bool selectable{false};
2893
2895
  SharedColor selectionColor{};
2894
2896
  SharedColor selectionHandleColor{};
@@ -6931,8 +6933,10 @@ class EnrichedMarkdownTextProps final : public ViewProps {
6931
6933
  std::string markdown{};
6932
6934
  EnrichedMarkdownTextMarkdownStyleStruct markdownStyle{};
6933
6935
  bool emitLinkLayout{false};
6936
+ bool emitSelectionChange{false};
6934
6937
  bool enableTaskListItemToggle{true};
6935
6938
  bool enableLinkPreview{true};
6939
+ bool linkLongPressSelects{false};
6936
6940
  bool selectable{false};
6937
6941
  SharedColor selectionColor{};
6938
6942
  SharedColor selectionHandleColor{};
@@ -26,13 +26,18 @@ import com.swmansion.enriched.markdown.utils.common.SegmentReconciler
26
26
  import com.swmansion.enriched.markdown.utils.common.StreamingMarkdownFilter
27
27
  import com.swmansion.enriched.markdown.utils.common.TableStreamingMode
28
28
  import com.swmansion.enriched.markdown.utils.common.emitLinkLayout
29
+ import com.swmansion.enriched.markdown.utils.common.emitSelectionChange
29
30
  import com.swmansion.enriched.markdown.utils.common.isReducedMotionEnabled
30
31
  import com.swmansion.enriched.markdown.utils.common.splitASTIntoSegments
31
32
  import com.swmansion.enriched.markdown.utils.text.TailFadeInAnimator
32
33
  import com.swmansion.enriched.markdown.utils.text.view.LinkLayoutCollector
33
34
  import com.swmansion.enriched.markdown.utils.text.view.LinkLayoutFragment
35
+ import com.swmansion.enriched.markdown.utils.text.view.SelectionChangeReporter
34
36
  import com.swmansion.enriched.markdown.utils.text.view.SelectionMenuConfig
37
+ import com.swmansion.enriched.markdown.utils.text.view.SelectionRect
38
+ import com.swmansion.enriched.markdown.utils.text.view.SelectionSnapshot
35
39
  import com.swmansion.enriched.markdown.utils.text.view.applySelectionColors
40
+ import com.swmansion.enriched.markdown.utils.text.view.currentTextSelection
36
41
  import com.swmansion.enriched.markdown.views.BlockMenuOptions
37
42
  import com.swmansion.enriched.markdown.views.BlockSegmentView
38
43
  import com.swmansion.enriched.markdown.views.CodeBlockContainerView
@@ -105,6 +110,21 @@ class EnrichedMarkdown
105
110
  private var linkLayoutEnabled = false
106
111
  private var lastLinkLayout: List<LinkLayoutFragment>? = null
107
112
 
113
+ // At most one segment holds a selection; a new one clears the previous.
114
+ private var selectionOwner: EnrichedMarkdownInternalText? = null
115
+
116
+ // A block long-pressed while contextMenuHidden is on, reported as the selection.
117
+ private class ActiveBlock(
118
+ val view: View,
119
+ val source: String,
120
+ val markdown: String,
121
+ val text: String,
122
+ )
123
+
124
+ private var activeBlock: ActiveBlock? = null
125
+ private val selectionReporter =
126
+ SelectionChangeReporter(this, ::selectionSnapshot) { snapshot -> emitSelectionChange(this, snapshot) }
127
+
108
128
  private var onLinkPressCallback: ((String) -> Unit)? = null
109
129
  private var onLinkLongPressCallback: ((String) -> Unit)? = null
110
130
  private var onTaskListItemPressCallback: ((Int, Boolean, String) -> Unit)? = null
@@ -398,6 +418,78 @@ class EnrichedMarkdown
398
418
  onContextMenuItemPressCallback?.invoke(itemText, selectedText, selectionStart, selectionEnd)
399
419
  }
400
420
 
421
+ fun setEmitSelectionChange(enabled: Boolean) {
422
+ selectionReporter.enabled = enabled
423
+ }
424
+
425
+ /** Clears the selection of every segment (and any active block) and reports `active: false`. */
426
+ fun clearSelection() {
427
+ val owner = selectionOwner
428
+ selectionOwner = null
429
+ activeBlock = null
430
+ owner?.clearSelection()
431
+ selectionReporter.flushNow()
432
+ }
433
+
434
+ private fun onSegmentSelectionChanged(
435
+ view: EnrichedMarkdownInternalText,
436
+ hasSelection: Boolean,
437
+ ) {
438
+ if (hasSelection) {
439
+ // Take ownership first so the previous owner's own "cleared" callback is ignored.
440
+ val previous = selectionOwner
441
+ selectionOwner = view
442
+ activeBlock = null
443
+ if (previous !== view) previous?.clearSelection()
444
+ selectionReporter.schedule()
445
+ } else if (selectionOwner === view) {
446
+ selectionReporter.onSelectionChanged(false)
447
+ }
448
+ }
449
+
450
+ private fun onBlockMenuSuppressed(
451
+ view: View,
452
+ source: String,
453
+ markdown: String,
454
+ text: String,
455
+ ) {
456
+ if (!selectionReporter.enabled) return
457
+ val previous = selectionOwner
458
+ selectionOwner = null
459
+ previous?.clearSelection()
460
+ activeBlock = ActiveBlock(view, source, markdown, text)
461
+ selectionReporter.forgetLastReported()
462
+ selectionReporter.flushNow()
463
+ }
464
+
465
+ private fun selectionSnapshot(): SelectionSnapshot {
466
+ activeBlock?.let { block ->
467
+ if (block.view.parent !== this) return SelectionSnapshot.INACTIVE
468
+ val rect =
469
+ SelectionRect.fromPx(
470
+ block.view.left.toFloat(),
471
+ block.view.top.toFloat(),
472
+ block.view.right.toFloat(),
473
+ block.view.bottom.toFloat(),
474
+ )
475
+ return SelectionSnapshot.block(block.source, block.markdown, block.text, rect)
476
+ }
477
+ val owner = selectionOwner?.takeIf { it.parent === this } ?: return SelectionSnapshot.INACTIVE
478
+ val selection = owner.currentTextSelection(owner.left, owner.top) ?: return SelectionSnapshot.INACTIVE
479
+ // Same rule as forwardSegmentCopyRequest.
480
+ val whole = selection.fullSelection && segmentViews.size == 1 && currentMarkdown.isNotEmpty()
481
+ return SelectionSnapshot(
482
+ active = true,
483
+ source = "selection",
484
+ text = selection.text,
485
+ markdown = if (whole) currentMarkdown else selection.markdown,
486
+ whole = whole,
487
+ rect = selection.rect,
488
+ startRect = selection.startRect,
489
+ endRect = selection.endRect,
490
+ )
491
+ }
492
+
401
493
  private fun recreateStyleConfig() {
402
494
  markdownStyleMap?.let {
403
495
  markdownStyle =
@@ -498,6 +590,9 @@ class EnrichedMarkdown
498
590
  segmentSignatures.clear()
499
591
  segmentSignatures.addAll(result.signatures)
500
592
 
593
+ if (selectionOwner?.let { owner -> segmentViews.none { it === owner } } == true) selectionOwner = null
594
+ if (activeBlock?.let { block -> segmentViews.none { it === block.view } } == true) activeBlock = null
595
+
501
596
  // A just-closed block has unchanged content, so the reconciler reuses it
502
597
  // without an update; sync pending here to trigger its deferred highlight.
503
598
  segmentViews.forEachIndexed { index, view ->
@@ -637,6 +732,7 @@ class EnrichedMarkdown
637
732
  this@EnrichedMarkdown.onTaskListItemPressCallback?.invoke(taskIndex, checked, itemText)
638
733
  }
639
734
  onCopyRequest = ::forwardSegmentCopyRequest
735
+ onSelectionChangedCallback = ::onSegmentSelectionChanged
640
736
 
641
737
  if (contextMenuItemTexts.isNotEmpty()) {
642
738
  setContextMenuItems(contextMenuItemTexts, ::forwardContextMenuItemPress)
@@ -658,6 +754,7 @@ class EnrichedMarkdown
658
754
  copyAsMarkdownLabel = this@EnrichedMarkdown.selectionMenuConfig.copyAsMarkdownLabel
659
755
  contextMenuHidden = this@EnrichedMarkdown.contextMenuHidden
660
756
  menuOptions = blockMenuOptions("table")
757
+ onMenuSuppressed = { markdown, text -> onBlockMenuSuppressed(this, "table", markdown, text) }
661
758
  applyTableNode(segment.node)
662
759
  }
663
760
 
@@ -669,6 +766,7 @@ class EnrichedMarkdown
669
766
  copyAsMarkdownLabel = this@EnrichedMarkdown.selectionMenuConfig.copyAsMarkdownLabel
670
767
  contextMenuHidden = this@EnrichedMarkdown.contextMenuHidden
671
768
  menuOptions = blockMenuOptions("code")
769
+ onMenuSuppressed = { markdown, text -> onBlockMenuSuppressed(this, "code", markdown, text) }
672
770
  onCopyPress = { code, language ->
673
771
  this@EnrichedMarkdown.onCopyPressCallback?.invoke(code, language)
674
772
  }
@@ -702,6 +800,12 @@ class EnrichedMarkdown
702
800
  .getMethod("setMenuOptions", BlockMenuOptions::class.java)
703
801
  .invoke(view, blockMenuOptions("math"))
704
802
  setMathContextMenuHidden(view)
803
+ val onMenuSuppressed: (String, String) -> Unit = { markdown, text ->
804
+ onBlockMenuSuppressed(view, "math", markdown, text)
805
+ }
806
+ runCatching {
807
+ resolvedClass.getMethod("setOnMenuSuppressed", Function2::class.java).invoke(view, onMenuSuppressed)
808
+ }.onFailure { Log.e(TAG, "Failed to set onMenuSuppressed on math view", it) }
705
809
  resolvedClass.getMethod("applyLatex", String::class.java).invoke(view, segment.latex)
706
810
  view
707
811
  } catch (e: Exception) {
@@ -789,6 +893,8 @@ class EnrichedMarkdown
789
893
  }
790
894
 
791
895
  emitLinkLayoutIfNeeded()
896
+ // Segments may have moved or been replaced; re-read the selection (deduplicated).
897
+ if (selectionOwner != null || activeBlock != null) selectionReporter.schedule() else selectionReporter.flushNow()
792
898
  }
793
899
 
794
900
  fun setEmitLinkLayout(enabled: Boolean) {
@@ -837,6 +943,7 @@ class EnrichedMarkdown
837
943
  }
838
944
 
839
945
  fun cleanup() {
946
+ selectionReporter.cancel()
840
947
  executor.shutdownNow()
841
948
  }
842
949
 
@@ -18,6 +18,7 @@ import com.swmansion.enriched.markdown.utils.text.view.SelectionMenuConfig
18
18
  import com.swmansion.enriched.markdown.utils.text.view.applySelectableState
19
19
  import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForCheckboxTap
20
20
  import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForLinkTap
21
+ import com.swmansion.enriched.markdown.utils.text.view.clearTextSelection
21
22
  import com.swmansion.enriched.markdown.utils.text.view.createSelectionActionModeCallback
22
23
  import com.swmansion.enriched.markdown.utils.text.view.reallowParentInterceptIfLinkReleased
23
24
  import com.swmansion.enriched.markdown.utils.text.view.setupAsMarkdownTextView
@@ -57,6 +58,10 @@ class EnrichedMarkdownInternalText
57
58
  private var onContextMenuItemPress: ((itemText: String, selectedText: String, selectionStart: Int, selectionEnd: Int) -> Unit)? = null
58
59
  var selectionMenuConfig: SelectionMenuConfig = SelectionMenuConfig()
59
60
  var onCopyRequest: ((markdown: String, text: String, fullSelection: Boolean) -> Unit)? = null
61
+
62
+ // Read from onSelectionChanged, which TextView already calls from its own
63
+ // constructor — so it must stay nullable.
64
+ var onSelectionChangedCallback: ((view: EnrichedMarkdownInternalText, hasSelection: Boolean) -> Unit)? = null
60
65
  private var activeActionMode: ActionMode? = null
61
66
  var contextMenuHidden: Boolean = false
62
67
  set(value) {
@@ -115,6 +120,20 @@ class EnrichedMarkdownInternalText
115
120
  applySelectableState(selectable)
116
121
  }
117
122
 
123
+ override fun onSelectionChanged(
124
+ selStart: Int,
125
+ selEnd: Int,
126
+ ) {
127
+ super.onSelectionChanged(selStart, selEnd)
128
+ onSelectionChangedCallback?.invoke(this, selEnd > selStart)
129
+ }
130
+
131
+ /** Clears the selection and ends its ActionMode. */
132
+ fun clearSelection() {
133
+ clearTextSelection()
134
+ activeActionMode?.finish()
135
+ }
136
+
118
137
  fun setContextMenuItems(
119
138
  items: List<String>,
120
139
  onPress: (itemText: String, selectedText: String, selectionStart: Int, selectionEnd: Int) -> Unit,
@@ -185,6 +185,20 @@ class EnrichedMarkdownManager :
185
185
  view?.setEmitLinkLayout(emitLinkLayout)
186
186
  }
187
187
 
188
+ @ReactProp(name = "emitSelectionChange", defaultBoolean = false)
189
+ override fun setEmitSelectionChange(
190
+ view: EnrichedMarkdown?,
191
+ emitSelectionChange: Boolean,
192
+ ) {
193
+ view?.setEmitSelectionChange(emitSelectionChange)
194
+ }
195
+
196
+ // Commands
197
+
198
+ override fun clearSelection(view: EnrichedMarkdown?) {
199
+ view?.clearSelection()
200
+ }
201
+
188
202
  @ReactProp(name = "enableLinkPreview", defaultBoolean = true)
189
203
  override fun setEnableLinkPreview(
190
204
  view: EnrichedMarkdown?,
@@ -193,6 +207,14 @@ class EnrichedMarkdownManager :
193
207
  // No-op on Android — only used on iOS
194
208
  }
195
209
 
210
+ @ReactProp(name = "linkLongPressSelects", defaultBoolean = false)
211
+ override fun setLinkLongPressSelects(
212
+ view: EnrichedMarkdown?,
213
+ linkLongPressSelects: Boolean,
214
+ ) {
215
+ // No-op on Android — only used on iOS
216
+ }
217
+
196
218
  @ReactProp(name = "enableTaskListItemToggle", defaultBoolean = true)
197
219
  override fun setEnableTaskListItemToggle(
198
220
  view: EnrichedMarkdown?,