react-native-enriched-markdown 0.1.0 → 0.2.0

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 (226) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +551 -0
  3. package/ReactNativeEnrichedMarkdown.podspec +27 -0
  4. package/android/build.gradle +101 -0
  5. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +54 -0
  6. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +26 -0
  7. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ComponentDescriptors.cpp +22 -0
  8. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ComponentDescriptors.h +24 -0
  9. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +33 -0
  10. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +31 -0
  11. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +82 -0
  12. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +1388 -0
  13. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ShadowNodes.cpp +17 -0
  14. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ShadowNodes.h +32 -0
  15. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/States.cpp +16 -0
  16. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/States.h +20 -0
  17. package/android/gradle.properties +5 -0
  18. package/android/src/main/AndroidManifest.xml +2 -0
  19. package/android/src/main/baseline-prof.txt +65 -0
  20. package/android/src/main/cpp/jni-adapter.cpp +220 -0
  21. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +270 -0
  22. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextLayoutManager.kt +15 -0
  23. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +173 -0
  24. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextPackage.kt +17 -0
  25. package/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt +385 -0
  26. package/android/src/main/java/com/swmansion/enriched/markdown/accessibility/MarkdownAccessibilityHelper.kt +279 -0
  27. package/android/src/main/java/com/swmansion/enriched/markdown/events/LinkLongPressEvent.kt +23 -0
  28. package/android/src/main/java/com/swmansion/enriched/markdown/events/LinkPressEvent.kt +23 -0
  29. package/android/src/main/java/com/swmansion/enriched/markdown/parser/MarkdownASTNode.kt +31 -0
  30. package/android/src/main/java/com/swmansion/enriched/markdown/parser/Parser.kt +62 -0
  31. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockStyleContext.kt +166 -0
  32. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockquoteRenderer.kt +84 -0
  33. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeBlockRenderer.kt +104 -0
  34. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeRenderer.kt +36 -0
  35. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/DocumentRenderer.kt +16 -0
  36. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/EmphasisRenderer.kt +27 -0
  37. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/HeadingRenderer.kt +70 -0
  38. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ImageRenderer.kt +68 -0
  39. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LineBreakRenderer.kt +16 -0
  40. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +29 -0
  41. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListContextManager.kt +105 -0
  42. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListItemRenderer.kt +59 -0
  43. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListRenderer.kt +76 -0
  44. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/NodeRenderer.kt +103 -0
  45. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ParagraphRenderer.kt +80 -0
  46. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/Renderer.kt +109 -0
  47. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +86 -0
  48. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/StrikethroughRenderer.kt +27 -0
  49. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/StrongRenderer.kt +27 -0
  50. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/TextRenderer.kt +30 -0
  51. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ThematicBreakRenderer.kt +45 -0
  52. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/UnderlineRenderer.kt +27 -0
  53. package/android/src/main/java/com/swmansion/enriched/markdown/spans/BaseListSpan.kt +136 -0
  54. package/android/src/main/java/com/swmansion/enriched/markdown/spans/BlockquoteSpan.kt +135 -0
  55. package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBackgroundSpan.kt +180 -0
  56. package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBlockSpan.kt +196 -0
  57. package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeSpan.kt +27 -0
  58. package/android/src/main/java/com/swmansion/enriched/markdown/spans/EmphasisSpan.kt +34 -0
  59. package/android/src/main/java/com/swmansion/enriched/markdown/spans/HeadingSpan.kt +38 -0
  60. package/android/src/main/java/com/swmansion/enriched/markdown/spans/ImageSpan.kt +321 -0
  61. package/android/src/main/java/com/swmansion/enriched/markdown/spans/LineHeightSpan.kt +27 -0
  62. package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkSpan.kt +51 -0
  63. package/android/src/main/java/com/swmansion/enriched/markdown/spans/MarginBottomSpan.kt +76 -0
  64. package/android/src/main/java/com/swmansion/enriched/markdown/spans/OrderedListSpan.kt +87 -0
  65. package/android/src/main/java/com/swmansion/enriched/markdown/spans/StrikethroughSpan.kt +12 -0
  66. package/android/src/main/java/com/swmansion/enriched/markdown/spans/StrongSpan.kt +37 -0
  67. package/android/src/main/java/com/swmansion/enriched/markdown/spans/TextSpan.kt +26 -0
  68. package/android/src/main/java/com/swmansion/enriched/markdown/spans/ThematicBreakSpan.kt +69 -0
  69. package/android/src/main/java/com/swmansion/enriched/markdown/spans/UnorderedListSpan.kt +69 -0
  70. package/android/src/main/java/com/swmansion/enriched/markdown/styles/BaseBlockStyle.kt +11 -0
  71. package/android/src/main/java/com/swmansion/enriched/markdown/styles/BlockquoteStyle.kt +51 -0
  72. package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeBlockStyle.kt +54 -0
  73. package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeStyle.kt +21 -0
  74. package/android/src/main/java/com/swmansion/enriched/markdown/styles/EmphasisStyle.kt +17 -0
  75. package/android/src/main/java/com/swmansion/enriched/markdown/styles/HeadingStyle.kt +33 -0
  76. package/android/src/main/java/com/swmansion/enriched/markdown/styles/ImageStyle.kt +23 -0
  77. package/android/src/main/java/com/swmansion/enriched/markdown/styles/InlineImageStyle.kt +17 -0
  78. package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkStyle.kt +19 -0
  79. package/android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt +57 -0
  80. package/android/src/main/java/com/swmansion/enriched/markdown/styles/ParagraphStyle.kt +33 -0
  81. package/android/src/main/java/com/swmansion/enriched/markdown/styles/StrikethroughStyle.kt +17 -0
  82. package/android/src/main/java/com/swmansion/enriched/markdown/styles/StrongStyle.kt +17 -0
  83. package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleConfig.kt +211 -0
  84. package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleParser.kt +92 -0
  85. package/android/src/main/java/com/swmansion/enriched/markdown/styles/TextAlignment.kt +32 -0
  86. package/android/src/main/java/com/swmansion/enriched/markdown/styles/ThematicBreakStyle.kt +23 -0
  87. package/android/src/main/java/com/swmansion/enriched/markdown/styles/UnderlineStyle.kt +17 -0
  88. package/android/src/main/java/com/swmansion/enriched/markdown/utils/AsyncDrawable.kt +91 -0
  89. package/android/src/main/java/com/swmansion/enriched/markdown/utils/HTMLGenerator.kt +827 -0
  90. package/android/src/main/java/com/swmansion/enriched/markdown/utils/LinkLongPressMovementMethod.kt +121 -0
  91. package/android/src/main/java/com/swmansion/enriched/markdown/utils/MarkdownExtractor.kt +375 -0
  92. package/android/src/main/java/com/swmansion/enriched/markdown/utils/SelectionActionMode.kt +139 -0
  93. package/android/src/main/java/com/swmansion/enriched/markdown/utils/Utils.kt +183 -0
  94. package/android/src/main/jni/CMakeLists.txt +70 -0
  95. package/android/src/main/jni/EnrichedMarkdownTextSpec.cpp +21 -0
  96. package/android/src/main/jni/EnrichedMarkdownTextSpec.h +25 -0
  97. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextComponentDescriptor.h +29 -0
  98. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextMeasurementManager.cpp +45 -0
  99. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextMeasurementManager.h +21 -0
  100. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.cpp +20 -0
  101. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.h +37 -0
  102. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/conversions.h +22 -0
  103. package/cpp/md4c/md4c.c +6492 -0
  104. package/cpp/md4c/md4c.h +402 -0
  105. package/cpp/parser/MD4CParser.cpp +327 -0
  106. package/cpp/parser/MD4CParser.hpp +27 -0
  107. package/cpp/parser/MarkdownASTNode.hpp +51 -0
  108. package/ios/EnrichedMarkdownText.h +18 -0
  109. package/ios/EnrichedMarkdownText.mm +1401 -0
  110. package/ios/attachments/EnrichedMarkdownImageAttachment.h +23 -0
  111. package/ios/attachments/EnrichedMarkdownImageAttachment.m +185 -0
  112. package/ios/attachments/ThematicBreakAttachment.h +15 -0
  113. package/ios/attachments/ThematicBreakAttachment.m +33 -0
  114. package/ios/generated/EnrichedMarkdownTextSpec/ComponentDescriptors.cpp +22 -0
  115. package/ios/generated/EnrichedMarkdownTextSpec/ComponentDescriptors.h +24 -0
  116. package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.cpp +33 -0
  117. package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.h +31 -0
  118. package/ios/generated/EnrichedMarkdownTextSpec/Props.cpp +82 -0
  119. package/ios/generated/EnrichedMarkdownTextSpec/Props.h +1388 -0
  120. package/ios/generated/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +20 -0
  121. package/ios/generated/EnrichedMarkdownTextSpec/ShadowNodes.cpp +17 -0
  122. package/ios/generated/EnrichedMarkdownTextSpec/ShadowNodes.h +32 -0
  123. package/ios/generated/EnrichedMarkdownTextSpec/States.cpp +16 -0
  124. package/ios/generated/EnrichedMarkdownTextSpec/States.h +20 -0
  125. package/ios/internals/EnrichedMarkdownTextComponentDescriptor.h +19 -0
  126. package/ios/internals/EnrichedMarkdownTextShadowNode.h +43 -0
  127. package/ios/internals/EnrichedMarkdownTextShadowNode.mm +85 -0
  128. package/ios/internals/EnrichedMarkdownTextState.h +24 -0
  129. package/ios/parser/MarkdownASTNode.h +35 -0
  130. package/ios/parser/MarkdownASTNode.m +32 -0
  131. package/ios/parser/MarkdownParser.h +17 -0
  132. package/ios/parser/MarkdownParser.mm +42 -0
  133. package/ios/parser/MarkdownParserBridge.mm +120 -0
  134. package/ios/renderer/AttributedRenderer.h +11 -0
  135. package/ios/renderer/AttributedRenderer.m +152 -0
  136. package/ios/renderer/BlockquoteRenderer.h +7 -0
  137. package/ios/renderer/BlockquoteRenderer.m +160 -0
  138. package/ios/renderer/CodeBlockRenderer.h +10 -0
  139. package/ios/renderer/CodeBlockRenderer.m +90 -0
  140. package/ios/renderer/CodeRenderer.h +10 -0
  141. package/ios/renderer/CodeRenderer.m +60 -0
  142. package/ios/renderer/EmphasisRenderer.h +6 -0
  143. package/ios/renderer/EmphasisRenderer.m +96 -0
  144. package/ios/renderer/HeadingRenderer.h +7 -0
  145. package/ios/renderer/HeadingRenderer.m +105 -0
  146. package/ios/renderer/ImageRenderer.h +12 -0
  147. package/ios/renderer/ImageRenderer.m +83 -0
  148. package/ios/renderer/LinkRenderer.h +7 -0
  149. package/ios/renderer/LinkRenderer.m +69 -0
  150. package/ios/renderer/ListItemRenderer.h +16 -0
  151. package/ios/renderer/ListItemRenderer.m +103 -0
  152. package/ios/renderer/ListRenderer.h +13 -0
  153. package/ios/renderer/ListRenderer.m +70 -0
  154. package/ios/renderer/NodeRenderer.h +8 -0
  155. package/ios/renderer/ParagraphRenderer.h +7 -0
  156. package/ios/renderer/ParagraphRenderer.m +80 -0
  157. package/ios/renderer/RenderContext.h +105 -0
  158. package/ios/renderer/RenderContext.m +312 -0
  159. package/ios/renderer/RendererFactory.h +12 -0
  160. package/ios/renderer/RendererFactory.m +116 -0
  161. package/ios/renderer/StrikethroughRenderer.h +6 -0
  162. package/ios/renderer/StrikethroughRenderer.m +40 -0
  163. package/ios/renderer/StrongRenderer.h +6 -0
  164. package/ios/renderer/StrongRenderer.m +83 -0
  165. package/ios/renderer/TextRenderer.h +6 -0
  166. package/ios/renderer/TextRenderer.m +16 -0
  167. package/ios/renderer/ThematicBreakRenderer.h +5 -0
  168. package/ios/renderer/ThematicBreakRenderer.m +53 -0
  169. package/ios/renderer/UnderlineRenderer.h +6 -0
  170. package/ios/renderer/UnderlineRenderer.m +39 -0
  171. package/ios/styles/StyleConfig.h +274 -0
  172. package/ios/styles/StyleConfig.mm +1806 -0
  173. package/ios/utils/AccessibilityInfo.h +35 -0
  174. package/ios/utils/AccessibilityInfo.m +24 -0
  175. package/ios/utils/BlockquoteBorder.h +20 -0
  176. package/ios/utils/BlockquoteBorder.m +92 -0
  177. package/ios/utils/CodeBackground.h +19 -0
  178. package/ios/utils/CodeBackground.m +191 -0
  179. package/ios/utils/CodeBlockBackground.h +17 -0
  180. package/ios/utils/CodeBlockBackground.m +82 -0
  181. package/ios/utils/EditMenuUtils.h +22 -0
  182. package/ios/utils/EditMenuUtils.m +118 -0
  183. package/ios/utils/FontUtils.h +25 -0
  184. package/ios/utils/FontUtils.m +27 -0
  185. package/ios/utils/HTMLGenerator.h +20 -0
  186. package/ios/utils/HTMLGenerator.m +793 -0
  187. package/ios/utils/LastElementUtils.h +53 -0
  188. package/ios/utils/ListMarkerDrawer.h +15 -0
  189. package/ios/utils/ListMarkerDrawer.m +127 -0
  190. package/ios/utils/MarkdownAccessibilityElementBuilder.h +45 -0
  191. package/ios/utils/MarkdownAccessibilityElementBuilder.m +323 -0
  192. package/ios/utils/MarkdownExtractor.h +17 -0
  193. package/ios/utils/MarkdownExtractor.m +308 -0
  194. package/ios/utils/ParagraphStyleUtils.h +21 -0
  195. package/ios/utils/ParagraphStyleUtils.m +111 -0
  196. package/ios/utils/PasteboardUtils.h +36 -0
  197. package/ios/utils/PasteboardUtils.m +134 -0
  198. package/ios/utils/RTFExportUtils.h +24 -0
  199. package/ios/utils/RTFExportUtils.m +297 -0
  200. package/ios/utils/RuntimeKeys.h +38 -0
  201. package/ios/utils/RuntimeKeys.m +11 -0
  202. package/ios/utils/TextViewLayoutManager.h +14 -0
  203. package/ios/utils/TextViewLayoutManager.mm +113 -0
  204. package/lib/module/EnrichedMarkdownText.js +65 -0
  205. package/lib/module/EnrichedMarkdownText.js.map +1 -0
  206. package/lib/module/EnrichedMarkdownTextNativeComponent.ts +210 -0
  207. package/lib/module/index.js +4 -0
  208. package/lib/module/index.js.map +1 -0
  209. package/lib/module/normalizeMarkdownStyle.js +384 -0
  210. package/lib/module/normalizeMarkdownStyle.js.map +1 -0
  211. package/lib/module/package.json +1 -0
  212. package/lib/typescript/package.json +1 -0
  213. package/lib/typescript/src/EnrichedMarkdownText.d.ts +183 -0
  214. package/lib/typescript/src/EnrichedMarkdownText.d.ts.map +1 -0
  215. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +185 -0
  216. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -0
  217. package/lib/typescript/src/index.d.ts +4 -0
  218. package/lib/typescript/src/index.d.ts.map +1 -0
  219. package/lib/typescript/src/normalizeMarkdownStyle.d.ts +6 -0
  220. package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -0
  221. package/package.json +186 -1
  222. package/react-native.config.js +13 -0
  223. package/src/EnrichedMarkdownText.tsx +280 -0
  224. package/src/EnrichedMarkdownTextNativeComponent.ts +210 -0
  225. package/src/index.tsx +10 -0
  226. package/src/normalizeMarkdownStyle.ts +423 -0
@@ -0,0 +1,1401 @@
1
+ #import "EnrichedMarkdownText.h"
2
+ #import "AccessibilityInfo.h"
3
+ #import "AttributedRenderer.h"
4
+ #import "CodeBlockBackground.h"
5
+ #import "EditMenuUtils.h"
6
+ #import "EnrichedMarkdownImageAttachment.h"
7
+ #import "FontUtils.h"
8
+ #import "LastElementUtils.h"
9
+ #import "MarkdownASTNode.h"
10
+ #import "MarkdownAccessibilityElementBuilder.h"
11
+ #import "MarkdownExtractor.h"
12
+ #import "MarkdownParser.h"
13
+ #import "ParagraphStyleUtils.h"
14
+ #import "RenderContext.h"
15
+ #import "RuntimeKeys.h"
16
+ #import "StyleConfig.h"
17
+ #import "TextViewLayoutManager.h"
18
+ #import <React/RCTUtils.h>
19
+ #import <objc/runtime.h>
20
+
21
+ #import <ReactNativeEnrichedMarkdown/EnrichedMarkdownTextComponentDescriptor.h>
22
+ #import <ReactNativeEnrichedMarkdown/EventEmitters.h>
23
+ #import <ReactNativeEnrichedMarkdown/Props.h>
24
+ #import <ReactNativeEnrichedMarkdown/RCTComponentViewHelpers.h>
25
+
26
+ #import "RCTFabricComponentsPlugins.h"
27
+ #import <React/RCTConversions.h>
28
+ #import <React/RCTFont.h>
29
+ #import <react/utils/ManagedObjectWrapper.h>
30
+
31
+ using namespace facebook::react;
32
+
33
+ @interface EnrichedMarkdownText () <RCTEnrichedMarkdownTextViewProtocol, UITextViewDelegate>
34
+ - (void)setupTextView;
35
+ - (void)renderMarkdownContent:(NSString *)markdownString;
36
+ - (void)applyRenderedText:(NSMutableAttributedString *)attributedText;
37
+ - (void)textTapped:(UITapGestureRecognizer *)recognizer;
38
+ - (void)setupLayoutManager;
39
+ @end
40
+
41
+ @implementation EnrichedMarkdownText {
42
+ UITextView *_textView;
43
+ MarkdownParser *_parser;
44
+ NSString *_cachedMarkdown;
45
+ StyleConfig *_config;
46
+ Md4cFlags *_md4cFlags;
47
+
48
+ // Background rendering support
49
+ dispatch_queue_t _renderQueue;
50
+ NSUInteger _currentRenderId;
51
+ BOOL _blockAsyncRender;
52
+
53
+ EnrichedMarkdownTextShadowNode::ConcreteState::Shared _state;
54
+ int _heightUpdateCounter;
55
+
56
+ // Font scale tracking
57
+ CGFloat _currentFontScale;
58
+ BOOL _allowFontScaling;
59
+ CGFloat _maxFontSizeMultiplier;
60
+
61
+ // Last element marginBottom tracking
62
+ CGFloat _lastElementMarginBottom;
63
+ BOOL _allowTrailingMargin;
64
+
65
+ // iOS link preview control
66
+ BOOL _enableLinkPreview;
67
+
68
+ // Accessibility data for VoiceOver
69
+ AccessibilityInfo *_accessibilityInfo;
70
+ NSMutableArray<UIAccessibilityElement *> *_accessibilityElements;
71
+ }
72
+
73
+ + (ComponentDescriptorProvider)componentDescriptorProvider
74
+ {
75
+ return concreteComponentDescriptorProvider<EnrichedMarkdownTextComponentDescriptor>();
76
+ }
77
+
78
+ #pragma mark - Measuring and State
79
+
80
+ - (CGSize)measureSize:(CGFloat)maxWidth
81
+ {
82
+ NSAttributedString *text = _textView.attributedText;
83
+ CGFloat defaultHeight = [UIFont systemFontOfSize:16.0].lineHeight;
84
+
85
+ if (text.length == 0) {
86
+ return CGSizeMake(maxWidth, defaultHeight);
87
+ }
88
+
89
+ // Use UITextView's layout manager for measurement to avoid
90
+ // boundingRectWithSize: height discrepancies with NSTextAttachment objects.
91
+ _textView.textContainer.size = CGSizeMake(maxWidth, CGFLOAT_MAX);
92
+ [_textView.layoutManager ensureLayoutForTextContainer:_textView.textContainer];
93
+ CGRect usedRect = [_textView.layoutManager usedRectForTextContainer:_textView.textContainer];
94
+
95
+ CGFloat measuredWidth = ceil(usedRect.size.width);
96
+ CGFloat measuredHeight = usedRect.size.height;
97
+
98
+ // When text ends with \n (e.g. code block's bottom padding spacer),
99
+ // TextKit creates an "extra line fragment" after it that adds unwanted height.
100
+ CGRect extraFragment = _textView.layoutManager.extraLineFragmentRect;
101
+ if (!CGRectIsEmpty(extraFragment)) {
102
+ measuredHeight -= extraFragment.size.height;
103
+ }
104
+
105
+ // Code block's bottom padding is a spacer \n with minimumLineHeight = codeBlockPadding.
106
+ // The layout manager may not size it accurately, so add the padding explicitly.
107
+ if (isLastElementCodeBlock(text)) {
108
+ measuredHeight += [_config codeBlockPadding];
109
+ }
110
+
111
+ if (_allowTrailingMargin && _lastElementMarginBottom > 0) {
112
+ measuredHeight += _lastElementMarginBottom;
113
+ }
114
+
115
+ return CGSizeMake(measuredWidth, ceil(measuredHeight));
116
+ }
117
+
118
+ - (void)updateState:(const facebook::react::State::Shared &)state
119
+ oldState:(const facebook::react::State::Shared &)oldState
120
+ {
121
+ _state = std::static_pointer_cast<const EnrichedMarkdownTextShadowNode::ConcreteState>(state);
122
+
123
+ // Trigger initial height calculation when state is first set
124
+ if (oldState == nullptr) {
125
+ [self requestHeightUpdate];
126
+ }
127
+ }
128
+
129
+ - (void)requestHeightUpdate
130
+ {
131
+ if (_state == nullptr) {
132
+ return;
133
+ }
134
+
135
+ _heightUpdateCounter++;
136
+ auto selfRef = wrapManagedObjectWeakly(self);
137
+ _state->updateState(EnrichedMarkdownTextState(_heightUpdateCounter, selfRef));
138
+ }
139
+
140
+ - (instancetype)initWithFrame:(CGRect)frame
141
+ {
142
+ if (self = [super initWithFrame:frame]) {
143
+ static const auto defaultProps = std::make_shared<const EnrichedMarkdownTextProps>();
144
+ _props = defaultProps;
145
+
146
+ self.backgroundColor = [UIColor clearColor];
147
+ _parser = [[MarkdownParser alloc] init];
148
+ _md4cFlags = [Md4cFlags defaultFlags];
149
+
150
+ // Serial queue for background rendering
151
+ _renderQueue = dispatch_queue_create("com.swmansion.enriched.markdown.render", DISPATCH_QUEUE_SERIAL);
152
+ _currentRenderId = 0;
153
+
154
+ // Initialize font scale from current content size category
155
+ _allowFontScaling = YES;
156
+ _maxFontSizeMultiplier = 0;
157
+ _allowTrailingMargin = NO;
158
+ _currentFontScale = RCTFontSizeMultiplier();
159
+ _enableLinkPreview = YES;
160
+
161
+ [[NSNotificationCenter defaultCenter] addObserver:self
162
+ selector:@selector(contentSizeCategoryDidChange:)
163
+ name:UIContentSizeCategoryDidChangeNotification
164
+ object:nil];
165
+
166
+ [self setupTextView];
167
+ }
168
+
169
+ return self;
170
+ }
171
+
172
+ - (void)dealloc
173
+ {
174
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:UIContentSizeCategoryDidChangeNotification object:nil];
175
+ }
176
+
177
+ - (CGFloat)effectiveFontScale
178
+ {
179
+ // If font scaling is disabled, always return 1.0 (no scaling)
180
+ return _allowFontScaling ? _currentFontScale : 1.0;
181
+ }
182
+
183
+ - (void)contentSizeCategoryDidChange:(NSNotification *)notification
184
+ {
185
+ if (!_allowFontScaling) {
186
+ return;
187
+ }
188
+
189
+ CGFloat newFontScale = RCTFontSizeMultiplier();
190
+ if (_currentFontScale != newFontScale) {
191
+ _currentFontScale = newFontScale;
192
+
193
+ if (_config != nil) {
194
+ [_config setFontScaleMultiplier:[self effectiveFontScale]];
195
+ }
196
+
197
+ if (_cachedMarkdown != nil && _cachedMarkdown.length > 0) {
198
+ [self renderMarkdownContent:_cachedMarkdown];
199
+ }
200
+ }
201
+ }
202
+
203
+ - (void)setupTextView
204
+ {
205
+ _textView = [[UITextView alloc] init];
206
+ _textView.text = @"";
207
+ _textView.font = [UIFont systemFontOfSize:16.0];
208
+ _textView.backgroundColor = [UIColor clearColor];
209
+ _textView.textColor = [UIColor blackColor];
210
+ _textView.editable = NO;
211
+ _textView.delegate = self;
212
+ _textView.scrollEnabled = NO;
213
+ _textView.showsVerticalScrollIndicator = NO;
214
+ _textView.showsHorizontalScrollIndicator = NO;
215
+ _textView.textContainerInset = UIEdgeInsetsZero;
216
+ _textView.textContainer.lineFragmentPadding = 0;
217
+ // Disable UITextView's default link styling - we handle it directly in attributed strings
218
+ _textView.linkTextAttributes = @{};
219
+ // selectable controls text selection and link previews
220
+ // Default to YES to match the prop default
221
+ _textView.selectable = YES;
222
+ // Hide initially to prevent flash before content is rendered
223
+ _textView.hidden = YES;
224
+ // Disable textView's built-in accessibility - we provide custom elements with proper traits
225
+ _textView.accessibilityElementsHidden = YES;
226
+
227
+ // Add tap gesture recognizer
228
+ UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
229
+ action:@selector(textTapped:)];
230
+ [_textView addGestureRecognizer:tapRecognizer];
231
+
232
+ // Use RCTViewComponentView's contentView for automatic sizing
233
+ self.contentView = _textView;
234
+ }
235
+
236
+ - (void)didAddSubview:(UIView *)subview
237
+ {
238
+ [super didAddSubview:subview];
239
+
240
+ // Set up layout manager when text view is added
241
+ if (subview == _textView) {
242
+ [self setupLayoutManager];
243
+ }
244
+ }
245
+
246
+ - (void)willRemoveSubview:(UIView *)subview
247
+ {
248
+ // Clean up layout manager when text view is removed
249
+ if (subview == _textView && _textView.layoutManager != nil) {
250
+ NSLayoutManager *layoutManager = _textView.layoutManager;
251
+ if ([object_getClass(layoutManager) isEqual:[TextViewLayoutManager class]]) {
252
+ [layoutManager setValue:nil forKey:@"config"];
253
+ object_setClass(layoutManager, [NSLayoutManager class]);
254
+ }
255
+ }
256
+ [super willRemoveSubview:subview];
257
+ }
258
+
259
+ - (void)setupLayoutManager
260
+ {
261
+ // Set up custom layout manager for rich text custom drawing (code, blockquotes, etc.)
262
+ // This single manager can handle multiple element types
263
+ NSLayoutManager *layoutManager = _textView.layoutManager;
264
+ if (layoutManager != nil) {
265
+ layoutManager.allowsNonContiguousLayout = NO; // workaround for onScroll issue (like react-native-live-markdown)
266
+ object_setClass(layoutManager, [TextViewLayoutManager class]);
267
+
268
+ // Set config on layout manager (like react-native-live-markdown sets markdownUtils)
269
+ if (_config != nil) {
270
+ [layoutManager setValue:_config forKey:@"config"];
271
+ }
272
+ }
273
+ }
274
+
275
+ - (void)renderMarkdownContent:(NSString *)markdownString
276
+ {
277
+ // Skip async render for mock views (they use renderMarkdownSynchronously)
278
+ if (_blockAsyncRender) {
279
+ return;
280
+ }
281
+
282
+ _cachedMarkdown = [markdownString copy];
283
+
284
+ // Increment render ID to invalidate any in-flight renders
285
+ NSUInteger renderId = ++_currentRenderId;
286
+
287
+ // Capture state needed for background rendering
288
+ StyleConfig *config = [_config copy];
289
+ MarkdownParser *parser = _parser;
290
+ Md4cFlags *md4cFlags = [_md4cFlags copy];
291
+ NSUInteger inputLength = markdownString.length;
292
+ NSDate *scheduleStart = [NSDate date];
293
+
294
+ // Capture font scaling settings
295
+ BOOL allowFontScaling = _allowFontScaling;
296
+ CGFloat maxFontSizeMultiplier = _maxFontSizeMultiplier;
297
+
298
+ // Dispatch heavy work to background queue
299
+ dispatch_async(_renderQueue, ^{
300
+ // 1. Parse Markdown → AST (C++ md4c parser)
301
+ NSDate *parseStart = [NSDate date];
302
+ MarkdownASTNode *ast = [parser parseMarkdown:markdownString flags:md4cFlags];
303
+ if (!ast) {
304
+ return;
305
+ }
306
+ NSTimeInterval parseTime = [[NSDate date] timeIntervalSinceDate:parseStart] * 1000;
307
+ NSUInteger nodeCount = ast.children.count;
308
+
309
+ // 2. Render AST → NSAttributedString
310
+ NSDate *renderStart = [NSDate date];
311
+ AttributedRenderer *renderer = [[AttributedRenderer alloc] initWithConfig:config];
312
+ [renderer setAllowTrailingMargin:self->_allowTrailingMargin];
313
+ RenderContext *context = [RenderContext new];
314
+ context.allowFontScaling = allowFontScaling;
315
+ context.maxFontSizeMultiplier = maxFontSizeMultiplier;
316
+ NSMutableAttributedString *attributedText = [renderer renderRoot:ast context:context];
317
+
318
+ self->_lastElementMarginBottom = [renderer getLastElementMarginBottom];
319
+
320
+ // Add link attributes
321
+ for (NSUInteger i = 0; i < context.linkRanges.count; i++) {
322
+ NSValue *rangeValue = context.linkRanges[i];
323
+ NSRange range = [rangeValue rangeValue];
324
+ NSString *url = context.linkURLs[i];
325
+ [attributedText addAttribute:@"linkURL" value:url range:range];
326
+ }
327
+
328
+ // Capture accessibility info
329
+ AccessibilityInfo *accessibilityInfo = [AccessibilityInfo infoFromContext:context];
330
+
331
+ NSTimeInterval renderTime = [[NSDate date] timeIntervalSinceDate:renderStart] * 1000;
332
+ NSUInteger styledLength = attributedText.length;
333
+
334
+ // Apply result on main thread
335
+ dispatch_async(dispatch_get_main_queue(), ^{
336
+ // Check if this render is still current
337
+ if (renderId != self->_currentRenderId) {
338
+ return;
339
+ }
340
+
341
+ // Store accessibility info
342
+ self->_accessibilityInfo = accessibilityInfo;
343
+
344
+ [self applyRenderedText:attributedText];
345
+
346
+ NSTimeInterval totalTime = [[NSDate date] timeIntervalSinceDate:scheduleStart] * 1000;
347
+ NSLog(@"┌──────────────────────────────────────────────");
348
+ NSLog(@"│ 📝 Input: %lu chars of Markdown", (unsigned long)inputLength);
349
+ NSLog(@"│ ⚡ md4c (C++ native): %.0fms → %lu AST nodes", parseTime, (unsigned long)nodeCount);
350
+ NSLog(@"│ 🎨 NSAttributedString render: %.0fms → %lu styled chars", renderTime, (unsigned long)styledLength);
351
+ NSLog(@"│ ✅ Total time to display: %.0fms", totalTime);
352
+ NSLog(@"└──────────────────────────────────────────────");
353
+ });
354
+ });
355
+ }
356
+
357
+ // Synchronous rendering for mock view measurement (no UI updates needed)
358
+ - (void)renderMarkdownSynchronously:(NSString *)markdownString
359
+ {
360
+ if (!markdownString || markdownString.length == 0) {
361
+ return;
362
+ }
363
+
364
+ // Block any async renders triggered by updateProps
365
+ _blockAsyncRender = YES;
366
+ _cachedMarkdown = [markdownString copy];
367
+
368
+ MarkdownASTNode *ast = [_parser parseMarkdown:markdownString flags:_md4cFlags];
369
+ if (!ast) {
370
+ return;
371
+ }
372
+
373
+ AttributedRenderer *renderer = [[AttributedRenderer alloc] initWithConfig:_config];
374
+ [renderer setAllowTrailingMargin:_allowTrailingMargin];
375
+ RenderContext *context = [RenderContext new];
376
+ context.allowFontScaling = _allowFontScaling;
377
+ context.maxFontSizeMultiplier = _maxFontSizeMultiplier;
378
+ NSMutableAttributedString *attributedText = [renderer renderRoot:ast context:context];
379
+
380
+ _lastElementMarginBottom = [renderer getLastElementMarginBottom];
381
+
382
+ for (NSUInteger i = 0; i < context.linkRanges.count; i++) {
383
+ NSValue *rangeValue = context.linkRanges[i];
384
+ NSRange range = [rangeValue rangeValue];
385
+ NSString *url = context.linkURLs[i];
386
+ [attributedText addAttribute:@"linkURL" value:url range:range];
387
+ }
388
+
389
+ // Store accessibility info
390
+ _accessibilityInfo = [AccessibilityInfo infoFromContext:context];
391
+
392
+ _textView.attributedText = attributedText;
393
+ }
394
+
395
+ - (void)applyRenderedText:(NSMutableAttributedString *)attributedText
396
+ {
397
+ // Set config on the layout manager
398
+ NSLayoutManager *layoutManager = _textView.layoutManager;
399
+ if ([layoutManager isKindOfClass:[TextViewLayoutManager class]]) {
400
+ [layoutManager setValue:_config forKey:@"config"];
401
+ }
402
+
403
+ // Store text view on text container so attachments can access it
404
+ objc_setAssociatedObject(_textView.textContainer, kTextViewKey, _textView, OBJC_ASSOCIATION_ASSIGN);
405
+
406
+ _textView.attributedText = attributedText;
407
+
408
+ // Ensure layout is updated
409
+ [_textView.layoutManager ensureLayoutForTextContainer:_textView.textContainer];
410
+ [_textView.layoutManager invalidateLayoutForCharacterRange:NSMakeRange(0, attributedText.length)
411
+ actualCharacterRange:NULL];
412
+
413
+ [_textView setNeedsLayout];
414
+ [_textView setNeedsDisplay];
415
+ [self setNeedsLayout];
416
+
417
+ // Request height recalculation from shadow node FIRST
418
+ [self requestHeightUpdate];
419
+
420
+ // Build accessibility elements after layout is complete
421
+ [self buildAccessibilityElements];
422
+
423
+ // Show text view on next run loop, after layout has settled
424
+ if (_textView.hidden) {
425
+ dispatch_async(dispatch_get_main_queue(), ^{ self->_textView.hidden = NO; });
426
+ }
427
+ }
428
+
429
+ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
430
+ {
431
+ const auto &oldViewProps = *std::static_pointer_cast<EnrichedMarkdownTextProps const>(_props);
432
+ const auto &newViewProps = *std::static_pointer_cast<EnrichedMarkdownTextProps const>(props);
433
+
434
+ BOOL stylePropChanged = NO;
435
+
436
+ if (_config == nil) {
437
+ _config = [[StyleConfig alloc] init];
438
+ [_config setFontScaleMultiplier:[self effectiveFontScale]];
439
+ }
440
+
441
+ // Paragraph style
442
+ if (newViewProps.markdownStyle.paragraph.fontSize != oldViewProps.markdownStyle.paragraph.fontSize) {
443
+ [_config setParagraphFontSize:newViewProps.markdownStyle.paragraph.fontSize];
444
+ stylePropChanged = YES;
445
+ }
446
+
447
+ if (newViewProps.markdownStyle.paragraph.fontFamily != oldViewProps.markdownStyle.paragraph.fontFamily) {
448
+ if (!newViewProps.markdownStyle.paragraph.fontFamily.empty()) {
449
+ NSString *fontFamily =
450
+ [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.paragraph.fontFamily.c_str()];
451
+ [_config setParagraphFontFamily:fontFamily];
452
+ } else {
453
+ [_config setParagraphFontFamily:nullptr];
454
+ }
455
+ stylePropChanged = YES;
456
+ }
457
+
458
+ if (newViewProps.markdownStyle.paragraph.fontWeight != oldViewProps.markdownStyle.paragraph.fontWeight) {
459
+ if (!newViewProps.markdownStyle.paragraph.fontWeight.empty()) {
460
+ NSString *fontWeight =
461
+ [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.paragraph.fontWeight.c_str()];
462
+ [_config setParagraphFontWeight:fontWeight];
463
+ } else {
464
+ [_config setParagraphFontWeight:nullptr];
465
+ }
466
+ stylePropChanged = YES;
467
+ }
468
+
469
+ if (newViewProps.markdownStyle.paragraph.color != oldViewProps.markdownStyle.paragraph.color) {
470
+ if (newViewProps.markdownStyle.paragraph.color) {
471
+ UIColor *paragraphColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.paragraph.color);
472
+ [_config setParagraphColor:paragraphColor];
473
+ } else {
474
+ [_config setParagraphColor:nullptr];
475
+ }
476
+ stylePropChanged = YES;
477
+ }
478
+
479
+ if (newViewProps.markdownStyle.paragraph.marginTop != oldViewProps.markdownStyle.paragraph.marginTop) {
480
+ [_config setParagraphMarginTop:newViewProps.markdownStyle.paragraph.marginTop];
481
+ stylePropChanged = YES;
482
+ }
483
+
484
+ if (newViewProps.markdownStyle.paragraph.marginBottom != oldViewProps.markdownStyle.paragraph.marginBottom) {
485
+ [_config setParagraphMarginBottom:newViewProps.markdownStyle.paragraph.marginBottom];
486
+ stylePropChanged = YES;
487
+ }
488
+
489
+ if (newViewProps.markdownStyle.paragraph.lineHeight != oldViewProps.markdownStyle.paragraph.lineHeight) {
490
+ [_config setParagraphLineHeight:newViewProps.markdownStyle.paragraph.lineHeight];
491
+ stylePropChanged = YES;
492
+ }
493
+
494
+ if (newViewProps.markdownStyle.paragraph.textAlign != oldViewProps.markdownStyle.paragraph.textAlign) {
495
+ [_config setParagraphTextAlign:textAlignmentFromString(@(newViewProps.markdownStyle.paragraph.textAlign.c_str()))];
496
+ stylePropChanged = YES;
497
+ }
498
+
499
+ // H1 style
500
+ if (newViewProps.markdownStyle.h1.fontSize != oldViewProps.markdownStyle.h1.fontSize) {
501
+ [_config setH1FontSize:newViewProps.markdownStyle.h1.fontSize];
502
+ stylePropChanged = YES;
503
+ }
504
+
505
+ if (newViewProps.markdownStyle.h1.fontFamily != oldViewProps.markdownStyle.h1.fontFamily) {
506
+ if (!newViewProps.markdownStyle.h1.fontFamily.empty()) {
507
+ NSString *fontFamily = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.h1.fontFamily.c_str()];
508
+ [_config setH1FontFamily:fontFamily];
509
+ } else {
510
+ [_config setH1FontFamily:nullptr];
511
+ }
512
+ stylePropChanged = YES;
513
+ }
514
+
515
+ if (newViewProps.markdownStyle.h1.fontWeight != oldViewProps.markdownStyle.h1.fontWeight) {
516
+ if (!newViewProps.markdownStyle.h1.fontWeight.empty()) {
517
+ NSString *fontWeight = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.h1.fontWeight.c_str()];
518
+ [_config setH1FontWeight:fontWeight];
519
+ } else {
520
+ [_config setH1FontWeight:nullptr];
521
+ }
522
+ stylePropChanged = YES;
523
+ }
524
+
525
+ if (newViewProps.markdownStyle.h1.color != oldViewProps.markdownStyle.h1.color) {
526
+ if (newViewProps.markdownStyle.h1.color) {
527
+ UIColor *h1Color = RCTUIColorFromSharedColor(newViewProps.markdownStyle.h1.color);
528
+ [_config setH1Color:h1Color];
529
+ } else {
530
+ [_config setH1Color:nullptr];
531
+ }
532
+ stylePropChanged = YES;
533
+ }
534
+
535
+ if (newViewProps.markdownStyle.h1.marginTop != oldViewProps.markdownStyle.h1.marginTop) {
536
+ [_config setH1MarginTop:newViewProps.markdownStyle.h1.marginTop];
537
+ stylePropChanged = YES;
538
+ }
539
+
540
+ if (newViewProps.markdownStyle.h1.marginBottom != oldViewProps.markdownStyle.h1.marginBottom) {
541
+ [_config setH1MarginBottom:newViewProps.markdownStyle.h1.marginBottom];
542
+ stylePropChanged = YES;
543
+ }
544
+
545
+ if (newViewProps.markdownStyle.h1.lineHeight != oldViewProps.markdownStyle.h1.lineHeight) {
546
+ [_config setH1LineHeight:newViewProps.markdownStyle.h1.lineHeight];
547
+ stylePropChanged = YES;
548
+ }
549
+
550
+ if (newViewProps.markdownStyle.h1.textAlign != oldViewProps.markdownStyle.h1.textAlign) {
551
+ [_config setH1TextAlign:textAlignmentFromString(@(newViewProps.markdownStyle.h1.textAlign.c_str()))];
552
+ stylePropChanged = YES;
553
+ }
554
+
555
+ // H2 style
556
+ if (newViewProps.markdownStyle.h2.fontSize != oldViewProps.markdownStyle.h2.fontSize) {
557
+ [_config setH2FontSize:newViewProps.markdownStyle.h2.fontSize];
558
+ stylePropChanged = YES;
559
+ }
560
+
561
+ if (newViewProps.markdownStyle.h2.fontFamily != oldViewProps.markdownStyle.h2.fontFamily) {
562
+ if (!newViewProps.markdownStyle.h2.fontFamily.empty()) {
563
+ NSString *fontFamily = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.h2.fontFamily.c_str()];
564
+ [_config setH2FontFamily:fontFamily];
565
+ } else {
566
+ [_config setH2FontFamily:nullptr];
567
+ }
568
+ stylePropChanged = YES;
569
+ }
570
+
571
+ if (newViewProps.markdownStyle.h2.fontWeight != oldViewProps.markdownStyle.h2.fontWeight) {
572
+ if (!newViewProps.markdownStyle.h2.fontWeight.empty()) {
573
+ NSString *fontWeight = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.h2.fontWeight.c_str()];
574
+ [_config setH2FontWeight:fontWeight];
575
+ } else {
576
+ [_config setH2FontWeight:nullptr];
577
+ }
578
+ stylePropChanged = YES;
579
+ }
580
+
581
+ if (newViewProps.markdownStyle.h2.color != oldViewProps.markdownStyle.h2.color) {
582
+ if (newViewProps.markdownStyle.h2.color) {
583
+ UIColor *h2Color = RCTUIColorFromSharedColor(newViewProps.markdownStyle.h2.color);
584
+ [_config setH2Color:h2Color];
585
+ } else {
586
+ [_config setH2Color:nullptr];
587
+ }
588
+ stylePropChanged = YES;
589
+ }
590
+
591
+ if (newViewProps.markdownStyle.h2.marginTop != oldViewProps.markdownStyle.h2.marginTop) {
592
+ [_config setH2MarginTop:newViewProps.markdownStyle.h2.marginTop];
593
+ stylePropChanged = YES;
594
+ }
595
+
596
+ if (newViewProps.markdownStyle.h2.marginBottom != oldViewProps.markdownStyle.h2.marginBottom) {
597
+ [_config setH2MarginBottom:newViewProps.markdownStyle.h2.marginBottom];
598
+ stylePropChanged = YES;
599
+ }
600
+
601
+ if (newViewProps.markdownStyle.h2.lineHeight != oldViewProps.markdownStyle.h2.lineHeight) {
602
+ [_config setH2LineHeight:newViewProps.markdownStyle.h2.lineHeight];
603
+ stylePropChanged = YES;
604
+ }
605
+
606
+ if (newViewProps.markdownStyle.h2.textAlign != oldViewProps.markdownStyle.h2.textAlign) {
607
+ [_config setH2TextAlign:textAlignmentFromString(@(newViewProps.markdownStyle.h2.textAlign.c_str()))];
608
+ stylePropChanged = YES;
609
+ }
610
+
611
+ // H3 style
612
+ if (newViewProps.markdownStyle.h3.fontSize != oldViewProps.markdownStyle.h3.fontSize) {
613
+ [_config setH3FontSize:newViewProps.markdownStyle.h3.fontSize];
614
+ stylePropChanged = YES;
615
+ }
616
+
617
+ if (newViewProps.markdownStyle.h3.fontFamily != oldViewProps.markdownStyle.h3.fontFamily) {
618
+ if (!newViewProps.markdownStyle.h3.fontFamily.empty()) {
619
+ NSString *fontFamily = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.h3.fontFamily.c_str()];
620
+ [_config setH3FontFamily:fontFamily];
621
+ } else {
622
+ [_config setH3FontFamily:nullptr];
623
+ }
624
+ stylePropChanged = YES;
625
+ }
626
+
627
+ if (newViewProps.markdownStyle.h3.fontWeight != oldViewProps.markdownStyle.h3.fontWeight) {
628
+ if (!newViewProps.markdownStyle.h3.fontWeight.empty()) {
629
+ NSString *fontWeight = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.h3.fontWeight.c_str()];
630
+ [_config setH3FontWeight:fontWeight];
631
+ } else {
632
+ [_config setH3FontWeight:nullptr];
633
+ }
634
+ stylePropChanged = YES;
635
+ }
636
+
637
+ if (newViewProps.markdownStyle.h3.color != oldViewProps.markdownStyle.h3.color) {
638
+ if (newViewProps.markdownStyle.h3.color) {
639
+ UIColor *h3Color = RCTUIColorFromSharedColor(newViewProps.markdownStyle.h3.color);
640
+ [_config setH3Color:h3Color];
641
+ } else {
642
+ [_config setH3Color:nullptr];
643
+ }
644
+ stylePropChanged = YES;
645
+ }
646
+
647
+ if (newViewProps.markdownStyle.h3.marginTop != oldViewProps.markdownStyle.h3.marginTop) {
648
+ [_config setH3MarginTop:newViewProps.markdownStyle.h3.marginTop];
649
+ stylePropChanged = YES;
650
+ }
651
+
652
+ if (newViewProps.markdownStyle.h3.marginBottom != oldViewProps.markdownStyle.h3.marginBottom) {
653
+ [_config setH3MarginBottom:newViewProps.markdownStyle.h3.marginBottom];
654
+ stylePropChanged = YES;
655
+ }
656
+
657
+ if (newViewProps.markdownStyle.h3.lineHeight != oldViewProps.markdownStyle.h3.lineHeight) {
658
+ [_config setH3LineHeight:newViewProps.markdownStyle.h3.lineHeight];
659
+ stylePropChanged = YES;
660
+ }
661
+
662
+ if (newViewProps.markdownStyle.h3.textAlign != oldViewProps.markdownStyle.h3.textAlign) {
663
+ [_config setH3TextAlign:textAlignmentFromString(@(newViewProps.markdownStyle.h3.textAlign.c_str()))];
664
+ stylePropChanged = YES;
665
+ }
666
+
667
+ // H4 style
668
+ if (newViewProps.markdownStyle.h4.fontSize != oldViewProps.markdownStyle.h4.fontSize) {
669
+ [_config setH4FontSize:newViewProps.markdownStyle.h4.fontSize];
670
+ stylePropChanged = YES;
671
+ }
672
+
673
+ if (newViewProps.markdownStyle.h4.fontFamily != oldViewProps.markdownStyle.h4.fontFamily) {
674
+ if (!newViewProps.markdownStyle.h4.fontFamily.empty()) {
675
+ NSString *fontFamily = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.h4.fontFamily.c_str()];
676
+ [_config setH4FontFamily:fontFamily];
677
+ } else {
678
+ [_config setH4FontFamily:nullptr];
679
+ }
680
+ stylePropChanged = YES;
681
+ }
682
+
683
+ if (newViewProps.markdownStyle.h4.fontWeight != oldViewProps.markdownStyle.h4.fontWeight) {
684
+ if (!newViewProps.markdownStyle.h4.fontWeight.empty()) {
685
+ NSString *fontWeight = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.h4.fontWeight.c_str()];
686
+ [_config setH4FontWeight:fontWeight];
687
+ } else {
688
+ [_config setH4FontWeight:nullptr];
689
+ }
690
+ stylePropChanged = YES;
691
+ }
692
+
693
+ if (newViewProps.markdownStyle.h4.color != oldViewProps.markdownStyle.h4.color) {
694
+ if (newViewProps.markdownStyle.h4.color) {
695
+ UIColor *h4Color = RCTUIColorFromSharedColor(newViewProps.markdownStyle.h4.color);
696
+ [_config setH4Color:h4Color];
697
+ } else {
698
+ [_config setH4Color:nullptr];
699
+ }
700
+ stylePropChanged = YES;
701
+ }
702
+
703
+ if (newViewProps.markdownStyle.h4.marginTop != oldViewProps.markdownStyle.h4.marginTop) {
704
+ [_config setH4MarginTop:newViewProps.markdownStyle.h4.marginTop];
705
+ stylePropChanged = YES;
706
+ }
707
+
708
+ if (newViewProps.markdownStyle.h4.marginBottom != oldViewProps.markdownStyle.h4.marginBottom) {
709
+ [_config setH4MarginBottom:newViewProps.markdownStyle.h4.marginBottom];
710
+ stylePropChanged = YES;
711
+ }
712
+
713
+ if (newViewProps.markdownStyle.h4.lineHeight != oldViewProps.markdownStyle.h4.lineHeight) {
714
+ [_config setH4LineHeight:newViewProps.markdownStyle.h4.lineHeight];
715
+ stylePropChanged = YES;
716
+ }
717
+
718
+ if (newViewProps.markdownStyle.h4.textAlign != oldViewProps.markdownStyle.h4.textAlign) {
719
+ [_config setH4TextAlign:textAlignmentFromString(@(newViewProps.markdownStyle.h4.textAlign.c_str()))];
720
+ stylePropChanged = YES;
721
+ }
722
+
723
+ // H5 style
724
+ if (newViewProps.markdownStyle.h5.fontSize != oldViewProps.markdownStyle.h5.fontSize) {
725
+ [_config setH5FontSize:newViewProps.markdownStyle.h5.fontSize];
726
+ stylePropChanged = YES;
727
+ }
728
+
729
+ if (newViewProps.markdownStyle.h5.fontFamily != oldViewProps.markdownStyle.h5.fontFamily) {
730
+ if (!newViewProps.markdownStyle.h5.fontFamily.empty()) {
731
+ NSString *fontFamily = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.h5.fontFamily.c_str()];
732
+ [_config setH5FontFamily:fontFamily];
733
+ } else {
734
+ [_config setH5FontFamily:nullptr];
735
+ }
736
+ stylePropChanged = YES;
737
+ }
738
+
739
+ if (newViewProps.markdownStyle.h5.fontWeight != oldViewProps.markdownStyle.h5.fontWeight) {
740
+ if (!newViewProps.markdownStyle.h5.fontWeight.empty()) {
741
+ NSString *fontWeight = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.h5.fontWeight.c_str()];
742
+ [_config setH5FontWeight:fontWeight];
743
+ } else {
744
+ [_config setH5FontWeight:nullptr];
745
+ }
746
+ stylePropChanged = YES;
747
+ }
748
+
749
+ if (newViewProps.markdownStyle.h5.color != oldViewProps.markdownStyle.h5.color) {
750
+ if (newViewProps.markdownStyle.h5.color) {
751
+ UIColor *h5Color = RCTUIColorFromSharedColor(newViewProps.markdownStyle.h5.color);
752
+ [_config setH5Color:h5Color];
753
+ } else {
754
+ [_config setH5Color:nullptr];
755
+ }
756
+ stylePropChanged = YES;
757
+ }
758
+
759
+ if (newViewProps.markdownStyle.h5.marginTop != oldViewProps.markdownStyle.h5.marginTop) {
760
+ [_config setH5MarginTop:newViewProps.markdownStyle.h5.marginTop];
761
+ stylePropChanged = YES;
762
+ }
763
+
764
+ if (newViewProps.markdownStyle.h5.marginBottom != oldViewProps.markdownStyle.h5.marginBottom) {
765
+ [_config setH5MarginBottom:newViewProps.markdownStyle.h5.marginBottom];
766
+ stylePropChanged = YES;
767
+ }
768
+
769
+ if (newViewProps.markdownStyle.h5.lineHeight != oldViewProps.markdownStyle.h5.lineHeight) {
770
+ [_config setH5LineHeight:newViewProps.markdownStyle.h5.lineHeight];
771
+ stylePropChanged = YES;
772
+ }
773
+
774
+ if (newViewProps.markdownStyle.h5.textAlign != oldViewProps.markdownStyle.h5.textAlign) {
775
+ [_config setH5TextAlign:textAlignmentFromString(@(newViewProps.markdownStyle.h5.textAlign.c_str()))];
776
+ stylePropChanged = YES;
777
+ }
778
+
779
+ // H6 style
780
+ if (newViewProps.markdownStyle.h6.fontSize != oldViewProps.markdownStyle.h6.fontSize) {
781
+ [_config setH6FontSize:newViewProps.markdownStyle.h6.fontSize];
782
+ stylePropChanged = YES;
783
+ }
784
+
785
+ if (newViewProps.markdownStyle.h6.fontFamily != oldViewProps.markdownStyle.h6.fontFamily) {
786
+ if (!newViewProps.markdownStyle.h6.fontFamily.empty()) {
787
+ NSString *fontFamily = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.h6.fontFamily.c_str()];
788
+ [_config setH6FontFamily:fontFamily];
789
+ } else {
790
+ [_config setH6FontFamily:nullptr];
791
+ }
792
+ stylePropChanged = YES;
793
+ }
794
+
795
+ if (newViewProps.markdownStyle.h6.fontWeight != oldViewProps.markdownStyle.h6.fontWeight) {
796
+ if (!newViewProps.markdownStyle.h6.fontWeight.empty()) {
797
+ NSString *fontWeight = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.h6.fontWeight.c_str()];
798
+ [_config setH6FontWeight:fontWeight];
799
+ } else {
800
+ [_config setH6FontWeight:nullptr];
801
+ }
802
+ stylePropChanged = YES;
803
+ }
804
+
805
+ if (newViewProps.markdownStyle.h6.color != oldViewProps.markdownStyle.h6.color) {
806
+ if (newViewProps.markdownStyle.h6.color) {
807
+ UIColor *h6Color = RCTUIColorFromSharedColor(newViewProps.markdownStyle.h6.color);
808
+ [_config setH6Color:h6Color];
809
+ } else {
810
+ [_config setH6Color:nullptr];
811
+ }
812
+ stylePropChanged = YES;
813
+ }
814
+
815
+ if (newViewProps.markdownStyle.h6.marginTop != oldViewProps.markdownStyle.h6.marginTop) {
816
+ [_config setH6MarginTop:newViewProps.markdownStyle.h6.marginTop];
817
+ stylePropChanged = YES;
818
+ }
819
+
820
+ if (newViewProps.markdownStyle.h6.marginBottom != oldViewProps.markdownStyle.h6.marginBottom) {
821
+ [_config setH6MarginBottom:newViewProps.markdownStyle.h6.marginBottom];
822
+ stylePropChanged = YES;
823
+ }
824
+
825
+ if (newViewProps.markdownStyle.h6.lineHeight != oldViewProps.markdownStyle.h6.lineHeight) {
826
+ [_config setH6LineHeight:newViewProps.markdownStyle.h6.lineHeight];
827
+ stylePropChanged = YES;
828
+ }
829
+
830
+ if (newViewProps.markdownStyle.h6.textAlign != oldViewProps.markdownStyle.h6.textAlign) {
831
+ [_config setH6TextAlign:textAlignmentFromString(@(newViewProps.markdownStyle.h6.textAlign.c_str()))];
832
+ stylePropChanged = YES;
833
+ }
834
+
835
+ // Blockquote style
836
+ if (newViewProps.markdownStyle.blockquote.fontSize != oldViewProps.markdownStyle.blockquote.fontSize) {
837
+ [_config setBlockquoteFontSize:newViewProps.markdownStyle.blockquote.fontSize];
838
+ stylePropChanged = YES;
839
+ }
840
+
841
+ if (newViewProps.markdownStyle.blockquote.fontFamily != oldViewProps.markdownStyle.blockquote.fontFamily) {
842
+ NSString *fontFamily =
843
+ [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.blockquote.fontFamily.c_str()];
844
+ [_config setBlockquoteFontFamily:fontFamily];
845
+ stylePropChanged = YES;
846
+ }
847
+
848
+ if (newViewProps.markdownStyle.blockquote.fontWeight != oldViewProps.markdownStyle.blockquote.fontWeight) {
849
+ NSString *fontWeight =
850
+ [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.blockquote.fontWeight.c_str()];
851
+ [_config setBlockquoteFontWeight:fontWeight];
852
+ stylePropChanged = YES;
853
+ }
854
+
855
+ if (newViewProps.markdownStyle.blockquote.color != oldViewProps.markdownStyle.blockquote.color) {
856
+ UIColor *blockquoteColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.blockquote.color);
857
+ [_config setBlockquoteColor:blockquoteColor];
858
+ stylePropChanged = YES;
859
+ }
860
+
861
+ if (newViewProps.markdownStyle.blockquote.marginTop != oldViewProps.markdownStyle.blockquote.marginTop) {
862
+ [_config setBlockquoteMarginTop:newViewProps.markdownStyle.blockquote.marginTop];
863
+ stylePropChanged = YES;
864
+ }
865
+
866
+ if (newViewProps.markdownStyle.blockquote.marginBottom != oldViewProps.markdownStyle.blockquote.marginBottom) {
867
+ [_config setBlockquoteMarginBottom:newViewProps.markdownStyle.blockquote.marginBottom];
868
+ stylePropChanged = YES;
869
+ }
870
+
871
+ if (newViewProps.markdownStyle.blockquote.lineHeight != oldViewProps.markdownStyle.blockquote.lineHeight) {
872
+ [_config setBlockquoteLineHeight:newViewProps.markdownStyle.blockquote.lineHeight];
873
+ stylePropChanged = YES;
874
+ }
875
+
876
+ if (newViewProps.markdownStyle.blockquote.borderColor != oldViewProps.markdownStyle.blockquote.borderColor) {
877
+ UIColor *blockquoteBorderColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.blockquote.borderColor);
878
+ [_config setBlockquoteBorderColor:blockquoteBorderColor];
879
+ stylePropChanged = YES;
880
+ }
881
+
882
+ if (newViewProps.markdownStyle.blockquote.borderWidth != oldViewProps.markdownStyle.blockquote.borderWidth) {
883
+ [_config setBlockquoteBorderWidth:newViewProps.markdownStyle.blockquote.borderWidth];
884
+ stylePropChanged = YES;
885
+ }
886
+
887
+ if (newViewProps.markdownStyle.blockquote.gapWidth != oldViewProps.markdownStyle.blockquote.gapWidth) {
888
+ [_config setBlockquoteGapWidth:newViewProps.markdownStyle.blockquote.gapWidth];
889
+ stylePropChanged = YES;
890
+ }
891
+
892
+ if (newViewProps.markdownStyle.blockquote.backgroundColor != oldViewProps.markdownStyle.blockquote.backgroundColor) {
893
+ UIColor *blockquoteBackgroundColor =
894
+ RCTUIColorFromSharedColor(newViewProps.markdownStyle.blockquote.backgroundColor);
895
+ [_config setBlockquoteBackgroundColor:blockquoteBackgroundColor];
896
+ stylePropChanged = YES;
897
+ }
898
+
899
+ if (newViewProps.markdownStyle.link.color != oldViewProps.markdownStyle.link.color) {
900
+ UIColor *linkColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.link.color);
901
+ [_config setLinkColor:linkColor];
902
+ stylePropChanged = YES;
903
+ }
904
+
905
+ if (newViewProps.markdownStyle.link.underline != oldViewProps.markdownStyle.link.underline) {
906
+ [_config setLinkUnderline:newViewProps.markdownStyle.link.underline];
907
+ stylePropChanged = YES;
908
+ }
909
+
910
+ if (newViewProps.markdownStyle.strong.color != oldViewProps.markdownStyle.strong.color) {
911
+ if (newViewProps.markdownStyle.strong.color) {
912
+ UIColor *strongColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.strong.color);
913
+ [_config setStrongColor:strongColor];
914
+ } else {
915
+ [_config setStrongColor:nullptr];
916
+ }
917
+ stylePropChanged = YES;
918
+ }
919
+
920
+ if (newViewProps.markdownStyle.em.color != oldViewProps.markdownStyle.em.color) {
921
+ if (newViewProps.markdownStyle.em.color) {
922
+ UIColor *emphasisColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.em.color);
923
+ [_config setEmphasisColor:emphasisColor];
924
+ } else {
925
+ [_config setEmphasisColor:nullptr];
926
+ }
927
+ stylePropChanged = YES;
928
+ }
929
+
930
+ if (newViewProps.markdownStyle.strikethrough.color != oldViewProps.markdownStyle.strikethrough.color) {
931
+ UIColor *strikethroughColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.strikethrough.color);
932
+ [_config setStrikethroughColor:strikethroughColor];
933
+ stylePropChanged = YES;
934
+ }
935
+
936
+ if (newViewProps.markdownStyle.underline.color != oldViewProps.markdownStyle.underline.color) {
937
+ UIColor *underlineColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.underline.color);
938
+ [_config setUnderlineColor:underlineColor];
939
+ stylePropChanged = YES;
940
+ }
941
+
942
+ if (newViewProps.markdownStyle.code.color != oldViewProps.markdownStyle.code.color) {
943
+ if (newViewProps.markdownStyle.code.color) {
944
+ UIColor *codeColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.code.color);
945
+ [_config setCodeColor:codeColor];
946
+ } else {
947
+ [_config setCodeColor:nullptr];
948
+ }
949
+ stylePropChanged = YES;
950
+ }
951
+
952
+ if (newViewProps.markdownStyle.code.backgroundColor != oldViewProps.markdownStyle.code.backgroundColor) {
953
+ if (newViewProps.markdownStyle.code.backgroundColor) {
954
+ UIColor *codeBackgroundColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.code.backgroundColor);
955
+ [_config setCodeBackgroundColor:codeBackgroundColor];
956
+ } else {
957
+ [_config setCodeBackgroundColor:nullptr];
958
+ }
959
+ stylePropChanged = YES;
960
+ }
961
+
962
+ if (newViewProps.markdownStyle.code.borderColor != oldViewProps.markdownStyle.code.borderColor) {
963
+ if (newViewProps.markdownStyle.code.borderColor) {
964
+ UIColor *codeBorderColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.code.borderColor);
965
+ [_config setCodeBorderColor:codeBorderColor];
966
+ } else {
967
+ [_config setCodeBorderColor:nullptr];
968
+ }
969
+ stylePropChanged = YES;
970
+ }
971
+
972
+ if (newViewProps.markdownStyle.image.height != oldViewProps.markdownStyle.image.height) {
973
+ [_config setImageHeight:newViewProps.markdownStyle.image.height];
974
+ stylePropChanged = YES;
975
+ }
976
+
977
+ if (newViewProps.markdownStyle.image.borderRadius != oldViewProps.markdownStyle.image.borderRadius) {
978
+ [_config setImageBorderRadius:newViewProps.markdownStyle.image.borderRadius];
979
+ stylePropChanged = YES;
980
+ }
981
+
982
+ if (newViewProps.markdownStyle.image.marginTop != oldViewProps.markdownStyle.image.marginTop) {
983
+ [_config setImageMarginTop:newViewProps.markdownStyle.image.marginTop];
984
+ stylePropChanged = YES;
985
+ }
986
+
987
+ if (newViewProps.markdownStyle.image.marginBottom != oldViewProps.markdownStyle.image.marginBottom) {
988
+ [_config setImageMarginBottom:newViewProps.markdownStyle.image.marginBottom];
989
+ stylePropChanged = YES;
990
+ }
991
+
992
+ if (newViewProps.markdownStyle.inlineImage.size != oldViewProps.markdownStyle.inlineImage.size) {
993
+ [_config setInlineImageSize:newViewProps.markdownStyle.inlineImage.size];
994
+ stylePropChanged = YES;
995
+ }
996
+
997
+ // List style
998
+ if (newViewProps.markdownStyle.list.fontSize != oldViewProps.markdownStyle.list.fontSize) {
999
+ [_config setListStyleFontSize:newViewProps.markdownStyle.list.fontSize];
1000
+ stylePropChanged = YES;
1001
+ }
1002
+
1003
+ if (newViewProps.markdownStyle.list.fontFamily != oldViewProps.markdownStyle.list.fontFamily) {
1004
+ NSString *fontFamily = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.list.fontFamily.c_str()];
1005
+ [_config setListStyleFontFamily:fontFamily];
1006
+ stylePropChanged = YES;
1007
+ }
1008
+
1009
+ if (newViewProps.markdownStyle.list.fontWeight != oldViewProps.markdownStyle.list.fontWeight) {
1010
+ NSString *fontWeight = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.list.fontWeight.c_str()];
1011
+ [_config setListStyleFontWeight:fontWeight];
1012
+ stylePropChanged = YES;
1013
+ }
1014
+
1015
+ if (newViewProps.markdownStyle.list.color != oldViewProps.markdownStyle.list.color) {
1016
+ UIColor *listColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.list.color);
1017
+ [_config setListStyleColor:listColor];
1018
+ stylePropChanged = YES;
1019
+ }
1020
+
1021
+ if (newViewProps.markdownStyle.list.marginTop != oldViewProps.markdownStyle.list.marginTop) {
1022
+ [_config setListStyleMarginTop:newViewProps.markdownStyle.list.marginTop];
1023
+ stylePropChanged = YES;
1024
+ }
1025
+
1026
+ if (newViewProps.markdownStyle.list.marginBottom != oldViewProps.markdownStyle.list.marginBottom) {
1027
+ [_config setListStyleMarginBottom:newViewProps.markdownStyle.list.marginBottom];
1028
+ stylePropChanged = YES;
1029
+ }
1030
+
1031
+ if (newViewProps.markdownStyle.list.lineHeight != oldViewProps.markdownStyle.list.lineHeight) {
1032
+ [_config setListStyleLineHeight:newViewProps.markdownStyle.list.lineHeight];
1033
+ stylePropChanged = YES;
1034
+ }
1035
+
1036
+ if (newViewProps.markdownStyle.list.bulletColor != oldViewProps.markdownStyle.list.bulletColor) {
1037
+ UIColor *bulletColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.list.bulletColor);
1038
+ [_config setListStyleBulletColor:bulletColor];
1039
+ stylePropChanged = YES;
1040
+ }
1041
+
1042
+ if (newViewProps.markdownStyle.list.bulletSize != oldViewProps.markdownStyle.list.bulletSize) {
1043
+ [_config setListStyleBulletSize:newViewProps.markdownStyle.list.bulletSize];
1044
+ stylePropChanged = YES;
1045
+ }
1046
+
1047
+ if (newViewProps.markdownStyle.list.markerColor != oldViewProps.markdownStyle.list.markerColor) {
1048
+ UIColor *markerColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.list.markerColor);
1049
+ [_config setListStyleMarkerColor:markerColor];
1050
+ stylePropChanged = YES;
1051
+ }
1052
+
1053
+ if (newViewProps.markdownStyle.list.markerFontWeight != oldViewProps.markdownStyle.list.markerFontWeight) {
1054
+ NSString *markerFontWeight =
1055
+ [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.list.markerFontWeight.c_str()];
1056
+ [_config setListStyleMarkerFontWeight:markerFontWeight];
1057
+ stylePropChanged = YES;
1058
+ }
1059
+
1060
+ if (newViewProps.markdownStyle.list.gapWidth != oldViewProps.markdownStyle.list.gapWidth) {
1061
+ [_config setListStyleGapWidth:newViewProps.markdownStyle.list.gapWidth];
1062
+ stylePropChanged = YES;
1063
+ }
1064
+
1065
+ if (newViewProps.markdownStyle.list.marginLeft != oldViewProps.markdownStyle.list.marginLeft) {
1066
+ [_config setListStyleMarginLeft:newViewProps.markdownStyle.list.marginLeft];
1067
+ stylePropChanged = YES;
1068
+ }
1069
+
1070
+ // Code block style
1071
+ if (newViewProps.markdownStyle.codeBlock.fontSize != oldViewProps.markdownStyle.codeBlock.fontSize) {
1072
+ [_config setCodeBlockFontSize:newViewProps.markdownStyle.codeBlock.fontSize];
1073
+ stylePropChanged = YES;
1074
+ }
1075
+
1076
+ if (newViewProps.markdownStyle.codeBlock.fontFamily != oldViewProps.markdownStyle.codeBlock.fontFamily) {
1077
+ NSString *fontFamily =
1078
+ [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.codeBlock.fontFamily.c_str()];
1079
+ [_config setCodeBlockFontFamily:fontFamily];
1080
+ stylePropChanged = YES;
1081
+ }
1082
+
1083
+ if (newViewProps.markdownStyle.codeBlock.fontWeight != oldViewProps.markdownStyle.codeBlock.fontWeight) {
1084
+ NSString *fontWeight =
1085
+ [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.codeBlock.fontWeight.c_str()];
1086
+ [_config setCodeBlockFontWeight:fontWeight];
1087
+ stylePropChanged = YES;
1088
+ }
1089
+
1090
+ if (newViewProps.markdownStyle.codeBlock.color != oldViewProps.markdownStyle.codeBlock.color) {
1091
+ UIColor *codeBlockColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.codeBlock.color);
1092
+ [_config setCodeBlockColor:codeBlockColor];
1093
+ stylePropChanged = YES;
1094
+ }
1095
+
1096
+ if (newViewProps.markdownStyle.codeBlock.marginTop != oldViewProps.markdownStyle.codeBlock.marginTop) {
1097
+ [_config setCodeBlockMarginTop:newViewProps.markdownStyle.codeBlock.marginTop];
1098
+ stylePropChanged = YES;
1099
+ }
1100
+
1101
+ if (newViewProps.markdownStyle.codeBlock.marginBottom != oldViewProps.markdownStyle.codeBlock.marginBottom) {
1102
+ [_config setCodeBlockMarginBottom:newViewProps.markdownStyle.codeBlock.marginBottom];
1103
+ stylePropChanged = YES;
1104
+ }
1105
+
1106
+ if (newViewProps.markdownStyle.codeBlock.lineHeight != oldViewProps.markdownStyle.codeBlock.lineHeight) {
1107
+ [_config setCodeBlockLineHeight:newViewProps.markdownStyle.codeBlock.lineHeight];
1108
+ stylePropChanged = YES;
1109
+ }
1110
+
1111
+ if (newViewProps.markdownStyle.codeBlock.backgroundColor != oldViewProps.markdownStyle.codeBlock.backgroundColor) {
1112
+ UIColor *codeBlockBackgroundColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.codeBlock.backgroundColor);
1113
+ [_config setCodeBlockBackgroundColor:codeBlockBackgroundColor];
1114
+ stylePropChanged = YES;
1115
+ }
1116
+
1117
+ if (newViewProps.markdownStyle.codeBlock.borderColor != oldViewProps.markdownStyle.codeBlock.borderColor) {
1118
+ UIColor *codeBlockBorderColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.codeBlock.borderColor);
1119
+ [_config setCodeBlockBorderColor:codeBlockBorderColor];
1120
+ stylePropChanged = YES;
1121
+ }
1122
+
1123
+ if (newViewProps.markdownStyle.codeBlock.borderRadius != oldViewProps.markdownStyle.codeBlock.borderRadius) {
1124
+ [_config setCodeBlockBorderRadius:newViewProps.markdownStyle.codeBlock.borderRadius];
1125
+ stylePropChanged = YES;
1126
+ }
1127
+
1128
+ if (newViewProps.markdownStyle.codeBlock.borderWidth != oldViewProps.markdownStyle.codeBlock.borderWidth) {
1129
+ [_config setCodeBlockBorderWidth:newViewProps.markdownStyle.codeBlock.borderWidth];
1130
+ stylePropChanged = YES;
1131
+ }
1132
+
1133
+ if (newViewProps.markdownStyle.codeBlock.padding != oldViewProps.markdownStyle.codeBlock.padding) {
1134
+ [_config setCodeBlockPadding:newViewProps.markdownStyle.codeBlock.padding];
1135
+ stylePropChanged = YES;
1136
+ }
1137
+
1138
+ // Thematic break style
1139
+ if (newViewProps.markdownStyle.thematicBreak.color != oldViewProps.markdownStyle.thematicBreak.color) {
1140
+ UIColor *thematicBreakColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.thematicBreak.color);
1141
+ [_config setThematicBreakColor:thematicBreakColor];
1142
+ stylePropChanged = YES;
1143
+ }
1144
+
1145
+ if (newViewProps.markdownStyle.thematicBreak.height != oldViewProps.markdownStyle.thematicBreak.height) {
1146
+ [_config setThematicBreakHeight:newViewProps.markdownStyle.thematicBreak.height];
1147
+ stylePropChanged = YES;
1148
+ }
1149
+
1150
+ if (newViewProps.markdownStyle.thematicBreak.marginTop != oldViewProps.markdownStyle.thematicBreak.marginTop) {
1151
+ [_config setThematicBreakMarginTop:newViewProps.markdownStyle.thematicBreak.marginTop];
1152
+ stylePropChanged = YES;
1153
+ }
1154
+
1155
+ if (newViewProps.markdownStyle.thematicBreak.marginBottom != oldViewProps.markdownStyle.thematicBreak.marginBottom) {
1156
+ [_config setThematicBreakMarginBottom:newViewProps.markdownStyle.thematicBreak.marginBottom];
1157
+ stylePropChanged = YES;
1158
+ }
1159
+
1160
+ // Update config reference on layout manager if it's not already set
1161
+ NSLayoutManager *layoutManager = _textView.layoutManager;
1162
+ if ([layoutManager isKindOfClass:[TextViewLayoutManager class]]) {
1163
+ StyleConfig *currentConfig = [layoutManager valueForKey:@"config"];
1164
+ if (currentConfig != _config) {
1165
+ // Only update reference if it's different (first time setup)
1166
+ [layoutManager setValue:_config forKey:@"config"];
1167
+ }
1168
+ }
1169
+
1170
+ // Control text selection and link previews via selectable property
1171
+ // According to Apple docs, selectable controls whether text selection and link previews work
1172
+ // https://developer.apple.com/documentation/uikit/uitextview/isselectable
1173
+ if (_textView.selectable != newViewProps.selectable) {
1174
+ _textView.selectable = newViewProps.selectable;
1175
+ }
1176
+
1177
+ if (newViewProps.allowFontScaling != oldViewProps.allowFontScaling) {
1178
+ _allowFontScaling = newViewProps.allowFontScaling;
1179
+
1180
+ if (_config != nil) {
1181
+ [_config setFontScaleMultiplier:[self effectiveFontScale]];
1182
+ }
1183
+
1184
+ stylePropChanged = YES;
1185
+ }
1186
+
1187
+ if (newViewProps.maxFontSizeMultiplier != oldViewProps.maxFontSizeMultiplier) {
1188
+ _maxFontSizeMultiplier = newViewProps.maxFontSizeMultiplier;
1189
+
1190
+ if (_config != nil) {
1191
+ [_config setMaxFontSizeMultiplier:_maxFontSizeMultiplier];
1192
+ }
1193
+
1194
+ stylePropChanged = YES;
1195
+ }
1196
+
1197
+ // Update allowTrailingMargin
1198
+ if (newViewProps.allowTrailingMargin != oldViewProps.allowTrailingMargin) {
1199
+ _allowTrailingMargin = newViewProps.allowTrailingMargin;
1200
+ }
1201
+
1202
+ // Update md4cFlags
1203
+ BOOL md4cFlagsChanged = NO;
1204
+ if (newViewProps.md4cFlags.underline != oldViewProps.md4cFlags.underline) {
1205
+ _md4cFlags.underline = newViewProps.md4cFlags.underline;
1206
+ md4cFlagsChanged = YES;
1207
+ }
1208
+
1209
+ BOOL markdownChanged = oldViewProps.markdown != newViewProps.markdown;
1210
+ BOOL allowTrailingMarginChanged = newViewProps.allowTrailingMargin != oldViewProps.allowTrailingMargin;
1211
+
1212
+ _enableLinkPreview = newViewProps.enableLinkPreview;
1213
+
1214
+ if (markdownChanged || stylePropChanged || md4cFlagsChanged || allowTrailingMarginChanged) {
1215
+ NSString *markdownString = [[NSString alloc] initWithUTF8String:newViewProps.markdown.c_str()];
1216
+ [self renderMarkdownContent:markdownString];
1217
+ }
1218
+
1219
+ [super updateProps:props oldProps:oldProps];
1220
+ }
1221
+
1222
+ Class<RCTComponentViewProtocol> EnrichedMarkdownTextCls(void)
1223
+ {
1224
+ return EnrichedMarkdownText.class;
1225
+ }
1226
+
1227
+ - (void)textTapped:(UITapGestureRecognizer *)recognizer
1228
+ {
1229
+ /*
1230
+ * HOW LINK TAPPING WORKS:
1231
+ *
1232
+ * 1. SETUP PHASE (During Rendering):
1233
+ * - Each link gets a custom @"linkURL" attribute attached to its text range
1234
+ * - The URL is stored as the attribute's value
1235
+ * - This creates an "invisible map" of where links are in the text
1236
+ *
1237
+ * Example:
1238
+ * Text: "Check out this [link to React Native](https://reactnative.dev)"
1239
+ * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1240
+ * | | | | | | | | | | |
1241
+ * 0 5 10 15 20 25 30 35 40 45 50
1242
+ *
1243
+ * Attributes:
1244
+ * - Characters 15-29: @"linkURL" = "https://reactnative.dev"
1245
+ * - Characters 0-14: no special attributes
1246
+ * - Characters 30-50: no special attributes
1247
+ *
1248
+ * 2. TOUCH DETECTION PHASE (When User Taps):
1249
+ * - UITapGestureRecognizer detects the tap
1250
+ * - We get the tap coordinates relative to the text view
1251
+ * - We adjust for text container insets to get precise text coordinates
1252
+ */
1253
+
1254
+ UITextView *textView = (UITextView *)recognizer.view;
1255
+
1256
+ // Location of the tap in text-container coordinates
1257
+ NSLayoutManager *layoutManager = textView.layoutManager;
1258
+ CGPoint location = [recognizer locationInView:textView];
1259
+ location.x -= textView.textContainerInset.left;
1260
+ location.y -= textView.textContainerInset.top;
1261
+
1262
+ /*
1263
+ * 3. CHARACTER INDEX LOOKUP:
1264
+ * - NSLayoutManager converts the tap coordinates to a character index
1265
+ * - This tells us exactly which character in the text was tapped
1266
+ * - Uses UIKit's built-in text layout system (very accurate)
1267
+ */
1268
+ NSUInteger characterIndex;
1269
+ characterIndex = [layoutManager characterIndexForPoint:location
1270
+ inTextContainer:textView.textContainer
1271
+ fractionOfDistanceBetweenInsertionPoints:NULL];
1272
+
1273
+ /*
1274
+ * 4. LINK DETECTION:
1275
+ * - We check if there's a @"linkURL" attribute at the tapped character
1276
+ * - If found, we get the URL value and the effective range
1277
+ * - If it's a link, we emit the onLinkPress event to React Native
1278
+ *
1279
+ * COMPLETE FLOW:
1280
+ * 1. User taps → UITapGestureRecognizer fires
1281
+ * 2. Get coordinates → Convert to text container coordinates
1282
+ * 3. Find character → NSLayoutManager.characterIndexForPoint
1283
+ * 4. Check attributes → Look for @"linkURL" at that character
1284
+ * 5. If link found → Emit onLinkPress event with URL
1285
+ * 6. React Native → Receives event and shows alert
1286
+ */
1287
+ if (characterIndex < textView.textStorage.length) {
1288
+ NSRange range;
1289
+ NSString *url = [textView.attributedText attribute:@"linkURL" atIndex:characterIndex effectiveRange:&range];
1290
+
1291
+ if (url) {
1292
+ // Emit onLinkPress event to React Native
1293
+ const auto &eventEmitter = *std::static_pointer_cast<EnrichedMarkdownTextEventEmitter const>(_eventEmitter);
1294
+ eventEmitter.onLinkPress({.url = std::string([url UTF8String])});
1295
+ }
1296
+ }
1297
+ }
1298
+
1299
+ #pragma mark - UITextViewDelegate (Link Interaction)
1300
+
1301
+ - (BOOL)textView:(UITextView *)textView
1302
+ shouldInteractWithURL:(NSURL *)URL
1303
+ inRange:(NSRange)characterRange
1304
+ interaction:(UITextItemInteraction)interaction
1305
+ {
1306
+ // Only intercept long-press interactions
1307
+ if (interaction != UITextItemInteractionPresentActions) {
1308
+ return YES;
1309
+ }
1310
+
1311
+ // Safely extract the custom URL attribute
1312
+ NSString *urlString = [textView.attributedText attribute:@"linkURL"
1313
+ atIndex:characterRange.location
1314
+ effectiveRange:NULL];
1315
+
1316
+ // If link preview is enabled or no URL found, allow default system behavior
1317
+ if (!urlString || _enableLinkPreview) {
1318
+ return YES;
1319
+ }
1320
+
1321
+ // System preview disabled — emit onLinkLongPress event to React Native
1322
+ auto eventEmitter = std::static_pointer_cast<EnrichedMarkdownTextEventEmitter const>(_eventEmitter);
1323
+ if (eventEmitter) {
1324
+ eventEmitter->onLinkLongPress({.url = std::string([urlString UTF8String])});
1325
+ }
1326
+ return NO;
1327
+ }
1328
+
1329
+ #pragma mark - UITextViewDelegate (Edit Menu)
1330
+
1331
+ // Customizes the edit menu
1332
+ - (UIMenu *)textView:(UITextView *)textView
1333
+ editMenuForTextInRange:(NSRange)range
1334
+ suggestedActions:(NSArray<UIMenuElement *> *)suggestedActions API_AVAILABLE(ios(16.0))
1335
+ {
1336
+ return buildEditMenuForSelection(_textView.attributedText, range, _cachedMarkdown, _config, suggestedActions);
1337
+ }
1338
+
1339
+ #pragma mark - Accessibility (VoiceOver Navigation)
1340
+
1341
+ - (void)buildAccessibilityElements
1342
+ {
1343
+ _accessibilityElements = [MarkdownAccessibilityElementBuilder buildElementsForTextView:_textView
1344
+ info:_accessibilityInfo
1345
+ container:self];
1346
+ }
1347
+
1348
+ - (BOOL)isAccessibilityElement
1349
+ {
1350
+ return NO; // This is a container, not a single element
1351
+ }
1352
+
1353
+ - (NSInteger)accessibilityElementCount
1354
+ {
1355
+ return _accessibilityElements.count;
1356
+ }
1357
+
1358
+ - (id)accessibilityElementAtIndex:(NSInteger)index
1359
+ {
1360
+ if (index < 0 || index >= (NSInteger)_accessibilityElements.count) {
1361
+ return nil;
1362
+ }
1363
+ return _accessibilityElements[index];
1364
+ }
1365
+
1366
+ - (NSInteger)indexOfAccessibilityElement:(id)element
1367
+ {
1368
+ return [_accessibilityElements indexOfObject:element];
1369
+ }
1370
+
1371
+ - (NSArray *)accessibilityElements
1372
+ {
1373
+ return _accessibilityElements;
1374
+ }
1375
+
1376
+ - (NSArray<UIAccessibilityCustomRotor *> *)accessibilityCustomRotors
1377
+ {
1378
+ NSMutableArray<UIAccessibilityCustomRotor *> *rotors = [NSMutableArray array];
1379
+
1380
+ NSArray<UIAccessibilityElement *> *headingElements =
1381
+ [MarkdownAccessibilityElementBuilder filterHeadingElements:_accessibilityElements];
1382
+ if (headingElements.count > 0) {
1383
+ [rotors addObject:[MarkdownAccessibilityElementBuilder createHeadingRotorWithElements:headingElements]];
1384
+ }
1385
+
1386
+ NSArray<UIAccessibilityElement *> *linkElements =
1387
+ [MarkdownAccessibilityElementBuilder filterLinkElements:_accessibilityElements];
1388
+ if (linkElements.count > 0) {
1389
+ [rotors addObject:[MarkdownAccessibilityElementBuilder createLinkRotorWithElements:linkElements]];
1390
+ }
1391
+
1392
+ NSArray<UIAccessibilityElement *> *imageElements =
1393
+ [MarkdownAccessibilityElementBuilder filterImageElements:_accessibilityElements];
1394
+ if (imageElements.count > 0) {
1395
+ [rotors addObject:[MarkdownAccessibilityElementBuilder createImageRotorWithElements:imageElements]];
1396
+ }
1397
+
1398
+ return rotors;
1399
+ }
1400
+
1401
+ @end