react-native-enriched-markdown-scaffold 1.0.2-scaffold.1 → 1.0.2-scaffold.10

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 (163) hide show
  1. package/android/build.gradle +2 -0
  2. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerDelegate.java +18 -0
  3. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerInterface.java +4 -0
  4. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +18 -0
  5. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +4 -0
  6. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +148 -0
  7. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +120 -0
  8. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +30 -0
  9. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +210 -0
  10. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +176 -2
  11. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +45 -0
  12. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +35 -0
  13. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +112 -0
  14. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +34 -0
  15. package/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt +46 -8
  16. package/android/src/main/java/com/swmansion/enriched/markdown/events/CopyRequestEvent.kt +28 -0
  17. package/android/src/main/java/com/swmansion/enriched/markdown/events/LinkLayoutEvent.kt +36 -0
  18. package/android/src/main/java/com/swmansion/enriched/markdown/events/SelectionChangeEvent.kt +43 -0
  19. package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputView.kt +25 -3
  20. package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/InputConnectionWrapper.kt +7 -0
  21. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +37 -7
  22. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +5 -1
  23. package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBackgroundSpan.kt +13 -99
  24. package/android/src/main/java/com/swmansion/enriched/markdown/spans/InlineChipPainter.kt +140 -0
  25. package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkChipSpans.kt +177 -0
  26. package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkSpan.kt +1 -6
  27. package/android/src/main/java/com/swmansion/enriched/markdown/styles/InlineMathStyle.kt +16 -1
  28. package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkStyle.kt +36 -1
  29. package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkVariantEntry.kt +7 -3
  30. package/android/src/main/java/com/swmansion/enriched/markdown/styles/MathStyle.kt +3 -0
  31. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +51 -0
  32. package/android/src/main/java/com/swmansion/enriched/markdown/utils/input/PasteTextUtils.kt +34 -2
  33. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/conversion/HTMLGenerator.kt +2 -1
  34. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/conversion/MarkdownExtractor.kt +6 -0
  35. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/HeadlessActionMode.kt +88 -0
  36. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkLayoutCollector.kt +100 -0
  37. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt +53 -14
  38. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionChangeReporter.kt +232 -0
  39. package/android/src/main/java/com/swmansion/enriched/markdown/views/BlockMenuOptions.kt +64 -0
  40. package/android/src/main/java/com/swmansion/enriched/markdown/views/CodeBlockContainerView.kt +29 -3
  41. package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +30 -7
  42. package/android/src/math/java/com/swmansion/enriched/markdown/renderer/MathInlineRenderer.kt +1 -0
  43. package/android/src/math/java/com/swmansion/enriched/markdown/spans/MathInlineSpan.kt +13 -6
  44. package/android/src/math/java/com/swmansion/enriched/markdown/spans/MathMeasureHelper.kt +2 -1
  45. package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +25 -9
  46. package/docs/API_REFERENCE.md +183 -4
  47. package/docs/COPY_OPTIONS.md +40 -0
  48. package/docs/INPUT.md +3 -1
  49. package/docs/MENTIONS.md +8 -1
  50. package/docs/STYLES.md +27 -0
  51. package/docs/TEXT.md +2 -0
  52. package/docs/WEB.md +1 -0
  53. package/ios/EnrichedMarkdown.mm +321 -9
  54. package/ios/EnrichedMarkdownText.mm +203 -6
  55. package/ios/attachments/ENRMMathInlineAttachment+macOS.m +2 -1
  56. package/ios/attachments/ENRMMathInlineAttachment.h +3 -0
  57. package/ios/attachments/ENRMMathInlineAttachment.m +2 -1
  58. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.cpp +148 -0
  59. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.h +120 -0
  60. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp +30 -0
  61. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +210 -0
  62. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +50 -2
  63. package/ios/internals/MeasurementCache.h +8 -2
  64. package/ios/math/ENRMRaTeXBridge.swift +23 -5
  65. package/ios/renderer/ENRMMathInlineRenderer.m +1 -0
  66. package/ios/renderer/LinkRenderer.m +47 -15
  67. package/ios/styles/StyleConfig.h +20 -0
  68. package/ios/styles/StyleConfig.mm +84 -0
  69. package/ios/utils/CodeBackground.m +3 -65
  70. package/ios/utils/ENRMBlockMenu.h +66 -0
  71. package/ios/utils/ENRMChipDrawing.h +24 -0
  72. package/ios/utils/ENRMChipDrawing.m +83 -0
  73. package/ios/utils/ENRMLinkChip.h +70 -0
  74. package/ios/utils/ENRMLinkChip.m +213 -0
  75. package/ios/utils/ENRMLinkLayout.h +50 -0
  76. package/ios/utils/ENRMLinkLayout.m +45 -0
  77. package/ios/utils/ENRMSelectionChange.h +120 -0
  78. package/ios/utils/ENRMSelectionChange.m +282 -0
  79. package/ios/utils/EditMenuUtils+macOS.m +4 -1
  80. package/ios/utils/EditMenuUtils.h +35 -6
  81. package/ios/utils/EditMenuUtils.m +171 -15
  82. package/ios/utils/HTMLGenerator.m +2 -1
  83. package/ios/utils/MarkdownAccessibilityElementBuilder.m +3 -1
  84. package/ios/utils/MarkdownExtractor.m +2 -1
  85. package/ios/utils/RTFExportUtils.m +2 -0
  86. package/ios/utils/StylePropsUtils.h +47 -1
  87. package/ios/utils/TextViewLayoutManager.mm +3 -0
  88. package/ios/views/ENRMCodeBlockContainerView.h +16 -2
  89. package/ios/views/ENRMCodeBlockContainerView.m +22 -19
  90. package/ios/views/ENRMMathContainerView.h +11 -0
  91. package/ios/views/ENRMMathContainerView.m +17 -20
  92. package/ios/views/TableContainerView.h +14 -0
  93. package/ios/views/TableContainerView.m +34 -18
  94. package/lib/module/EnrichedMarkdownNativeComponent.ts +138 -1
  95. package/lib/module/EnrichedMarkdownTextNativeComponent.ts +138 -1
  96. package/lib/module/index.js.map +1 -1
  97. package/lib/module/jest/index.js +14 -0
  98. package/lib/module/jest/index.js.map +1 -1
  99. package/lib/module/native/EnrichedMarkdownText.js +169 -11
  100. package/lib/module/native/EnrichedMarkdownText.js.map +1 -1
  101. package/lib/module/normalizeMarkdownStyle.js +17 -4
  102. package/lib/module/normalizeMarkdownStyle.js.map +1 -1
  103. package/lib/module/normalizeMarkdownStyle.web.js +17 -4
  104. package/lib/module/normalizeMarkdownStyle.web.js.map +1 -1
  105. package/lib/module/web/EnrichedMarkdownText.js +6 -3
  106. package/lib/module/web/EnrichedMarkdownText.js.map +1 -1
  107. package/lib/module/web/renderers/InlineRenderers.js +96 -3
  108. package/lib/module/web/renderers/InlineRenderers.js.map +1 -1
  109. package/lib/module/web/styles.js +18 -0
  110. package/lib/module/web/styles.js.map +1 -1
  111. package/lib/module/web/utils.js +13 -0
  112. package/lib/module/web/utils.js.map +1 -1
  113. package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +123 -2
  114. package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
  115. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +123 -2
  116. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
  117. package/lib/typescript/src/index.d.ts +2 -2
  118. package/lib/typescript/src/index.d.ts.map +1 -1
  119. package/lib/typescript/src/index.web.d.ts +1 -1
  120. package/lib/typescript/src/index.web.d.ts.map +1 -1
  121. package/lib/typescript/src/jest/index.d.ts +1 -1
  122. package/lib/typescript/src/jest/index.d.ts.map +1 -1
  123. package/lib/typescript/src/native/EnrichedMarkdownText.d.ts +5 -5
  124. package/lib/typescript/src/native/EnrichedMarkdownText.d.ts.map +1 -1
  125. package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -1
  126. package/lib/typescript/src/normalizeMarkdownStyle.web.d.ts.map +1 -1
  127. package/lib/typescript/src/types/MarkdownStyle.d.ts +50 -5
  128. package/lib/typescript/src/types/MarkdownStyle.d.ts.map +1 -1
  129. package/lib/typescript/src/types/MarkdownStyleInternal.d.ts +12 -0
  130. package/lib/typescript/src/types/MarkdownStyleInternal.d.ts.map +1 -1
  131. package/lib/typescript/src/types/MarkdownTextProps.d.ts +144 -11
  132. package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
  133. package/lib/typescript/src/types/MarkdownTextProps.web.d.ts +41 -1
  134. package/lib/typescript/src/types/MarkdownTextProps.web.d.ts.map +1 -1
  135. package/lib/typescript/src/types/events.d.ts +66 -0
  136. package/lib/typescript/src/types/events.d.ts.map +1 -1
  137. package/lib/typescript/src/web/EnrichedMarkdownText.d.ts +1 -1
  138. package/lib/typescript/src/web/EnrichedMarkdownText.d.ts.map +1 -1
  139. package/lib/typescript/src/web/renderers/InlineRenderers.d.ts.map +1 -1
  140. package/lib/typescript/src/web/styles.d.ts.map +1 -1
  141. package/lib/typescript/src/web/types.d.ts +4 -0
  142. package/lib/typescript/src/web/types.d.ts.map +1 -1
  143. package/lib/typescript/src/web/utils.d.ts +4 -0
  144. package/lib/typescript/src/web/utils.d.ts.map +1 -1
  145. package/package.json +1 -1
  146. package/src/EnrichedMarkdownNativeComponent.ts +138 -1
  147. package/src/EnrichedMarkdownTextNativeComponent.ts +138 -1
  148. package/src/index.tsx +6 -0
  149. package/src/index.web.tsx +5 -1
  150. package/src/jest/index.tsx +22 -1
  151. package/src/native/EnrichedMarkdownText.tsx +220 -9
  152. package/src/normalizeMarkdownStyle.ts +15 -0
  153. package/src/normalizeMarkdownStyle.web.ts +13 -0
  154. package/src/types/MarkdownStyle.ts +51 -5
  155. package/src/types/MarkdownStyleInternal.ts +12 -0
  156. package/src/types/MarkdownTextProps.ts +151 -11
  157. package/src/types/MarkdownTextProps.web.ts +43 -1
  158. package/src/types/events.ts +69 -0
  159. package/src/web/EnrichedMarkdownText.tsx +10 -3
  160. package/src/web/renderers/InlineRenderers.tsx +111 -3
  161. package/src/web/styles.ts +25 -0
  162. package/src/web/types.ts +4 -0
  163. package/src/web/utils.ts +8 -0
@@ -14,9 +14,33 @@
14
14
  NS_ASSUME_NONNULL_BEGIN
15
15
 
16
16
  @protocol RCTEnrichedMarkdownViewProtocol <NSObject>
17
-
17
+ - (void)clearSelection;
18
18
  @end
19
19
 
20
+ RCT_EXTERN inline void RCTEnrichedMarkdownHandleCommand(
21
+ id<RCTEnrichedMarkdownViewProtocol> componentView,
22
+ NSString const *commandName,
23
+ NSArray const *args)
24
+ {
25
+ if ([commandName isEqualToString:@"clearSelection"]) {
26
+ #if RCT_DEBUG
27
+ if ([args count] != 0) {
28
+ RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"EnrichedMarkdown", commandName, (int)[args count], 0);
29
+ return;
30
+ }
31
+ #endif
32
+
33
+
34
+
35
+ [componentView clearSelection];
36
+ return;
37
+ }
38
+
39
+ #if RCT_DEBUG
40
+ RCTLogError(@"%@ received command %@, which is not a supported command.", @"EnrichedMarkdown", commandName);
41
+ #endif
42
+ }
43
+
20
44
  @protocol RCTEnrichedMarkdownTextInputViewProtocol <NSObject>
21
45
  - (void)focus;
22
46
  - (void)blur;
@@ -576,7 +600,31 @@ if ([commandName isEqualToString:@"requestCaretRect"]) {
576
600
  }
577
601
 
578
602
  @protocol RCTEnrichedMarkdownTextViewProtocol <NSObject>
579
-
603
+ - (void)clearSelection;
580
604
  @end
581
605
 
606
+ RCT_EXTERN inline void RCTEnrichedMarkdownTextHandleCommand(
607
+ id<RCTEnrichedMarkdownTextViewProtocol> componentView,
608
+ NSString const *commandName,
609
+ NSArray const *args)
610
+ {
611
+ if ([commandName isEqualToString:@"clearSelection"]) {
612
+ #if RCT_DEBUG
613
+ if ([args count] != 0) {
614
+ RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"EnrichedMarkdownText", commandName, (int)[args count], 0);
615
+ return;
616
+ }
617
+ #endif
618
+
619
+
620
+
621
+ [componentView clearSelection];
622
+ return;
623
+ }
624
+
625
+ #if RCT_DEBUG
626
+ RCTLogError(@"%@ received command %@, which is not a supported command.", @"EnrichedMarkdownText", commandName);
627
+ #endif
628
+ }
629
+
582
630
  NS_ASSUME_NONNULL_END
@@ -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
@@ -139,8 +144,9 @@ template <typename StyleStruct> inline size_t computeStyleFingerprint(const Styl
139
144
  hashTextLayout(s.table);
140
145
  hashFields(s.table.headerFontFamily, s.table.cellPaddingHorizontal, s.table.cellPaddingVertical, s.table.borderWidth,
141
146
  s.table.borderRadius, s.table.horizontalOverflow);
142
- hashFields(s.math.fontSize, s.math.padding, s.math.marginTop, s.math.marginBottom, s.math.textAlign);
143
- hashFields(s.inlineMath.paddingVertical);
147
+ hashFields(s.math.fontSize, s.math.padding, s.math.marginTop, s.math.marginBottom, s.math.textAlign,
148
+ s.math.glyphOverflow);
149
+ hashFields(s.inlineMath.paddingVertical, s.inlineMath.glyphOverflow);
144
150
  hashFields(s.taskList.checkboxSize, s.taskList.checkboxBorderRadius);
145
151
 
146
152
  return h;
@@ -42,23 +42,39 @@ public final class ENRMRaTeXRenderResult: NSObject {
42
42
  /// Non-zero only in bold mode: half of it grows the glyph ink on each side.
43
43
  private let boldStrokeWidth: CGFloat
44
44
  private let boldStrokeColor: CGColor?
45
+ /// Headroom added above and below RaTeX's box. RaTeX sizes a formula by its TeX
46
+ /// metric box, which digit and capital outlines (and the bold stroke) overshoot;
47
+ /// every view and attachment clips to `totalHeight`, so that ink needs room inside it.
48
+ private let glyphOverflow: CGFloat
45
49
 
46
50
  @objc public let width: CGFloat
47
51
  @objc public let ascent: CGFloat
48
52
  @objc public let descent: CGFloat
49
53
  @objc public var totalHeight: CGFloat { ascent + descent }
50
54
 
51
- init(renderer: RaTeXRenderer, boldStrokeWidth: CGFloat, boldStrokeColor: CGColor?) {
55
+ init(renderer: RaTeXRenderer, boldStrokeWidth: CGFloat, boldStrokeColor: CGColor?, glyphOverflow: CGFloat) {
52
56
  self.renderer = renderer
53
57
  self.boldStrokeWidth = boldStrokeWidth
54
58
  self.boldStrokeColor = boldStrokeColor
59
+ self.glyphOverflow = max(glyphOverflow, 0)
55
60
  self.width = renderer.width
56
- self.ascent = renderer.height
57
- self.descent = renderer.depth
61
+ self.ascent = renderer.height + self.glyphOverflow
62
+ self.descent = renderer.depth + self.glyphOverflow
58
63
  super.init()
59
64
  }
60
65
 
61
66
  @objc public func draw(in context: CGContext) {
67
+ guard glyphOverflow > 0 else {
68
+ drawFormula(in: context)
69
+ return
70
+ }
71
+ context.saveGState()
72
+ context.translateBy(x: 0, y: glyphOverflow)
73
+ drawFormula(in: context)
74
+ context.restoreGState()
75
+ }
76
+
77
+ private func drawFormula(in context: CGContext) {
62
78
  guard boldStrokeWidth > 0, let boldStrokeColor else {
63
79
  renderer.draw(in: context)
64
80
  return
@@ -90,7 +106,8 @@ public final class ENRMRaTeXBridge: NSObject {
90
106
  displayMode: Bool,
91
107
  fontSize: CGFloat,
92
108
  color: UIColor,
93
- bold: Bool
109
+ bold: Bool,
110
+ glyphOverflow: CGFloat
94
111
  ) -> ENRMRaTeXRenderResult? {
95
112
  RaTeXFontLoader.ensureLoaded()
96
113
  do {
@@ -100,7 +117,8 @@ public final class ENRMRaTeXBridge: NSObject {
100
117
  return ENRMRaTeXRenderResult(
101
118
  renderer: renderer,
102
119
  boldStrokeWidth: strokeWidth,
103
- boldStrokeColor: strokeWidth > 0 ? color.cgColor : nil
120
+ boldStrokeColor: strokeWidth > 0 ? color.cgColor : nil,
121
+ glyphOverflow: glyphOverflow
104
122
  )
105
123
  } catch {
106
124
  NSLog("[RaTeX] Failed to parse LaTeX: %@", error.localizedDescription)
@@ -30,6 +30,7 @@
30
30
  attachment.mathTextColor = [_config inlineMathColor];
31
31
  attachment.containerPaddingVertical = [_config inlineMathPaddingVertical];
32
32
  attachment.bold = ENRMMathFontWeightIsBold([_config inlineMathFontWeight]);
33
+ attachment.glyphOverflow = [_config inlineMathGlyphOverflow];
33
34
 
34
35
  #if TARGET_OS_OSX
35
36
  // On macOS, NSLayoutManager uses self.image/self.bounds rather than calling
@@ -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;
@@ -402,6 +418,8 @@ NS_ASSUME_NONNULL_BEGIN
402
418
  - (void)setMathTextAlign:(NSString *)newValue;
403
419
  - (NSString *)mathFontWeight;
404
420
  - (void)setMathFontWeight:(NSString *)newValue;
421
+ - (CGFloat)mathGlyphOverflow;
422
+ - (void)setMathGlyphOverflow:(CGFloat)newValue;
405
423
 
406
424
  // Inline Math properties
407
425
  - (RCTUIColor *)inlineMathColor;
@@ -410,6 +428,8 @@ NS_ASSUME_NONNULL_BEGIN
410
428
  - (void)setInlineMathPaddingVertical:(CGFloat)newValue;
411
429
  - (NSString *)inlineMathFontWeight;
412
430
  - (void)setInlineMathFontWeight:(NSString *)newValue;
431
+ - (CGFloat)inlineMathGlyphOverflow;
432
+ - (void)setInlineMathGlyphOverflow:(CGFloat)newValue;
413
433
  // Spoiler properties
414
434
  - (RCTUIColor *)spoilerColor;
415
435
  - (void)setSpoilerColor:(RCTUIColor *)newValue;
@@ -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
@@ -239,10 +244,12 @@ static inline NSString *normalizedFontWeight(NSString *fontWeight)
239
244
  CGFloat _mathMarginBottom;
240
245
  NSString *_mathTextAlign;
241
246
  NSString *_mathFontWeight;
247
+ CGFloat _mathGlyphOverflow;
242
248
  // Inline Math properties
243
249
  RCTUIColor *_inlineMathColor;
244
250
  CGFloat _inlineMathPaddingVertical;
245
251
  NSString *_inlineMathFontWeight;
252
+ CGFloat _inlineMathGlyphOverflow;
246
253
  // Spoiler properties
247
254
  RCTUIColor *_spoilerColor;
248
255
  CGFloat _spoilerParticleDensity;
@@ -405,6 +412,11 @@ static inline NSString *normalizedFontWeight(NSString *fontWeight)
405
412
  copy->_linkColor = [_linkColor copy];
406
413
  copy->_linkUnderline = _linkUnderline;
407
414
  copy->_linkBackgroundColor = [_linkBackgroundColor copy];
415
+ copy->_linkBorderRadius = _linkBorderRadius;
416
+ copy->_linkPaddingHorizontal = _linkPaddingHorizontal;
417
+ copy->_linkPaddingVertical = _linkPaddingVertical;
418
+ copy->_linkBorderColor = [_linkBorderColor copy];
419
+ copy->_linkBorderWidth = _linkBorderWidth;
408
420
  copy->_linkVariants = [_linkVariants copy];
409
421
  copy->_compiledVariantRegexes = [_compiledVariantRegexes copy];
410
422
  copy->_strongFontFamily = [_strongFontFamily copy];
@@ -512,9 +524,11 @@ static inline NSString *normalizedFontWeight(NSString *fontWeight)
512
524
  copy->_mathMarginBottom = _mathMarginBottom;
513
525
  copy->_mathTextAlign = [_mathTextAlign copy];
514
526
  copy->_mathFontWeight = [_mathFontWeight copy];
527
+ copy->_mathGlyphOverflow = _mathGlyphOverflow;
515
528
  copy->_inlineMathColor = [_inlineMathColor copy];
516
529
  copy->_inlineMathPaddingVertical = _inlineMathPaddingVertical;
517
530
  copy->_inlineMathFontWeight = [_inlineMathFontWeight copy];
531
+ copy->_inlineMathGlyphOverflow = _inlineMathGlyphOverflow;
518
532
  copy->_spoilerColor = [_spoilerColor copy];
519
533
  copy->_spoilerParticleDensity = _spoilerParticleDensity;
520
534
  copy->_spoilerParticleSpeed = _spoilerParticleSpeed;
@@ -1334,6 +1348,56 @@ static inline NSString *normalizedFontWeight(NSString *fontWeight)
1334
1348
  _linkBackgroundColor = newValue;
1335
1349
  }
1336
1350
 
1351
+ - (CGFloat)linkBorderRadius
1352
+ {
1353
+ return _linkBorderRadius;
1354
+ }
1355
+
1356
+ - (void)setLinkBorderRadius:(CGFloat)newValue
1357
+ {
1358
+ _linkBorderRadius = newValue;
1359
+ }
1360
+
1361
+ - (CGFloat)linkPaddingHorizontal
1362
+ {
1363
+ return _linkPaddingHorizontal;
1364
+ }
1365
+
1366
+ - (void)setLinkPaddingHorizontal:(CGFloat)newValue
1367
+ {
1368
+ _linkPaddingHorizontal = newValue;
1369
+ }
1370
+
1371
+ - (CGFloat)linkPaddingVertical
1372
+ {
1373
+ return _linkPaddingVertical;
1374
+ }
1375
+
1376
+ - (void)setLinkPaddingVertical:(CGFloat)newValue
1377
+ {
1378
+ _linkPaddingVertical = newValue;
1379
+ }
1380
+
1381
+ - (RCTUIColor *)linkBorderColor
1382
+ {
1383
+ return _linkBorderColor;
1384
+ }
1385
+
1386
+ - (void)setLinkBorderColor:(RCTUIColor *)newValue
1387
+ {
1388
+ _linkBorderColor = newValue;
1389
+ }
1390
+
1391
+ - (CGFloat)linkBorderWidth
1392
+ {
1393
+ return _linkBorderWidth;
1394
+ }
1395
+
1396
+ - (void)setLinkBorderWidth:(CGFloat)newValue
1397
+ {
1398
+ _linkBorderWidth = newValue;
1399
+ }
1400
+
1337
1401
  - (NSArray<LinkVariantConfig *> *)linkVariants
1338
1402
  {
1339
1403
  return _linkVariants;
@@ -2598,6 +2662,26 @@ static const CGFloat kDefaultMinGap = 4.0;
2598
2662
  _inlineMathPaddingVertical = newValue;
2599
2663
  }
2600
2664
 
2665
+ - (CGFloat)mathGlyphOverflow
2666
+ {
2667
+ return _mathGlyphOverflow;
2668
+ }
2669
+
2670
+ - (void)setMathGlyphOverflow:(CGFloat)newValue
2671
+ {
2672
+ _mathGlyphOverflow = newValue;
2673
+ }
2674
+
2675
+ - (CGFloat)inlineMathGlyphOverflow
2676
+ {
2677
+ return _inlineMathGlyphOverflow;
2678
+ }
2679
+
2680
+ - (void)setInlineMathGlyphOverflow:(CGFloat)newValue
2681
+ {
2682
+ _inlineMathGlyphOverflow = newValue;
2683
+ }
2684
+
2601
2685
  // ── Spoiler ─────────────────────────────────────────────────────────────
2602
2686
 
2603
2687
  - (RCTUIColor *)spoilerColor
@@ -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,66 @@
1
+ #pragma once
2
+ #import "ENRMUIKit.h"
3
+ #include <TargetConditionals.h>
4
+
5
+ NS_ASSUME_NONNULL_BEGIN
6
+
7
+ /// Selection-menu options that table, math and code block views need for their
8
+ /// own long-press menus. Every field is phrased so that the zero value keeps the
9
+ /// legacy behavior, so a block view that has not received options yet behaves
10
+ /// exactly as before.
11
+ typedef struct {
12
+ // `selectionMenuConfig.copy.enabled == false`.
13
+ BOOL copyHidden;
14
+ // `selectionMenuConfig.copy.format == 'markdown'`: the Copy item writes the
15
+ // block's Markdown instead of its raw content.
16
+ BOOL copyWritesMarkdown;
17
+ // `selectionMenuConfig.copyAsMarkdown.enabled == false`.
18
+ BOOL copyAsMarkdownHidden;
19
+ // `selectionMenuConfig.codeBlockCopyButton.enabled == false`.
20
+ BOOL copyButtonHidden;
21
+ // JS `onCopy` is set: the Copy item hands the block's content to the view's
22
+ // `onCopyRequest` instead of writing the pasteboard.
23
+ BOOL interceptCopy;
24
+ } ENRMBlockMenuOptions;
25
+
26
+ /// Receives an intercepted block Copy: the block's Markdown and its raw content.
27
+ typedef void (^ENRMBlockCopyRequestBlock)(NSString *markdown, NSString *text);
28
+
29
+ #if !TARGET_OS_OSX
30
+ /// Builds the long-press menu shared by the table, math and code block views.
31
+ /// Returns nil when no item is left — nil is how UIKit suppresses a context
32
+ /// menu, whereas an empty menu would still lift the view. `requestCopy` replaces
33
+ /// the Copy item's handler when `options.interceptCopy` is set.
34
+ static inline UIContextMenuConfiguration *_Nullable ENRMBlockContextMenuConfiguration(
35
+ ENRMBlockMenuOptions options, NSString *_Nullable copyLabel, NSString *_Nullable copyAsMarkdownLabel,
36
+ void (^copyRaw)(void), void (^copyMarkdown)(void), void (^_Nullable requestCopy)(void))
37
+ {
38
+ if (options.copyHidden && options.copyAsMarkdownHidden) {
39
+ return nil;
40
+ }
41
+ void (^copyHandler)(void) = (options.interceptCopy && requestCopy) ? requestCopy
42
+ : options.copyWritesMarkdown ? copyMarkdown
43
+ : copyRaw;
44
+ return [UIContextMenuConfiguration
45
+ configurationWithIdentifier:nil
46
+ previewProvider:nil
47
+ actionProvider:^UIMenu *(NSArray<UIMenuElement *> *suggestedActions) {
48
+ NSMutableArray<UIMenuElement *> *children = [NSMutableArray arrayWithCapacity:2];
49
+ if (!options.copyHidden) {
50
+ [children addObject:[UIAction actionWithTitle:copyLabel ?: @""
51
+ image:[RCTUIImage systemImageNamed:@"doc.on.doc"]
52
+ identifier:nil
53
+ handler:^(__kindof UIAction *action) { copyHandler(); }]];
54
+ }
55
+ if (!options.copyAsMarkdownHidden) {
56
+ [children addObject:[UIAction actionWithTitle:copyAsMarkdownLabel ?: @""
57
+ image:[RCTUIImage systemImageNamed:@"doc.text"]
58
+ identifier:nil
59
+ handler:^(__kindof UIAction *action) { copyMarkdown(); }]];
60
+ }
61
+ return [UIMenu menuWithTitle:@"" children:children];
62
+ }];
63
+ }
64
+ #endif
65
+
66
+ NS_ASSUME_NONNULL_END
@@ -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