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
@@ -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};
@@ -128,6 +128,11 @@ template <typename StyleStruct> inline size_t computeStyleFingerprint(const Styl
128
128
  s.codeBlock.borderWidth);
129
129
  hashFields(s.code.fontFamily, s.code.fontSize);
130
130
  hashFields(s.link.fontFamily, s.strong.fontFamily, s.strong.fontWeight, s.em.fontFamily, s.em.fontStyle);
131
+ // Link chip horizontal padding reserves space in the line; variants can set their own.
132
+ hashFields(s.link.paddingHorizontal);
133
+ for (const auto &variant : s.linkVariants) {
134
+ hashFields(variant.pattern, variant.paddingHorizontal);
135
+ }
131
136
  hashFields(s.highlight.backgroundColor, s.highlight.color);
132
137
 
133
138
  // Visual/Spacing Elements
@@ -1,4 +1,5 @@
1
1
  #import "LinkRenderer.h"
2
+ #import "ENRMLinkChip.h"
2
3
  #import "FontUtils.h"
3
4
  #import "RenderContext.h"
4
5
  #import "RendererFactory.h"
@@ -13,31 +14,49 @@
13
14
  into:(NSMutableAttributedString *)output
14
15
  context:(RenderContext *)context
15
16
  {
16
- NSUInteger start = output.length;
17
-
18
- // 1. Render children first to establish base attributes
19
- [_rendererFactory renderChildrenOfNode:node into:output context:context];
20
-
21
- NSRange range = NSMakeRange(start, output.length - start);
22
- if (range.length == 0)
23
- return;
24
-
25
- // 2. Extract configuration
17
+ // 1. Extract configuration
26
18
  NSString *url = node.attributes[@"url"] ?: @"";
27
19
  LinkVariantConfig *variant = [_config effectiveLinkVariantForURL:url];
28
20
 
29
21
  RCTUIColor *linkColor = variant.color ?: [_config linkColor];
30
22
  BOOL linkUnderline = variant ? variant.underline : [_config linkUnderline];
31
23
  NSString *linkFontFamily = [_config linkFontFamily];
32
- RCTUIColor *backgroundColor = variant ? variant.backgroundColor : [_config linkBackgroundColor];
24
+ // Chip ("box") properties never fall back to the base link style.
25
+ ENRMLinkChip *chip = [[ENRMLinkChip alloc] init];
26
+ chip.backgroundColor = variant ? variant.backgroundColor : [_config linkBackgroundColor];
27
+ chip.borderColor = variant ? variant.borderColor : [_config linkBorderColor];
28
+ chip.borderWidth = variant ? variant.borderWidth : [_config linkBorderWidth];
29
+ chip.borderRadius = variant ? variant.borderRadius : [_config linkBorderRadius];
30
+ chip.paddingHorizontal = variant ? variant.paddingHorizontal : [_config linkPaddingHorizontal];
31
+ chip.paddingVertical = variant ? variant.paddingVertical : [_config linkPaddingVertical];
32
+ BOOL hasSpacers = chip.paddingHorizontal > 0;
33
33
 
34
34
  NSNumber *underlineStyle = @(linkUnderline ? NSUnderlineStyleSingle : NSUnderlineStyleNone);
35
35
 
36
+ // 2. Render children to establish base attributes. With horizontal padding, a
37
+ // leading spacer goes in first so ranges the children register stay valid;
38
+ // its attributes are fixed up once the first child glyph exists.
39
+ NSUInteger start = output.length;
40
+ if (hasSpacers) {
41
+ [output appendAttributedString:ENRMLinkSpacerString([context getTextAttributes], chip.paddingHorizontal)];
42
+ }
43
+ NSUInteger contentStart = output.length;
44
+
45
+ [_rendererFactory renderChildrenOfNode:node into:output context:context];
46
+
47
+ NSRange contentRange = NSMakeRange(contentStart, output.length - contentStart);
48
+ if (contentRange.length == 0) {
49
+ if (hasSpacers) {
50
+ [output deleteCharactersInRange:NSMakeRange(start, contentStart - start)];
51
+ }
52
+ return;
53
+ }
54
+
36
55
  // 3. Apply core link functionality (non-destructive)
37
- [output addAttribute:NSLinkAttributeName value:url range:range];
56
+ [output addAttribute:NSLinkAttributeName value:url range:contentRange];
38
57
 
39
58
  // 4. Optimize visual attributes via enumeration to avoid redundant updates
40
- [output enumerateAttributesInRange:range
59
+ [output enumerateAttributesInRange:contentRange
41
60
  options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
42
61
  usingBlock:^(NSDictionary<NSAttributedStringKey, id> *attrs, NSRange subrange, BOOL *stop) {
43
62
  NSMutableDictionary *newAttributes = [NSMutableDictionary dictionary];
@@ -74,10 +93,23 @@
74
93
  }
75
94
  }];
76
95
 
77
- if (backgroundColor) {
78
- [output addAttribute:NSBackgroundColorAttributeName value:backgroundColor range:range];
96
+ // 5. Reserve horizontal padding with a spacer at each outer edge (see
97
+ // ENRMLinkSpacerString). Spacers sit inside the link range, so tapping the
98
+ // padding hits the link.
99
+ NSRange range = contentRange;
100
+ if (hasSpacers) {
101
+ NSDictionary *firstAttrs = [output attributesAtIndex:contentRange.location effectiveRange:NULL];
102
+ NSDictionary *lastAttrs = [output attributesAtIndex:NSMaxRange(contentRange) - 1 effectiveRange:NULL];
103
+ NSAttributedString *leading = ENRMLinkSpacerString(firstAttrs, chip.paddingHorizontal);
104
+ [output setAttributes:[leading attributesAtIndex:0 effectiveRange:NULL] range:NSMakeRange(start, 1)];
105
+ [output appendAttributedString:ENRMLinkSpacerString(lastAttrs, chip.paddingHorizontal)];
106
+ range = NSMakeRange(start, output.length - start);
79
107
  }
80
108
 
109
+ // 6. Chip background/border is drawn by the layout manager (see ENRMLinkChip).
110
+ // Attached even when invisible so the chip's padding is known to link-layout reporting.
111
+ [output addAttribute:ENRMLinkChipAttributeName value:chip range:range];
112
+
81
113
  [context registerLinkRange:range url:url];
82
114
  }
83
115
 
@@ -13,6 +13,12 @@ NS_ASSUME_NONNULL_BEGIN
13
13
  @property (nonatomic, strong) RCTUIColor *color;
14
14
  @property (nonatomic, assign) BOOL underline;
15
15
  @property (nonatomic, strong, nullable) RCTUIColor *backgroundColor;
16
+ // Chip ("box") properties — never inherited from the base link style.
17
+ @property (nonatomic, assign) CGFloat borderRadius;
18
+ @property (nonatomic, assign) CGFloat paddingHorizontal;
19
+ @property (nonatomic, assign) CGFloat paddingVertical;
20
+ @property (nonatomic, strong, nullable) RCTUIColor *borderColor;
21
+ @property (nonatomic, assign) CGFloat borderWidth;
16
22
  @end
17
23
 
18
24
  @interface StyleConfig : NSObject <NSCopying>
@@ -166,6 +172,16 @@ NS_ASSUME_NONNULL_BEGIN
166
172
  - (void)setLinkUnderline:(BOOL)newValue;
167
173
  - (nullable RCTUIColor *)linkBackgroundColor;
168
174
  - (void)setLinkBackgroundColor:(nullable RCTUIColor *)newValue;
175
+ - (CGFloat)linkBorderRadius;
176
+ - (void)setLinkBorderRadius:(CGFloat)newValue;
177
+ - (CGFloat)linkPaddingHorizontal;
178
+ - (void)setLinkPaddingHorizontal:(CGFloat)newValue;
179
+ - (CGFloat)linkPaddingVertical;
180
+ - (void)setLinkPaddingVertical:(CGFloat)newValue;
181
+ - (nullable RCTUIColor *)linkBorderColor;
182
+ - (void)setLinkBorderColor:(nullable RCTUIColor *)newValue;
183
+ - (CGFloat)linkBorderWidth;
184
+ - (void)setLinkBorderWidth:(CGFloat)newValue;
169
185
  - (NSArray<LinkVariantConfig *> *)linkVariants;
170
186
  - (void)setLinkVariants:(NSArray<LinkVariantConfig *> *)newValue;
171
187
  - (nullable LinkVariantConfig *)effectiveLinkVariantForURL:(NSString *)url;
@@ -113,6 +113,11 @@ static inline NSString *normalizedFontWeight(NSString *fontWeight)
113
113
  RCTUIColor *_linkColor;
114
114
  BOOL _linkUnderline;
115
115
  RCTUIColor *_linkBackgroundColor;
116
+ CGFloat _linkBorderRadius;
117
+ CGFloat _linkPaddingHorizontal;
118
+ CGFloat _linkPaddingVertical;
119
+ RCTUIColor *_linkBorderColor;
120
+ CGFloat _linkBorderWidth;
116
121
  NSArray<LinkVariantConfig *> *_linkVariants;
117
122
  NSArray<LinkVariantRegexEntry *> *_compiledVariantRegexes;
118
123
  // Strong properties
@@ -405,6 +410,11 @@ static inline NSString *normalizedFontWeight(NSString *fontWeight)
405
410
  copy->_linkColor = [_linkColor copy];
406
411
  copy->_linkUnderline = _linkUnderline;
407
412
  copy->_linkBackgroundColor = [_linkBackgroundColor copy];
413
+ copy->_linkBorderRadius = _linkBorderRadius;
414
+ copy->_linkPaddingHorizontal = _linkPaddingHorizontal;
415
+ copy->_linkPaddingVertical = _linkPaddingVertical;
416
+ copy->_linkBorderColor = [_linkBorderColor copy];
417
+ copy->_linkBorderWidth = _linkBorderWidth;
408
418
  copy->_linkVariants = [_linkVariants copy];
409
419
  copy->_compiledVariantRegexes = [_compiledVariantRegexes copy];
410
420
  copy->_strongFontFamily = [_strongFontFamily copy];
@@ -1334,6 +1344,56 @@ static inline NSString *normalizedFontWeight(NSString *fontWeight)
1334
1344
  _linkBackgroundColor = newValue;
1335
1345
  }
1336
1346
 
1347
+ - (CGFloat)linkBorderRadius
1348
+ {
1349
+ return _linkBorderRadius;
1350
+ }
1351
+
1352
+ - (void)setLinkBorderRadius:(CGFloat)newValue
1353
+ {
1354
+ _linkBorderRadius = newValue;
1355
+ }
1356
+
1357
+ - (CGFloat)linkPaddingHorizontal
1358
+ {
1359
+ return _linkPaddingHorizontal;
1360
+ }
1361
+
1362
+ - (void)setLinkPaddingHorizontal:(CGFloat)newValue
1363
+ {
1364
+ _linkPaddingHorizontal = newValue;
1365
+ }
1366
+
1367
+ - (CGFloat)linkPaddingVertical
1368
+ {
1369
+ return _linkPaddingVertical;
1370
+ }
1371
+
1372
+ - (void)setLinkPaddingVertical:(CGFloat)newValue
1373
+ {
1374
+ _linkPaddingVertical = newValue;
1375
+ }
1376
+
1377
+ - (RCTUIColor *)linkBorderColor
1378
+ {
1379
+ return _linkBorderColor;
1380
+ }
1381
+
1382
+ - (void)setLinkBorderColor:(RCTUIColor *)newValue
1383
+ {
1384
+ _linkBorderColor = newValue;
1385
+ }
1386
+
1387
+ - (CGFloat)linkBorderWidth
1388
+ {
1389
+ return _linkBorderWidth;
1390
+ }
1391
+
1392
+ - (void)setLinkBorderWidth:(CGFloat)newValue
1393
+ {
1394
+ _linkBorderWidth = newValue;
1395
+ }
1396
+
1337
1397
  - (NSArray<LinkVariantConfig *> *)linkVariants
1338
1398
  {
1339
1399
  return _linkVariants;
@@ -1,4 +1,5 @@
1
1
  #import "CodeBackground.h"
2
+ #import "ENRMChipDrawing.h"
2
3
  #import "ENRMUIKit.h"
3
4
  #import "RenderContext.h"
4
5
 
@@ -116,71 +117,8 @@ static const CGFloat kCodeBackgroundBorderWidth = 0.5;
116
117
  isFirst:(BOOL)isFirst
117
118
  isLast:(BOOL)isLast
118
119
  {
119
- UIBezierPath *path = [self pathForRect:rect isFirst:isFirst isLast:isLast];
120
- [backgroundColor setFill];
121
- [path fill];
122
-
123
- if (borderColor) {
124
- [borderColor setStroke];
125
- UIBezierPath *strokePath =
126
- (isFirst && isLast) ? path : [self openBorderPathForRect:rect isFirst:isFirst isLast:isLast];
127
- strokePath.lineWidth = kCodeBackgroundBorderWidth;
128
- BezierPathSetRoundStyle(strokePath);
129
- [strokePath stroke];
130
- }
131
- }
132
-
133
- - (UIBezierPath *)pathForRect:(CGRect)rect isFirst:(BOOL)isFirst isLast:(BOOL)isLast
134
- {
135
- #if !TARGET_OS_OSX
136
- UIRectCorner corners = 0;
137
- if (isFirst)
138
- corners |= (UIRectCornerTopLeft | UIRectCornerBottomLeft);
139
- if (isLast)
140
- corners |= (UIRectCornerTopRight | UIRectCornerBottomRight);
141
-
142
- return [UIBezierPath bezierPathWithRoundedRect:rect
143
- byRoundingCorners:corners
144
- cornerRadii:CGSizeMake(kCodeBackgroundCornerRadius, kCodeBackgroundCornerRadius)];
145
- #else
146
- // NSBezierPath doesn't support per-corner rounding — use uniform corner radius
147
- CGFloat cornerRadius = (isFirst || isLast) ? kCodeBackgroundCornerRadius : 0;
148
- return UIBezierPathWithRoundedRect(rect, cornerRadius);
149
- #endif
150
- }
151
-
152
- - (UIBezierPath *)openBorderPathForRect:(CGRect)rect isFirst:(BOOL)isFirst isLast:(BOOL)isLast
153
- {
154
- UIBezierPath *path = [UIBezierPath bezierPath];
155
- CGFloat r = kCodeBackgroundCornerRadius;
156
- CGFloat inset = kCodeBackgroundBorderWidth / 2.0;
157
- CGRect insetRect = CGRectInset(rect, inset, inset);
158
-
159
- if (isFirst) {
160
- [path moveToPoint:CGPointMake(CGRectGetMaxX(rect), insetRect.origin.y)];
161
- BezierPathAddLine(path, CGPointMake(insetRect.origin.x + r, insetRect.origin.y));
162
- BezierPathAddQuadCurve(path, CGPointMake(insetRect.origin.x, insetRect.origin.y + r),
163
- CGPointMake(insetRect.origin.x, insetRect.origin.y));
164
- BezierPathAddLine(path, CGPointMake(insetRect.origin.x, CGRectGetMaxY(insetRect) - r));
165
- BezierPathAddQuadCurve(path, CGPointMake(insetRect.origin.x + r, CGRectGetMaxY(insetRect)),
166
- CGPointMake(insetRect.origin.x, CGRectGetMaxY(insetRect)));
167
- BezierPathAddLine(path, CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(insetRect)));
168
- } else if (isLast) {
169
- [path moveToPoint:CGPointMake(rect.origin.x, insetRect.origin.y)];
170
- BezierPathAddLine(path, CGPointMake(CGRectGetMaxX(insetRect) - r, insetRect.origin.y));
171
- BezierPathAddQuadCurve(path, CGPointMake(CGRectGetMaxX(insetRect), insetRect.origin.y + r),
172
- CGPointMake(CGRectGetMaxX(insetRect), insetRect.origin.y));
173
- BezierPathAddLine(path, CGPointMake(CGRectGetMaxX(insetRect), CGRectGetMaxY(insetRect) - r));
174
- BezierPathAddQuadCurve(path, CGPointMake(CGRectGetMaxX(insetRect) - r, CGRectGetMaxY(insetRect)),
175
- CGPointMake(CGRectGetMaxX(insetRect), CGRectGetMaxY(insetRect)));
176
- BezierPathAddLine(path, CGPointMake(rect.origin.x, CGRectGetMaxY(insetRect)));
177
- } else {
178
- [path moveToPoint:CGPointMake(rect.origin.x, insetRect.origin.y)];
179
- BezierPathAddLine(path, CGPointMake(CGRectGetMaxX(rect), insetRect.origin.y));
180
- [path moveToPoint:CGPointMake(rect.origin.x, CGRectGetMaxY(insetRect))];
181
- BezierPathAddLine(path, CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(insetRect)));
182
- }
183
- return path;
120
+ ENRMDrawChipFragment(rect, backgroundColor, borderColor, kCodeBackgroundBorderWidth, kCodeBackgroundCornerRadius,
121
+ isFirst, isLast, NO);
184
122
  }
185
123
 
186
124
  #pragma mark - Helpers
@@ -0,0 +1,24 @@
1
+ #pragma once
2
+ #import "ENRMUIKit.h"
3
+
4
+ NS_ASSUME_NONNULL_BEGIN
5
+
6
+ #ifdef __cplusplus
7
+ extern "C" {
8
+ #endif
9
+
10
+ /// Fills (and optionally strokes) one line fragment of an inline "chip" — the
11
+ /// rounded background shared by inline code and link chips. For a chip that
12
+ /// wraps, only the outer ends are rounded (`isFirst` / `isLast`) and the border
13
+ /// stays open where the fragment is cut.
14
+ ///
15
+ /// `strokeInset` keeps a closed border inside `rect` (link chips); otherwise it
16
+ /// is centered on the edge (inline code, kept for pixel parity).
17
+ void ENRMDrawChipFragment(CGRect rect, RCTUIColor *_Nullable backgroundColor, RCTUIColor *_Nullable borderColor,
18
+ CGFloat borderWidth, CGFloat cornerRadius, BOOL isFirst, BOOL isLast, BOOL strokeInset);
19
+
20
+ #ifdef __cplusplus
21
+ }
22
+ #endif
23
+
24
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,83 @@
1
+ #import "ENRMChipDrawing.h"
2
+
3
+ static UIBezierPath *chipFillPath(CGRect rect, CGFloat r, BOOL isFirst, BOOL isLast)
4
+ {
5
+ #if !TARGET_OS_OSX
6
+ UIRectCorner corners = 0;
7
+ if (isFirst)
8
+ corners |= (UIRectCornerTopLeft | UIRectCornerBottomLeft);
9
+ if (isLast)
10
+ corners |= (UIRectCornerTopRight | UIRectCornerBottomRight);
11
+
12
+ return [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:CGSizeMake(r, r)];
13
+ #else
14
+ // NSBezierPath doesn't support per-corner rounding — use uniform corner radius
15
+ return UIBezierPathWithRoundedRect(rect, (isFirst || isLast) ? r : 0);
16
+ #endif
17
+ }
18
+
19
+ static UIBezierPath *chipOpenBorderPath(CGRect rect, CGFloat r, CGFloat borderWidth, BOOL isFirst, BOOL isLast)
20
+ {
21
+ UIBezierPath *path = [UIBezierPath bezierPath];
22
+ CGFloat inset = borderWidth / 2.0;
23
+ CGRect insetRect = CGRectInset(rect, inset, inset);
24
+
25
+ if (isFirst) {
26
+ [path moveToPoint:CGPointMake(CGRectGetMaxX(rect), insetRect.origin.y)];
27
+ BezierPathAddLine(path, CGPointMake(insetRect.origin.x + r, insetRect.origin.y));
28
+ BezierPathAddQuadCurve(path, CGPointMake(insetRect.origin.x, insetRect.origin.y + r),
29
+ CGPointMake(insetRect.origin.x, insetRect.origin.y));
30
+ BezierPathAddLine(path, CGPointMake(insetRect.origin.x, CGRectGetMaxY(insetRect) - r));
31
+ BezierPathAddQuadCurve(path, CGPointMake(insetRect.origin.x + r, CGRectGetMaxY(insetRect)),
32
+ CGPointMake(insetRect.origin.x, CGRectGetMaxY(insetRect)));
33
+ BezierPathAddLine(path, CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(insetRect)));
34
+ } else if (isLast) {
35
+ [path moveToPoint:CGPointMake(rect.origin.x, insetRect.origin.y)];
36
+ BezierPathAddLine(path, CGPointMake(CGRectGetMaxX(insetRect) - r, insetRect.origin.y));
37
+ BezierPathAddQuadCurve(path, CGPointMake(CGRectGetMaxX(insetRect), insetRect.origin.y + r),
38
+ CGPointMake(CGRectGetMaxX(insetRect), insetRect.origin.y));
39
+ BezierPathAddLine(path, CGPointMake(CGRectGetMaxX(insetRect), CGRectGetMaxY(insetRect) - r));
40
+ BezierPathAddQuadCurve(path, CGPointMake(CGRectGetMaxX(insetRect) - r, CGRectGetMaxY(insetRect)),
41
+ CGPointMake(CGRectGetMaxX(insetRect), CGRectGetMaxY(insetRect)));
42
+ BezierPathAddLine(path, CGPointMake(rect.origin.x, CGRectGetMaxY(insetRect)));
43
+ } else {
44
+ [path moveToPoint:CGPointMake(rect.origin.x, insetRect.origin.y)];
45
+ BezierPathAddLine(path, CGPointMake(CGRectGetMaxX(rect), insetRect.origin.y));
46
+ [path moveToPoint:CGPointMake(rect.origin.x, CGRectGetMaxY(insetRect))];
47
+ BezierPathAddLine(path, CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(insetRect)));
48
+ }
49
+ return path;
50
+ }
51
+
52
+ void ENRMDrawChipFragment(CGRect rect, RCTUIColor *backgroundColor, RCTUIColor *borderColor, CGFloat borderWidth,
53
+ CGFloat cornerRadius, BOOL isFirst, BOOL isLast, BOOL strokeInset)
54
+ {
55
+ if (CGRectIsEmpty(rect))
56
+ return;
57
+
58
+ CGFloat r = MIN(MAX(cornerRadius, 0), MIN(rect.size.width, rect.size.height) / 2.0);
59
+
60
+ if (backgroundColor) {
61
+ [backgroundColor setFill];
62
+ [chipFillPath(rect, r, isFirst, isLast) fill];
63
+ }
64
+
65
+ if (!borderColor || borderWidth <= 0)
66
+ return;
67
+
68
+ [borderColor setStroke];
69
+ UIBezierPath *strokePath;
70
+ if (isFirst && isLast) {
71
+ if (strokeInset) {
72
+ CGFloat inset = borderWidth / 2.0;
73
+ strokePath = chipFillPath(CGRectInset(rect, inset, inset), MAX(r - inset, 0), YES, YES);
74
+ } else {
75
+ strokePath = chipFillPath(rect, r, YES, YES);
76
+ }
77
+ } else {
78
+ strokePath = chipOpenBorderPath(rect, r, borderWidth, isFirst, isLast);
79
+ }
80
+ strokePath.lineWidth = borderWidth;
81
+ BezierPathSetRoundStyle(strokePath);
82
+ [strokePath stroke];
83
+ }