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

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 (120) hide show
  1. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerDelegate.java +3 -0
  2. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerInterface.java +1 -0
  3. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +3 -0
  4. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +1 -0
  5. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +48 -0
  6. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +32 -0
  7. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +10 -0
  8. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +122 -0
  9. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +38 -0
  10. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +8 -0
  11. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +37 -0
  12. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +8 -0
  13. package/android/src/main/java/com/swmansion/enriched/markdown/events/LinkLayoutEvent.kt +36 -0
  14. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +37 -7
  15. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +5 -1
  16. package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBackgroundSpan.kt +13 -99
  17. package/android/src/main/java/com/swmansion/enriched/markdown/spans/InlineChipPainter.kt +140 -0
  18. package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkChipSpans.kt +177 -0
  19. package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkSpan.kt +1 -6
  20. package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkStyle.kt +36 -1
  21. package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkVariantEntry.kt +7 -3
  22. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +14 -0
  23. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/conversion/HTMLGenerator.kt +2 -1
  24. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/conversion/MarkdownExtractor.kt +6 -0
  25. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkLayoutCollector.kt +100 -0
  26. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt +4 -2
  27. package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +14 -0
  28. package/docs/API_REFERENCE.md +53 -0
  29. package/docs/MENTIONS.md +8 -1
  30. package/docs/STYLES.md +27 -0
  31. package/docs/TEXT.md +2 -0
  32. package/ios/EnrichedMarkdown.mm +48 -0
  33. package/ios/EnrichedMarkdownText.mm +39 -0
  34. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.cpp +48 -0
  35. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.h +32 -0
  36. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp +10 -0
  37. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +122 -0
  38. package/ios/internals/MeasurementCache.h +5 -0
  39. package/ios/renderer/LinkRenderer.m +47 -15
  40. package/ios/styles/StyleConfig.h +16 -0
  41. package/ios/styles/StyleConfig.mm +60 -0
  42. package/ios/utils/CodeBackground.m +3 -65
  43. package/ios/utils/ENRMChipDrawing.h +24 -0
  44. package/ios/utils/ENRMChipDrawing.m +83 -0
  45. package/ios/utils/ENRMLinkChip.h +70 -0
  46. package/ios/utils/ENRMLinkChip.m +194 -0
  47. package/ios/utils/ENRMLinkLayout.h +50 -0
  48. package/ios/utils/ENRMLinkLayout.m +45 -0
  49. package/ios/utils/EditMenuUtils+macOS.m +4 -1
  50. package/ios/utils/EditMenuUtils.m +4 -1
  51. package/ios/utils/HTMLGenerator.m +2 -1
  52. package/ios/utils/MarkdownAccessibilityElementBuilder.m +4 -1
  53. package/ios/utils/MarkdownExtractor.m +2 -1
  54. package/ios/utils/RTFExportUtils.m +2 -0
  55. package/ios/utils/StylePropsUtils.h +37 -1
  56. package/ios/utils/TextViewLayoutManager.mm +3 -0
  57. package/ios/views/TableContainerView.h +3 -0
  58. package/ios/views/TableContainerView.m +21 -0
  59. package/lib/module/EnrichedMarkdownNativeComponent.ts +41 -0
  60. package/lib/module/EnrichedMarkdownTextNativeComponent.ts +41 -0
  61. package/lib/module/index.js.map +1 -1
  62. package/lib/module/native/EnrichedMarkdownText.js +96 -7
  63. package/lib/module/native/EnrichedMarkdownText.js.map +1 -1
  64. package/lib/module/normalizeMarkdownStyle.js +13 -2
  65. package/lib/module/normalizeMarkdownStyle.js.map +1 -1
  66. package/lib/module/normalizeMarkdownStyle.web.js +13 -2
  67. package/lib/module/normalizeMarkdownStyle.web.js.map +1 -1
  68. package/lib/module/web/EnrichedMarkdownText.js +6 -3
  69. package/lib/module/web/EnrichedMarkdownText.js.map +1 -1
  70. package/lib/module/web/renderers/InlineRenderers.js +96 -3
  71. package/lib/module/web/renderers/InlineRenderers.js.map +1 -1
  72. package/lib/module/web/styles.js +18 -0
  73. package/lib/module/web/styles.js.map +1 -1
  74. package/lib/module/web/utils.js +13 -0
  75. package/lib/module/web/utils.js.map +1 -1
  76. package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +40 -0
  77. package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
  78. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +40 -0
  79. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
  80. package/lib/typescript/src/index.d.ts +1 -1
  81. package/lib/typescript/src/index.d.ts.map +1 -1
  82. package/lib/typescript/src/index.web.d.ts +1 -1
  83. package/lib/typescript/src/index.web.d.ts.map +1 -1
  84. package/lib/typescript/src/native/EnrichedMarkdownText.d.ts +3 -3
  85. package/lib/typescript/src/native/EnrichedMarkdownText.d.ts.map +1 -1
  86. package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -1
  87. package/lib/typescript/src/normalizeMarkdownStyle.web.d.ts.map +1 -1
  88. package/lib/typescript/src/types/MarkdownStyle.d.ts +28 -5
  89. package/lib/typescript/src/types/MarkdownStyle.d.ts.map +1 -1
  90. package/lib/typescript/src/types/MarkdownStyleInternal.d.ts +10 -0
  91. package/lib/typescript/src/types/MarkdownStyleInternal.d.ts.map +1 -1
  92. package/lib/typescript/src/types/MarkdownTextProps.d.ts +41 -0
  93. package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
  94. package/lib/typescript/src/types/MarkdownTextProps.web.d.ts +41 -1
  95. package/lib/typescript/src/types/MarkdownTextProps.web.d.ts.map +1 -1
  96. package/lib/typescript/src/web/EnrichedMarkdownText.d.ts +1 -1
  97. package/lib/typescript/src/web/EnrichedMarkdownText.d.ts.map +1 -1
  98. package/lib/typescript/src/web/renderers/InlineRenderers.d.ts.map +1 -1
  99. package/lib/typescript/src/web/styles.d.ts.map +1 -1
  100. package/lib/typescript/src/web/types.d.ts +4 -0
  101. package/lib/typescript/src/web/types.d.ts.map +1 -1
  102. package/lib/typescript/src/web/utils.d.ts +4 -0
  103. package/lib/typescript/src/web/utils.d.ts.map +1 -1
  104. package/package.json +1 -1
  105. package/src/EnrichedMarkdownNativeComponent.ts +41 -0
  106. package/src/EnrichedMarkdownTextNativeComponent.ts +41 -0
  107. package/src/index.tsx +2 -0
  108. package/src/index.web.tsx +5 -1
  109. package/src/native/EnrichedMarkdownText.tsx +103 -6
  110. package/src/normalizeMarkdownStyle.ts +13 -0
  111. package/src/normalizeMarkdownStyle.web.ts +11 -0
  112. package/src/types/MarkdownStyle.ts +29 -5
  113. package/src/types/MarkdownStyleInternal.ts +10 -0
  114. package/src/types/MarkdownTextProps.ts +43 -0
  115. package/src/types/MarkdownTextProps.web.ts +43 -1
  116. package/src/web/EnrichedMarkdownText.tsx +10 -3
  117. package/src/web/renderers/InlineRenderers.tsx +111 -3
  118. package/src/web/styles.ts +25 -0
  119. package/src/web/types.ts +4 -0
  120. package/src/web/utils.ts +8 -0
@@ -32,6 +32,9 @@ public class EnrichedMarkdownManagerDelegate<T extends View, U extends BaseViewM
32
32
  case "markdownStyle":
33
33
  mViewManager.setMarkdownStyle(view, (ReadableMap) value);
34
34
  break;
35
+ case "emitLinkLayout":
36
+ mViewManager.setEmitLinkLayout(view, value == null ? false : (boolean) value);
37
+ break;
35
38
  case "enableTaskListItemToggle":
36
39
  mViewManager.setEnableTaskListItemToggle(view, value == null ? true : (boolean) value);
37
40
  break;
@@ -18,6 +18,7 @@ import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface;
18
18
  public interface EnrichedMarkdownManagerInterface<T extends View> extends ViewManagerWithGeneratedInterface {
19
19
  void setMarkdown(T view, @Nullable String value);
20
20
  void setMarkdownStyle(T view, @Nullable ReadableMap value);
21
+ void setEmitLinkLayout(T view, boolean value);
21
22
  void setEnableTaskListItemToggle(T view, boolean value);
22
23
  void setEnableLinkPreview(T view, boolean value);
23
24
  void setSelectable(T view, boolean value);
@@ -32,6 +32,9 @@ public class EnrichedMarkdownTextManagerDelegate<T extends View, U extends BaseV
32
32
  case "markdownStyle":
33
33
  mViewManager.setMarkdownStyle(view, (ReadableMap) value);
34
34
  break;
35
+ case "emitLinkLayout":
36
+ mViewManager.setEmitLinkLayout(view, value == null ? false : (boolean) value);
37
+ break;
35
38
  case "enableTaskListItemToggle":
36
39
  mViewManager.setEnableTaskListItemToggle(view, value == null ? true : (boolean) value);
37
40
  break;
@@ -18,6 +18,7 @@ import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface;
18
18
  public interface EnrichedMarkdownTextManagerInterface<T extends View> extends ViewManagerWithGeneratedInterface {
19
19
  void setMarkdown(T view, @Nullable String value);
20
20
  void setMarkdownStyle(T view, @Nullable ReadableMap value);
21
+ void setEmitLinkLayout(T view, boolean value);
21
22
  void setEnableTaskListItemToggle(T view, boolean value);
22
23
  void setEnableLinkPreview(T view, boolean value);
23
24
  void setSelectable(T view, boolean value);
@@ -31,6 +31,30 @@ void EnrichedMarkdownEventEmitter::onLinkLongPress(OnLinkLongPress event) const
31
31
  }
32
32
 
33
33
 
34
+ void EnrichedMarkdownEventEmitter::onLinkLayout(OnLinkLayout event) const {
35
+ dispatchEvent("linkLayout", [event=std::move(event)](jsi::Runtime &runtime) {
36
+ auto payload = jsi::Object(runtime);
37
+
38
+ auto fragments = jsi::Array(runtime, event.fragments.size());
39
+ size_t fragmentsIndex = 0;
40
+ for (auto fragmentsValue : event.fragments) {
41
+ auto fragmentsObject = jsi::Object(runtime);
42
+ fragmentsObject.setProperty(runtime, "linkIndex", fragmentsValue.linkIndex);
43
+ fragmentsObject.setProperty(runtime, "url", fragmentsValue.url);
44
+ fragmentsObject.setProperty(runtime, "text", fragmentsValue.text);
45
+ fragmentsObject.setProperty(runtime, "x", fragmentsValue.x);
46
+ fragmentsObject.setProperty(runtime, "y", fragmentsValue.y);
47
+ fragmentsObject.setProperty(runtime, "width", fragmentsValue.width);
48
+ fragmentsObject.setProperty(runtime, "height", fragmentsValue.height);
49
+ fragments.setValueAtIndex(runtime, fragmentsIndex++, fragmentsObject);
50
+ }
51
+ payload.setProperty(runtime, "fragments", fragments);
52
+
53
+ return payload;
54
+ });
55
+ }
56
+
57
+
34
58
  void EnrichedMarkdownEventEmitter::onTaskListItemPress(OnTaskListItemPress event) const {
35
59
  dispatchEvent("taskListItemPress", [event=std::move(event)](jsi::Runtime &runtime) {
36
60
  auto payload = jsi::Object(runtime);
@@ -370,6 +394,30 @@ void EnrichedMarkdownTextEventEmitter::onLinkLongPress(OnLinkLongPress event) co
370
394
  }
371
395
 
372
396
 
397
+ void EnrichedMarkdownTextEventEmitter::onLinkLayout(OnLinkLayout event) const {
398
+ dispatchEvent("linkLayout", [event=std::move(event)](jsi::Runtime &runtime) {
399
+ auto payload = jsi::Object(runtime);
400
+
401
+ auto fragments = jsi::Array(runtime, event.fragments.size());
402
+ size_t fragmentsIndex = 0;
403
+ for (auto fragmentsValue : event.fragments) {
404
+ auto fragmentsObject = jsi::Object(runtime);
405
+ fragmentsObject.setProperty(runtime, "linkIndex", fragmentsValue.linkIndex);
406
+ fragmentsObject.setProperty(runtime, "url", fragmentsValue.url);
407
+ fragmentsObject.setProperty(runtime, "text", fragmentsValue.text);
408
+ fragmentsObject.setProperty(runtime, "x", fragmentsValue.x);
409
+ fragmentsObject.setProperty(runtime, "y", fragmentsValue.y);
410
+ fragmentsObject.setProperty(runtime, "width", fragmentsValue.width);
411
+ fragmentsObject.setProperty(runtime, "height", fragmentsValue.height);
412
+ fragments.setValueAtIndex(runtime, fragmentsIndex++, fragmentsObject);
413
+ }
414
+ payload.setProperty(runtime, "fragments", fragments);
415
+
416
+ return payload;
417
+ });
418
+ }
419
+
420
+
373
421
  void EnrichedMarkdownTextEventEmitter::onTaskListItemPress(OnTaskListItemPress event) const {
374
422
  dispatchEvent("taskListItemPress", [event=std::move(event)](jsi::Runtime &runtime) {
375
423
  auto payload = jsi::Object(runtime);
@@ -25,6 +25,20 @@ class EnrichedMarkdownEventEmitter : public ViewEventEmitter {
25
25
  std::string url;
26
26
  };
27
27
 
28
+ struct OnLinkLayoutFragments {
29
+ int linkIndex;
30
+ std::string url;
31
+ std::string text;
32
+ double x;
33
+ double y;
34
+ double width;
35
+ double height;
36
+ };
37
+
38
+ struct OnLinkLayout {
39
+ std::vector<OnLinkLayoutFragments> fragments;
40
+ };
41
+
28
42
  struct OnTaskListItemPress {
29
43
  int index;
30
44
  bool checked;
@@ -53,6 +67,8 @@ class EnrichedMarkdownEventEmitter : public ViewEventEmitter {
53
67
 
54
68
  void onLinkLongPress(OnLinkLongPress value) const;
55
69
 
70
+ void onLinkLayout(OnLinkLayout value) const;
71
+
56
72
  void onTaskListItemPress(OnTaskListItemPress value) const;
57
73
 
58
74
  void onCopyPress(OnCopyPress value) const;
@@ -301,6 +317,20 @@ class EnrichedMarkdownTextEventEmitter : public ViewEventEmitter {
301
317
  std::string url;
302
318
  };
303
319
 
320
+ struct OnLinkLayoutFragments {
321
+ int linkIndex;
322
+ std::string url;
323
+ std::string text;
324
+ double x;
325
+ double y;
326
+ double width;
327
+ double height;
328
+ };
329
+
330
+ struct OnLinkLayout {
331
+ std::vector<OnLinkLayoutFragments> fragments;
332
+ };
333
+
304
334
  struct OnTaskListItemPress {
305
335
  int index;
306
336
  bool checked;
@@ -329,6 +359,8 @@ class EnrichedMarkdownTextEventEmitter : public ViewEventEmitter {
329
359
 
330
360
  void onLinkLongPress(OnLinkLongPress value) const;
331
361
 
362
+ void onLinkLayout(OnLinkLayout value) const;
363
+
332
364
  void onTaskListItemPress(OnTaskListItemPress value) const;
333
365
 
334
366
  void onCopyPress(OnCopyPress value) const;
@@ -21,6 +21,7 @@ EnrichedMarkdownProps::EnrichedMarkdownProps(
21
21
 
22
22
  markdown(convertRawProp(context, rawProps, "markdown", sourceProps.markdown, {})),
23
23
  markdownStyle(convertRawProp(context, rawProps, "markdownStyle", sourceProps.markdownStyle, {})),
24
+ emitLinkLayout(convertRawProp(context, rawProps, "emitLinkLayout", sourceProps.emitLinkLayout, {false})),
24
25
  enableTaskListItemToggle(convertRawProp(context, rawProps, "enableTaskListItemToggle", sourceProps.enableTaskListItemToggle, {true})),
25
26
  enableLinkPreview(convertRawProp(context, rawProps, "enableLinkPreview", sourceProps.enableLinkPreview, {true})),
26
27
  selectable(convertRawProp(context, rawProps, "selectable", sourceProps.selectable, {false})),
@@ -66,6 +67,10 @@ folly::dynamic EnrichedMarkdownProps::getDiffProps(
66
67
  result["markdownStyle"] = toDynamic(markdownStyle);
67
68
  }
68
69
 
70
+ if (emitLinkLayout != oldProps->emitLinkLayout) {
71
+ result["emitLinkLayout"] = emitLinkLayout;
72
+ }
73
+
69
74
  if (enableTaskListItemToggle != oldProps->enableTaskListItemToggle) {
70
75
  result["enableTaskListItemToggle"] = enableTaskListItemToggle;
71
76
  }
@@ -304,6 +309,7 @@ EnrichedMarkdownTextProps::EnrichedMarkdownTextProps(
304
309
 
305
310
  markdown(convertRawProp(context, rawProps, "markdown", sourceProps.markdown, {})),
306
311
  markdownStyle(convertRawProp(context, rawProps, "markdownStyle", sourceProps.markdownStyle, {})),
312
+ emitLinkLayout(convertRawProp(context, rawProps, "emitLinkLayout", sourceProps.emitLinkLayout, {false})),
307
313
  enableTaskListItemToggle(convertRawProp(context, rawProps, "enableTaskListItemToggle", sourceProps.enableTaskListItemToggle, {true})),
308
314
  enableLinkPreview(convertRawProp(context, rawProps, "enableLinkPreview", sourceProps.enableLinkPreview, {true})),
309
315
  selectable(convertRawProp(context, rawProps, "selectable", sourceProps.selectable, {false})),
@@ -349,6 +355,10 @@ folly::dynamic EnrichedMarkdownTextProps::getDiffProps(
349
355
  result["markdownStyle"] = toDynamic(markdownStyle);
350
356
  }
351
357
 
358
+ if (emitLinkLayout != oldProps->emitLinkLayout) {
359
+ result["emitLinkLayout"] = emitLinkLayout;
360
+ }
361
+
352
362
  if (enableTaskListItemToggle != oldProps->enableTaskListItemToggle) {
353
363
  result["enableTaskListItemToggle"] = enableTaskListItemToggle;
354
364
  }
@@ -997,6 +997,11 @@ struct EnrichedMarkdownMarkdownStyleLinkStruct {
997
997
  SharedColor color{};
998
998
  bool underline{false};
999
999
  SharedColor backgroundColor{};
1000
+ Float borderRadius{0.0};
1001
+ Float paddingHorizontal{0.0};
1002
+ Float paddingVertical{0.0};
1003
+ SharedColor borderColor{};
1004
+ Float borderWidth{0.0};
1000
1005
 
1001
1006
 
1002
1007
  #ifdef RN_SERIALIZABLE_STATE
@@ -1008,6 +1013,11 @@ struct EnrichedMarkdownMarkdownStyleLinkStruct {
1008
1013
  result["color"] = ::facebook::react::toDynamic(color);
1009
1014
  result["underline"] = underline;
1010
1015
  result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor);
1016
+ result["borderRadius"] = borderRadius;
1017
+ result["paddingHorizontal"] = paddingHorizontal;
1018
+ result["paddingVertical"] = paddingVertical;
1019
+ result["borderColor"] = ::facebook::react::toDynamic(borderColor);
1020
+ result["borderWidth"] = borderWidth;
1011
1021
  return result;
1012
1022
  }
1013
1023
  #endif
@@ -1032,6 +1042,26 @@ static inline void fromRawValue(const PropsParserContext& context, const RawValu
1032
1042
  if (tmp_backgroundColor != map.end()) {
1033
1043
  fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor);
1034
1044
  }
1045
+ auto tmp_borderRadius = map.find("borderRadius");
1046
+ if (tmp_borderRadius != map.end()) {
1047
+ fromRawValue(context, tmp_borderRadius->second, result.borderRadius);
1048
+ }
1049
+ auto tmp_paddingHorizontal = map.find("paddingHorizontal");
1050
+ if (tmp_paddingHorizontal != map.end()) {
1051
+ fromRawValue(context, tmp_paddingHorizontal->second, result.paddingHorizontal);
1052
+ }
1053
+ auto tmp_paddingVertical = map.find("paddingVertical");
1054
+ if (tmp_paddingVertical != map.end()) {
1055
+ fromRawValue(context, tmp_paddingVertical->second, result.paddingVertical);
1056
+ }
1057
+ auto tmp_borderColor = map.find("borderColor");
1058
+ if (tmp_borderColor != map.end()) {
1059
+ fromRawValue(context, tmp_borderColor->second, result.borderColor);
1060
+ }
1061
+ auto tmp_borderWidth = map.find("borderWidth");
1062
+ if (tmp_borderWidth != map.end()) {
1063
+ fromRawValue(context, tmp_borderWidth->second, result.borderWidth);
1064
+ }
1035
1065
  }
1036
1066
 
1037
1067
  static inline std::string toString(const EnrichedMarkdownMarkdownStyleLinkStruct &value) {
@@ -1049,6 +1079,11 @@ struct EnrichedMarkdownMarkdownStyleLinkVariantsStruct {
1049
1079
  SharedColor color{};
1050
1080
  bool underline{false};
1051
1081
  SharedColor backgroundColor{};
1082
+ Float borderRadius{0.0};
1083
+ Float paddingHorizontal{0.0};
1084
+ Float paddingVertical{0.0};
1085
+ SharedColor borderColor{};
1086
+ Float borderWidth{0.0};
1052
1087
 
1053
1088
 
1054
1089
  #ifdef RN_SERIALIZABLE_STATE
@@ -1060,6 +1095,11 @@ struct EnrichedMarkdownMarkdownStyleLinkVariantsStruct {
1060
1095
  result["color"] = ::facebook::react::toDynamic(color);
1061
1096
  result["underline"] = underline;
1062
1097
  result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor);
1098
+ result["borderRadius"] = borderRadius;
1099
+ result["paddingHorizontal"] = paddingHorizontal;
1100
+ result["paddingVertical"] = paddingVertical;
1101
+ result["borderColor"] = ::facebook::react::toDynamic(borderColor);
1102
+ result["borderWidth"] = borderWidth;
1063
1103
  return result;
1064
1104
  }
1065
1105
  #endif
@@ -1084,6 +1124,26 @@ static inline void fromRawValue(const PropsParserContext& context, const RawValu
1084
1124
  if (tmp_backgroundColor != map.end()) {
1085
1125
  fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor);
1086
1126
  }
1127
+ auto tmp_borderRadius = map.find("borderRadius");
1128
+ if (tmp_borderRadius != map.end()) {
1129
+ fromRawValue(context, tmp_borderRadius->second, result.borderRadius);
1130
+ }
1131
+ auto tmp_paddingHorizontal = map.find("paddingHorizontal");
1132
+ if (tmp_paddingHorizontal != map.end()) {
1133
+ fromRawValue(context, tmp_paddingHorizontal->second, result.paddingHorizontal);
1134
+ }
1135
+ auto tmp_paddingVertical = map.find("paddingVertical");
1136
+ if (tmp_paddingVertical != map.end()) {
1137
+ fromRawValue(context, tmp_paddingVertical->second, result.paddingVertical);
1138
+ }
1139
+ auto tmp_borderColor = map.find("borderColor");
1140
+ if (tmp_borderColor != map.end()) {
1141
+ fromRawValue(context, tmp_borderColor->second, result.borderColor);
1142
+ }
1143
+ auto tmp_borderWidth = map.find("borderWidth");
1144
+ if (tmp_borderWidth != map.end()) {
1145
+ fromRawValue(context, tmp_borderWidth->second, result.borderWidth);
1146
+ }
1087
1147
  }
1088
1148
 
1089
1149
  static inline std::string toString(const EnrichedMarkdownMarkdownStyleLinkVariantsStruct &value) {
@@ -2826,6 +2886,7 @@ class EnrichedMarkdownProps final : public ViewProps {
2826
2886
 
2827
2887
  std::string markdown{};
2828
2888
  EnrichedMarkdownMarkdownStyleStruct markdownStyle{};
2889
+ bool emitLinkLayout{false};
2829
2890
  bool enableTaskListItemToggle{true};
2830
2891
  bool enableLinkPreview{true};
2831
2892
  bool selectable{false};
@@ -4980,6 +5041,11 @@ struct EnrichedMarkdownTextMarkdownStyleLinkStruct {
4980
5041
  SharedColor color{};
4981
5042
  bool underline{false};
4982
5043
  SharedColor backgroundColor{};
5044
+ Float borderRadius{0.0};
5045
+ Float paddingHorizontal{0.0};
5046
+ Float paddingVertical{0.0};
5047
+ SharedColor borderColor{};
5048
+ Float borderWidth{0.0};
4983
5049
 
4984
5050
 
4985
5051
  #ifdef RN_SERIALIZABLE_STATE
@@ -4991,6 +5057,11 @@ struct EnrichedMarkdownTextMarkdownStyleLinkStruct {
4991
5057
  result["color"] = ::facebook::react::toDynamic(color);
4992
5058
  result["underline"] = underline;
4993
5059
  result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor);
5060
+ result["borderRadius"] = borderRadius;
5061
+ result["paddingHorizontal"] = paddingHorizontal;
5062
+ result["paddingVertical"] = paddingVertical;
5063
+ result["borderColor"] = ::facebook::react::toDynamic(borderColor);
5064
+ result["borderWidth"] = borderWidth;
4994
5065
  return result;
4995
5066
  }
4996
5067
  #endif
@@ -5015,6 +5086,26 @@ static inline void fromRawValue(const PropsParserContext& context, const RawValu
5015
5086
  if (tmp_backgroundColor != map.end()) {
5016
5087
  fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor);
5017
5088
  }
5089
+ auto tmp_borderRadius = map.find("borderRadius");
5090
+ if (tmp_borderRadius != map.end()) {
5091
+ fromRawValue(context, tmp_borderRadius->second, result.borderRadius);
5092
+ }
5093
+ auto tmp_paddingHorizontal = map.find("paddingHorizontal");
5094
+ if (tmp_paddingHorizontal != map.end()) {
5095
+ fromRawValue(context, tmp_paddingHorizontal->second, result.paddingHorizontal);
5096
+ }
5097
+ auto tmp_paddingVertical = map.find("paddingVertical");
5098
+ if (tmp_paddingVertical != map.end()) {
5099
+ fromRawValue(context, tmp_paddingVertical->second, result.paddingVertical);
5100
+ }
5101
+ auto tmp_borderColor = map.find("borderColor");
5102
+ if (tmp_borderColor != map.end()) {
5103
+ fromRawValue(context, tmp_borderColor->second, result.borderColor);
5104
+ }
5105
+ auto tmp_borderWidth = map.find("borderWidth");
5106
+ if (tmp_borderWidth != map.end()) {
5107
+ fromRawValue(context, tmp_borderWidth->second, result.borderWidth);
5108
+ }
5018
5109
  }
5019
5110
 
5020
5111
  static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleLinkStruct &value) {
@@ -5032,6 +5123,11 @@ struct EnrichedMarkdownTextMarkdownStyleLinkVariantsStruct {
5032
5123
  SharedColor color{};
5033
5124
  bool underline{false};
5034
5125
  SharedColor backgroundColor{};
5126
+ Float borderRadius{0.0};
5127
+ Float paddingHorizontal{0.0};
5128
+ Float paddingVertical{0.0};
5129
+ SharedColor borderColor{};
5130
+ Float borderWidth{0.0};
5035
5131
 
5036
5132
 
5037
5133
  #ifdef RN_SERIALIZABLE_STATE
@@ -5043,6 +5139,11 @@ struct EnrichedMarkdownTextMarkdownStyleLinkVariantsStruct {
5043
5139
  result["color"] = ::facebook::react::toDynamic(color);
5044
5140
  result["underline"] = underline;
5045
5141
  result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor);
5142
+ result["borderRadius"] = borderRadius;
5143
+ result["paddingHorizontal"] = paddingHorizontal;
5144
+ result["paddingVertical"] = paddingVertical;
5145
+ result["borderColor"] = ::facebook::react::toDynamic(borderColor);
5146
+ result["borderWidth"] = borderWidth;
5046
5147
  return result;
5047
5148
  }
5048
5149
  #endif
@@ -5067,6 +5168,26 @@ static inline void fromRawValue(const PropsParserContext& context, const RawValu
5067
5168
  if (tmp_backgroundColor != map.end()) {
5068
5169
  fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor);
5069
5170
  }
5171
+ auto tmp_borderRadius = map.find("borderRadius");
5172
+ if (tmp_borderRadius != map.end()) {
5173
+ fromRawValue(context, tmp_borderRadius->second, result.borderRadius);
5174
+ }
5175
+ auto tmp_paddingHorizontal = map.find("paddingHorizontal");
5176
+ if (tmp_paddingHorizontal != map.end()) {
5177
+ fromRawValue(context, tmp_paddingHorizontal->second, result.paddingHorizontal);
5178
+ }
5179
+ auto tmp_paddingVertical = map.find("paddingVertical");
5180
+ if (tmp_paddingVertical != map.end()) {
5181
+ fromRawValue(context, tmp_paddingVertical->second, result.paddingVertical);
5182
+ }
5183
+ auto tmp_borderColor = map.find("borderColor");
5184
+ if (tmp_borderColor != map.end()) {
5185
+ fromRawValue(context, tmp_borderColor->second, result.borderColor);
5186
+ }
5187
+ auto tmp_borderWidth = map.find("borderWidth");
5188
+ if (tmp_borderWidth != map.end()) {
5189
+ fromRawValue(context, tmp_borderWidth->second, result.borderWidth);
5190
+ }
5070
5191
  }
5071
5192
 
5072
5193
  static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleLinkVariantsStruct &value) {
@@ -6809,6 +6930,7 @@ class EnrichedMarkdownTextProps final : public ViewProps {
6809
6930
 
6810
6931
  std::string markdown{};
6811
6932
  EnrichedMarkdownTextMarkdownStyleStruct markdownStyle{};
6933
+ bool emitLinkLayout{false};
6812
6934
  bool enableTaskListItemToggle{true};
6813
6935
  bool enableLinkPreview{true};
6814
6936
  bool selectable{false};
@@ -25,9 +25,12 @@ import com.swmansion.enriched.markdown.utils.common.RenderedSegment
25
25
  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
+ import com.swmansion.enriched.markdown.utils.common.emitLinkLayout
28
29
  import com.swmansion.enriched.markdown.utils.common.isReducedMotionEnabled
29
30
  import com.swmansion.enriched.markdown.utils.common.splitASTIntoSegments
30
31
  import com.swmansion.enriched.markdown.utils.text.TailFadeInAnimator
32
+ import com.swmansion.enriched.markdown.utils.text.view.LinkLayoutCollector
33
+ import com.swmansion.enriched.markdown.utils.text.view.LinkLayoutFragment
31
34
  import com.swmansion.enriched.markdown.utils.text.view.SelectionMenuConfig
32
35
  import com.swmansion.enriched.markdown.utils.text.view.applySelectionColors
33
36
  import com.swmansion.enriched.markdown.views.BlockMenuOptions
@@ -99,6 +102,8 @@ class EnrichedMarkdown
99
102
  private var selectionMenuConfig = SelectionMenuConfig()
100
103
  private var accessibilityLabels = AccessibilityLabels()
101
104
  private var textBreakStrategy: String = BreakStrategyUtils.DEFAULT_STRATEGY
105
+ private var linkLayoutEnabled = false
106
+ private var lastLinkLayout: List<LinkLayoutFragment>? = null
102
107
 
103
108
  private var onLinkPressCallback: ((String) -> Unit)? = null
104
109
  private var onLinkLongPressCallback: ((String) -> Unit)? = null
@@ -782,6 +787,39 @@ class EnrichedMarkdown
782
787
  currentY += segment?.segmentMarginBottom ?: 0
783
788
  }
784
789
  }
790
+
791
+ emitLinkLayoutIfNeeded()
792
+ }
793
+
794
+ fun setEmitLinkLayout(enabled: Boolean) {
795
+ if (linkLayoutEnabled == enabled) return
796
+ linkLayoutEnabled = enabled
797
+ lastLinkLayout = null
798
+ emitLinkLayoutIfNeeded()
799
+ }
800
+
801
+ /**
802
+ * Reports link rects of every text segment (offset by the segment's position) to JS for
803
+ * `renderLinkDecoration`. Tables are skipped: their cells scroll horizontally, so rects would
804
+ * go stale. Runs after each segment layout pass; deduplicated so an unchanged layout sends nothing.
805
+ */
806
+ private fun emitLinkLayoutIfNeeded() {
807
+ if (!linkLayoutEnabled || width <= 0) return
808
+ val fragments = mutableListOf<LinkLayoutFragment>()
809
+ var linkCount = 0
810
+ segmentViews.forEach { view ->
811
+ if (view is TableContainerView) {
812
+ linkCount += view.linkCount
813
+ return@forEach
814
+ }
815
+ if (view !is EnrichedMarkdownInternalText) return@forEach
816
+ val (segmentFragments, segmentLinks) = LinkLayoutCollector.collect(view, view.left, view.top, linkCount)
817
+ fragments += segmentFragments
818
+ linkCount += segmentLinks
819
+ }
820
+ if (fragments == lastLinkLayout) return
821
+ lastLinkLayout = fragments
822
+ emitLinkLayout(this, fragments)
785
823
  }
786
824
 
787
825
  private fun computeSegmentsTotalHeight(): Int {
@@ -177,6 +177,14 @@ class EnrichedMarkdownManager :
177
177
  view?.setContextMenuHidden(contextMenuHidden)
178
178
  }
179
179
 
180
+ @ReactProp(name = "emitLinkLayout", defaultBoolean = false)
181
+ override fun setEmitLinkLayout(
182
+ view: EnrichedMarkdown?,
183
+ emitLinkLayout: Boolean,
184
+ ) {
185
+ view?.setEmitLinkLayout(emitLinkLayout)
186
+ }
187
+
180
188
  @ReactProp(name = "enableLinkPreview", defaultBoolean = true)
181
189
  override fun setEnableLinkPreview(
182
190
  view: EnrichedMarkdown?,
@@ -24,8 +24,11 @@ import com.swmansion.enriched.markdown.spoiler.SpoilerOverlay
24
24
  import com.swmansion.enriched.markdown.spoiler.SpoilerOverlayDrawer
25
25
  import com.swmansion.enriched.markdown.styles.StyleConfig
26
26
  import com.swmansion.enriched.markdown.utils.common.BreakStrategyUtils
27
+ import com.swmansion.enriched.markdown.utils.common.emitLinkLayout
27
28
  import com.swmansion.enriched.markdown.utils.text.TailFadeInAnimator
28
29
  import com.swmansion.enriched.markdown.utils.text.interaction.CheckboxTouchHelper
30
+ import com.swmansion.enriched.markdown.utils.text.view.LinkLayoutCollector
31
+ import com.swmansion.enriched.markdown.utils.text.view.LinkLayoutFragment
29
32
  import com.swmansion.enriched.markdown.utils.text.view.LinkLongPressMovementMethod
30
33
  import com.swmansion.enriched.markdown.utils.text.view.SelectionMenuConfig
31
34
  import com.swmansion.enriched.markdown.utils.text.view.applySelectableState
@@ -102,6 +105,9 @@ class EnrichedMarkdownText
102
105
  private var contextMenuHidden = false
103
106
  private var activeActionMode: ActionMode? = null
104
107
 
108
+ private var linkLayoutEnabled = false
109
+ private var lastLinkLayout: List<LinkLayoutFragment>? = null
110
+
105
111
  init {
106
112
  setupAsMarkdownTextView()
107
113
  customSelectionActionModeCallback =
@@ -310,6 +316,37 @@ class EnrichedMarkdownText
310
316
  }
311
317
 
312
318
  applySelectionColors(selectionColor, selectionHandleColor)
319
+ emitLinkLayoutIfNeeded()
320
+ }
321
+
322
+ fun setEmitLinkLayout(enabled: Boolean) {
323
+ if (linkLayoutEnabled == enabled) return
324
+ linkLayoutEnabled = enabled
325
+ lastLinkLayout = null
326
+ emitLinkLayoutIfNeeded()
327
+ }
328
+
329
+ /**
330
+ * Reports link rects to JS (for `renderLinkDecoration`) after every render and layout;
331
+ * deduplicated so an unchanged layout — the common case while streaming — sends nothing.
332
+ */
333
+ private fun emitLinkLayoutIfNeeded() {
334
+ if (!linkLayoutEnabled || layout == null || width <= 0) return
335
+ val (fragments, _) = LinkLayoutCollector.collect(this)
336
+ if (fragments == lastLinkLayout) return
337
+ lastLinkLayout = fragments
338
+ emitLinkLayout(this, fragments)
339
+ }
340
+
341
+ override fun onLayout(
342
+ changed: Boolean,
343
+ left: Int,
344
+ top: Int,
345
+ right: Int,
346
+ bottom: Int,
347
+ ) {
348
+ super.onLayout(changed, left, top, right, bottom)
349
+ emitLinkLayoutIfNeeded()
313
350
  }
314
351
 
315
352
  // Trailing bottom margin included in the shadow-node measurement — must be
@@ -177,6 +177,14 @@ class EnrichedMarkdownTextManager :
177
177
  view?.setContextMenuHidden(contextMenuHidden)
178
178
  }
179
179
 
180
+ @ReactProp(name = "emitLinkLayout", defaultBoolean = false)
181
+ override fun setEmitLinkLayout(
182
+ view: EnrichedMarkdownText?,
183
+ emitLinkLayout: Boolean,
184
+ ) {
185
+ view?.setEmitLinkLayout(emitLinkLayout)
186
+ }
187
+
180
188
  @ReactProp(name = "enableLinkPreview", defaultBoolean = true)
181
189
  override fun setEnableLinkPreview(
182
190
  view: EnrichedMarkdownText?,
@@ -0,0 +1,36 @@
1
+ package com.swmansion.enriched.markdown.events
2
+
3
+ import com.facebook.react.bridge.Arguments
4
+ import com.facebook.react.bridge.WritableMap
5
+ import com.facebook.react.uimanager.events.Event
6
+ import com.swmansion.enriched.markdown.utils.text.view.LinkLayoutFragment
7
+
8
+ class LinkLayoutEvent(
9
+ surfaceId: Int,
10
+ viewId: Int,
11
+ private val fragments: List<LinkLayoutFragment>,
12
+ ) : Event<LinkLayoutEvent>(surfaceId, viewId) {
13
+ override fun getEventName(): String = EVENT_NAME
14
+
15
+ override fun getEventData(): WritableMap {
16
+ val array = Arguments.createArray()
17
+ fragments.forEach { fragment ->
18
+ array.pushMap(
19
+ Arguments.createMap().apply {
20
+ putInt("linkIndex", fragment.linkIndex)
21
+ putString("url", fragment.url)
22
+ putString("text", fragment.text)
23
+ putDouble("x", fragment.x.toDouble())
24
+ putDouble("y", fragment.y.toDouble())
25
+ putDouble("width", fragment.width.toDouble())
26
+ putDouble("height", fragment.height.toDouble())
27
+ },
28
+ )
29
+ }
30
+ return Arguments.createMap().apply { putArray("fragments", array) }
31
+ }
32
+
33
+ companion object {
34
+ const val EVENT_NAME: String = "onLinkLayout"
35
+ }
36
+ }
@@ -2,6 +2,9 @@ package com.swmansion.enriched.markdown.renderer
2
2
 
3
3
  import android.text.SpannableStringBuilder
4
4
  import com.swmansion.enriched.markdown.parser.MarkdownASTNode
5
+ import com.swmansion.enriched.markdown.spans.LinkChipBackgroundSpan
6
+ import com.swmansion.enriched.markdown.spans.LinkSpacerSpan
7
+ import com.swmansion.enriched.markdown.spans.LinkSpacers
5
8
  import com.swmansion.enriched.markdown.spans.LinkSpan
6
9
  import com.swmansion.enriched.markdown.utils.text.span.SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE
7
10
 
@@ -16,14 +19,41 @@ class LinkRenderer(
16
19
  factory: RendererFactory,
17
20
  ) {
18
21
  val url = node.getAttribute("url") ?: return
22
+ // Chip ("box") properties never fall back to the base link style.
23
+ val chip = factory.styleCache.linkChipForUrl(url)
24
+ val padding = chip.paddingHorizontal
19
25
 
20
- factory.renderWithSpan(builder, { factory.renderChildren(node, builder, onLinkPress, onLinkLongPress) }) { start, end, blockStyle ->
21
- builder.setSpan(
22
- LinkSpan(url, onLinkPress, onLinkLongPress, factory.styleCache, blockStyle, factory.context),
23
- start,
24
- end,
25
- SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
26
- )
26
+ val start = builder.length
27
+ // The leading spacer goes in before the children so ranges they register stay valid.
28
+ if (padding > 0f) appendSpacer(builder, padding)
29
+ val contentStart = builder.length
30
+
31
+ factory.renderChildren(node, builder, onLinkPress, onLinkLongPress)
32
+
33
+ if (builder.length == contentStart) {
34
+ if (contentStart > start) builder.delete(start, contentStart)
35
+ return
27
36
  }
37
+ if (padding > 0f) appendSpacer(builder, padding)
38
+ val end = builder.length
39
+
40
+ val blockStyle = factory.blockStyleContext.requireBlockStyle()
41
+ builder.setSpan(
42
+ LinkSpan(url, onLinkPress, onLinkLongPress, factory.styleCache, blockStyle, factory.context),
43
+ start,
44
+ end,
45
+ SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
46
+ )
47
+ // One instance per link: also identifies the link for link-layout reporting.
48
+ builder.setSpan(LinkChipBackgroundSpan(chip), start, end, SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE)
49
+ }
50
+
51
+ private fun appendSpacer(
52
+ builder: SpannableStringBuilder,
53
+ width: Float,
54
+ ) {
55
+ val position = builder.length
56
+ builder.append(LinkSpacers.CHAR)
57
+ builder.setSpan(LinkSpacerSpan(width), position, position + 1, SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE)
28
58
  }
29
59
  }