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
@@ -1,6 +1,7 @@
1
1
  #import "RTFExportUtils.h"
2
2
  #import "BlockquoteBorder.h"
3
3
  #import "CodeBackground.h"
4
+ #import "ENRMLinkChip.h"
4
5
  #import "LastElementUtils.h"
5
6
  #import "ListItemRenderer.h"
6
7
  #import "RenderContext.h"
@@ -288,6 +289,7 @@ NSAttributedString *prepareAttributedStringForRTFExport(NSAttributedString *attr
288
289
  RCTUIColor *blockquoteBgColor = [styleConfig blockquoteBackgroundColor];
289
290
 
290
291
  processThematicBreaks(prepared);
292
+ ENRMApplyLinkChipBackgroundsAsTextBackground(prepared);
291
293
  processCodes(prepared, codeBgColor);
292
294
  processCodeBlocks(prepared, codeBlockBgColor);
293
295
  processBlockquotes(prepared, blockquoteBgColor);
@@ -523,6 +523,32 @@ BOOL applyMarkdownStyleToConfig(StyleConfig *config, const MarkdownStyle &newSty
523
523
  changed = YES;
524
524
  }
525
525
 
526
+ if (newStyle.link.borderRadius != oldStyle.link.borderRadius) {
527
+ [config setLinkBorderRadius:newStyle.link.borderRadius];
528
+ changed = YES;
529
+ }
530
+
531
+ if (newStyle.link.paddingHorizontal != oldStyle.link.paddingHorizontal) {
532
+ [config setLinkPaddingHorizontal:newStyle.link.paddingHorizontal];
533
+ changed = YES;
534
+ }
535
+
536
+ if (newStyle.link.paddingVertical != oldStyle.link.paddingVertical) {
537
+ [config setLinkPaddingVertical:newStyle.link.paddingVertical];
538
+ changed = YES;
539
+ }
540
+
541
+ if (newStyle.link.borderColor != oldStyle.link.borderColor) {
542
+ RCTUIColor *color = RCTUIColorFromSharedColor(newStyle.link.borderColor);
543
+ [config setLinkBorderColor:CGColorGetAlpha(color.CGColor) > 0 ? color : nil];
544
+ changed = YES;
545
+ }
546
+
547
+ if (newStyle.link.borderWidth != oldStyle.link.borderWidth) {
548
+ [config setLinkBorderWidth:newStyle.link.borderWidth];
549
+ changed = YES;
550
+ }
551
+
526
552
  {
527
553
  BOOL linkVariantsChanged = newStyle.linkVariants.size() != oldStyle.linkVariants.size();
528
554
  if (!linkVariantsChanged) {
@@ -530,7 +556,11 @@ BOOL applyMarkdownStyleToConfig(StyleConfig *config, const MarkdownStyle &newSty
530
556
  const auto &newVariant = newStyle.linkVariants[i];
531
557
  const auto &oldVariant = oldStyle.linkVariants[i];
532
558
  if (newVariant.pattern != oldVariant.pattern || newVariant.color != oldVariant.color ||
533
- newVariant.underline != oldVariant.underline || newVariant.backgroundColor != oldVariant.backgroundColor) {
559
+ newVariant.underline != oldVariant.underline || newVariant.backgroundColor != oldVariant.backgroundColor ||
560
+ newVariant.borderRadius != oldVariant.borderRadius ||
561
+ newVariant.paddingHorizontal != oldVariant.paddingHorizontal ||
562
+ newVariant.paddingVertical != oldVariant.paddingVertical ||
563
+ newVariant.borderColor != oldVariant.borderColor || newVariant.borderWidth != oldVariant.borderWidth) {
534
564
  linkVariantsChanged = YES;
535
565
  break;
536
566
  }
@@ -545,6 +575,12 @@ BOOL applyMarkdownStyleToConfig(StyleConfig *config, const MarkdownStyle &newSty
545
575
  variant.underline = entry.underline;
546
576
  RCTUIColor *backgroundColor = RCTUIColorFromSharedColor(entry.backgroundColor);
547
577
  variant.backgroundColor = CGColorGetAlpha(backgroundColor.CGColor) > 0 ? backgroundColor : nil;
578
+ variant.borderRadius = entry.borderRadius;
579
+ variant.paddingHorizontal = entry.paddingHorizontal;
580
+ variant.paddingVertical = entry.paddingVertical;
581
+ RCTUIColor *borderColor = RCTUIColorFromSharedColor(entry.borderColor);
582
+ variant.borderColor = CGColorGetAlpha(borderColor.CGColor) > 0 ? borderColor : nil;
583
+ variant.borderWidth = entry.borderWidth;
548
584
  [variants addObject:variant];
549
585
  }
550
586
  [config setLinkVariants:variants];
@@ -1131,6 +1167,11 @@ BOOL applyMarkdownStyleToConfig(StyleConfig *config, const MarkdownStyle &newSty
1131
1167
  changed = YES;
1132
1168
  }
1133
1169
 
1170
+ if (newStyle.math.glyphOverflow != oldStyle.math.glyphOverflow) {
1171
+ [config setMathGlyphOverflow:newStyle.math.glyphOverflow];
1172
+ changed = YES;
1173
+ }
1174
+
1134
1175
  if (newStyle.math.marginTop != oldStyle.math.marginTop) {
1135
1176
  [config setMathMarginTop:newStyle.math.marginTop];
1136
1177
  changed = YES;
@@ -1179,6 +1220,11 @@ BOOL applyMarkdownStyleToConfig(StyleConfig *config, const MarkdownStyle &newSty
1179
1220
  changed = YES;
1180
1221
  }
1181
1222
 
1223
+ if (newStyle.inlineMath.glyphOverflow != oldStyle.inlineMath.glyphOverflow) {
1224
+ [config setInlineMathGlyphOverflow:newStyle.inlineMath.glyphOverflow];
1225
+ changed = YES;
1226
+ }
1227
+
1182
1228
  // ── Spoiler ─────────────────────────────────────────────────────────────
1183
1229
 
1184
1230
  if (newStyle.spoiler.color != oldStyle.spoiler.color) {
@@ -2,6 +2,7 @@
2
2
  #import "BlockquoteBorder.h"
3
3
  #import "CodeBackground.h"
4
4
  #import "CodeBlockBackground.h"
5
+ #import "ENRMLinkChip.h"
5
6
  #import "ListMarkerDrawer.h"
6
7
  #import "RuntimeKeys.h"
7
8
  #import "StyleConfig.h"
@@ -31,6 +32,8 @@
31
32
  // We fetch the objects into local variables FIRST to ensure they don't
32
33
  // disappear mid-method if setConfig: is called on another thread.
33
34
 
35
+ ENRMDrawLinkChipsForGlyphRange(self, textContainer, glyphsToShow, origin);
36
+
34
37
  CodeBackground *codeBg = [self getCodeBackgroundWithConfig:config];
35
38
  [codeBg drawBackgroundsForGlyphRange:glyphsToShow layoutManager:self textContainer:textContainer atPoint:origin];
36
39
 
@@ -1,4 +1,5 @@
1
1
  #pragma once
2
+ #import "ENRMBlockMenu.h"
2
3
  #import "ENRMUIKit.h"
3
4
  #import "StyleConfig.h"
4
5
 
@@ -33,12 +34,25 @@ typedef void (^ENRMCodeBlockCopyBlock)(NSString *code, NSString *language);
33
34
 
34
35
  /// Suppresses the long-press copy menu on this block. Mirrors the
35
36
  /// `contextMenuHidden` prop; the header copy button is a visible control, not a
36
- /// context menu, and stays.
37
+ /// context menu, and stays (hide it with `menuOptions.copyButtonHidden`).
37
38
  @property (nonatomic, assign) BOOL contextMenuHidden;
38
39
 
40
+ /// Copy / Copy as Markdown visibility, Copy format and the header copy button
41
+ /// from `selectionMenuConfig`. `copyHidden` also drops the VoiceOver copy action;
42
+ /// the header button and VoiceOver action always copy the raw code.
43
+ @property (nonatomic, assign) ENRMBlockMenuOptions menuOptions;
44
+
45
+ /// Set by the host; receives the Copy item's content when
46
+ /// `menuOptions.interceptCopy` is set.
47
+ @property (nonatomic, copy, nullable) ENRMBlockCopyRequestBlock onCopyRequest;
48
+ /// Called instead of the menu on long press while `contextMenuHidden` is on, so
49
+ /// the owner can report the block (`onSelectionChange`) and show its own UI.
50
+ @property (nonatomic, copy, nullable) ENRMBlockCopyRequestBlock onMenuSuppressed;
51
+
39
52
  // Fired when the code is copied (header button, context-menu Copy, or the
40
53
  // VoiceOver copy action); set by the host to bridge up to the JS onCopyPress
41
- // event. Not fired for "Copy as Markdown".
54
+ // event. Not fired for "Copy as Markdown", nor for the context-menu Copy when
55
+ // `menuOptions.copyWritesMarkdown` is set.
42
56
  @property (nonatomic, copy, nullable) ENRMCodeBlockCopyBlock onCopyPress;
43
57
 
44
58
  @end
@@ -175,6 +175,13 @@ static BOOL ENRMColorIsDark(RCTUIColor *color)
175
175
  _copyButton.accessibilityLabel = _copyLabel;
176
176
  }
177
177
 
178
+ - (void)setMenuOptions:(ENRMBlockMenuOptions)menuOptions
179
+ {
180
+ _menuOptions = menuOptions;
181
+ // Hiding the button leaves the header (and its height) untouched.
182
+ _copyButton.hidden = menuOptions.copyButtonHidden;
183
+ }
184
+
178
185
  - (void)setPending:(BOOL)pending
179
186
  {
180
187
  if (_pending == pending) {
@@ -488,26 +495,22 @@ static BOOL ENRMColorIsDark(RCTUIColor *color)
488
495
  {
489
496
  // Returning nil is the UIKit-sanctioned way to suppress a context menu.
490
497
  if (_pending || self.contextMenuHidden) {
498
+ if (!_pending && self.onMenuSuppressed) {
499
+ self.onMenuSuppressed([self fencedMarkdown], _cachedCode ?: @"");
500
+ }
491
501
  return nil;
492
502
  }
493
- return [UIContextMenuConfiguration
494
- configurationWithIdentifier:nil
495
- previewProvider:nil
496
- actionProvider:^UIMenu *(NSArray<UIMenuElement *> *suggestedActions) {
497
- UIAction *copyCode =
498
- [UIAction actionWithTitle:self.copyLabel
499
- image:[RCTUIImage systemImageNamed:@"doc.on.doc"]
500
- identifier:nil
501
- handler:^(__kindof UIAction *action) { [self copyCodeToPasteboard]; }];
502
-
503
- UIAction *copyMarkdown =
504
- [UIAction actionWithTitle:self.copyAsMarkdownLabel
505
- image:[RCTUIImage systemImageNamed:@"doc.text"]
506
- identifier:nil
507
- handler:^(__kindof UIAction *action) { [self copyMarkdownToPasteboard]; }];
508
-
509
- return [UIMenu menuWithTitle:@"" children:@[ copyCode, copyMarkdown ]];
510
- }];
503
+ __weak __typeof(self) weakSelf = self;
504
+ return ENRMBlockContextMenuConfiguration(
505
+ self.menuOptions, self.copyLabel, self.copyAsMarkdownLabel, ^{ [weakSelf copyCodeToPasteboard]; },
506
+ ^{ [weakSelf copyMarkdownToPasteboard]; }, ^{ [weakSelf requestCopy]; });
507
+ }
508
+
509
+ - (void)requestCopy
510
+ {
511
+ if (self.onCopyRequest) {
512
+ self.onCopyRequest([self fencedMarkdown], _cachedCode ?: @"");
513
+ }
511
514
  }
512
515
  #endif
513
516
 
@@ -546,7 +549,7 @@ static BOOL ENRMColorIsDark(RCTUIColor *color)
546
549
  // button subview from VoiceOver; expose the copy action explicitly instead.
547
550
  - (NSArray<UIAccessibilityCustomAction *> *)accessibilityCustomActions
548
551
  {
549
- if (_pending || self.copyLabel.length == 0) {
552
+ if (_pending || self.menuOptions.copyHidden || self.copyLabel.length == 0) {
550
553
  return @[];
551
554
  }
552
555
  UIAccessibilityCustomAction *copyAction =
@@ -1,4 +1,5 @@
1
1
  #pragma once
2
+ #import "ENRMBlockMenu.h"
2
3
  #import "ENRMUIKit.h"
3
4
  #import "StyleConfig.h"
4
5
 
@@ -35,6 +36,16 @@ NS_ASSUME_NONNULL_BEGIN
35
36
  /// `contextMenuHidden` prop; selection and link handling are unaffected.
36
37
  @property (nonatomic, assign) BOOL contextMenuHidden;
37
38
 
39
+ /// Copy / Copy as Markdown visibility and Copy format from `selectionMenuConfig`.
40
+ @property (nonatomic, assign) ENRMBlockMenuOptions menuOptions;
41
+
42
+ /// Set by the host; receives the Copy item's content when
43
+ /// `menuOptions.interceptCopy` is set.
44
+ @property (nonatomic, copy, nullable) ENRMBlockCopyRequestBlock onCopyRequest;
45
+ /// Called instead of the menu on long press while `contextMenuHidden` is on, so
46
+ /// the owner can report the block (`onSelectionChange`) and show its own UI.
47
+ @property (nonatomic, copy, nullable) ENRMBlockCopyRequestBlock onMenuSuppressed;
48
+
38
49
  @end
39
50
 
40
51
  NS_ASSUME_NONNULL_END
@@ -104,7 +104,8 @@
104
104
  displayMode:YES
105
105
  fontSize:config.mathFontSize
106
106
  color:config.mathColor
107
- bold:ENRMMathFontWeightIsBold(config.mathFontWeight)];
107
+ bold:ENRMMathFontWeightIsBold(config.mathFontWeight)
108
+ glyphOverflow:config.mathGlyphOverflow];
108
109
  _mathView.renderResult = result;
109
110
  _mathView.fallbackSource = result ? nil : ENRMMathFallbackString(latex, @"$$", config.mathFontSize, config.mathColor);
110
111
 
@@ -128,27 +129,22 @@
128
129
  {
129
130
  // Returning nil is the UIKit-sanctioned way to suppress a context menu.
130
131
  if (self.contextMenuHidden) {
132
+ if (self.onMenuSuppressed) {
133
+ self.onMenuSuppressed([NSString stringWithFormat:@"$$\n%@\n$$", _cachedLatex ?: @""], _cachedLatex ?: @"");
134
+ }
131
135
  return nil;
132
136
  }
137
+ __weak __typeof(self) weakSelf = self;
138
+ return ENRMBlockContextMenuConfiguration(
139
+ self.menuOptions, self.copyLabel, self.copyAsMarkdownLabel, ^{ [weakSelf copyLatexToPasteboard]; },
140
+ ^{ [weakSelf copyMarkdownToPasteboard]; }, ^{ [weakSelf requestCopy]; });
141
+ }
133
142
 
134
- return [UIContextMenuConfiguration
135
- configurationWithIdentifier:nil
136
- previewProvider:nil
137
- actionProvider:^UIMenu *(NSArray<UIMenuElement *> *suggestedActions) {
138
- UIAction *copyPlainText =
139
- [UIAction actionWithTitle:self.copyLabel
140
- image:[RCTUIImage systemImageNamed:@"doc.on.doc"]
141
- identifier:nil
142
- handler:^(__kindof UIAction *action) { [self copyLatexToPasteboard]; }];
143
-
144
- UIAction *copyMarkdown =
145
- [UIAction actionWithTitle:self.copyAsMarkdownLabel
146
- image:[RCTUIImage systemImageNamed:@"doc.text"]
147
- identifier:nil
148
- handler:^(__kindof UIAction *action) { [self copyMarkdownToPasteboard]; }];
149
-
150
- return [UIMenu menuWithTitle:@"" children:@[ copyPlainText, copyMarkdown ]];
151
- }];
143
+ - (void)requestCopy
144
+ {
145
+ if (self.onCopyRequest) {
146
+ self.onCopyRequest([NSString stringWithFormat:@"$$\n%@\n$$", _cachedLatex ?: @""], _cachedLatex ?: @"");
147
+ }
152
148
  }
153
149
  #endif
154
150
 
@@ -204,7 +200,8 @@
204
200
  displayMode:YES
205
201
  fontSize:config.mathFontSize
206
202
  color:config.mathColor
207
- bold:ENRMMathFontWeightIsBold(config.mathFontWeight)];
203
+ bold:ENRMMathFontWeightIsBold(config.mathFontWeight)
204
+ glyphOverflow:config.mathGlyphOverflow];
208
205
  if (result) {
209
206
  return ceil(result.totalHeight) + padding * 2;
210
207
  }
@@ -1,4 +1,5 @@
1
1
  #pragma once
2
+ #import "ENRMBlockMenu.h"
2
3
  #import "ENRMUIKit.h"
3
4
  #import "ParagraphStyleUtils.h"
4
5
  #import "StyleConfig.h"
@@ -56,7 +57,20 @@ typedef void (^TableLinkPressBlock)(NSString *url);
56
57
  /// `contextMenuHidden` prop; selection and link handling are unaffected.
57
58
  @property (nonatomic, assign) BOOL contextMenuHidden;
58
59
 
60
+ /// Copy / Copy as Markdown visibility and Copy format from `selectionMenuConfig`.
61
+ @property (nonatomic, assign) ENRMBlockMenuOptions menuOptions;
62
+
63
+ /// Set by the host; receives the Copy item's content when
64
+ /// `menuOptions.interceptCopy` is set.
65
+ @property (nonatomic, copy, nullable) ENRMBlockCopyRequestBlock onCopyRequest;
66
+ /// Called instead of the menu on long press while `contextMenuHidden` is on, so
67
+ /// the owner can report the block (`onSelectionChange`) and show its own UI.
68
+ @property (nonatomic, copy, nullable) ENRMBlockCopyRequestBlock onMenuSuppressed;
69
+
59
70
  @property (nonatomic, readonly) NSUInteger rowCount;
71
+ /// Number of links across all cells. Tables are not reported by `onLinkLayout`,
72
+ /// but their links still count so later link indices stay in document order.
73
+ @property (nonatomic, readonly) NSInteger linkCount;
60
74
 
61
75
  - (void)animateNewRowsFromPreviousCount:(NSUInteger)previousRowCount duration:(NSTimeInterval)duration;
62
76
 
@@ -1,6 +1,7 @@
1
1
  #import "TableContainerView.h"
2
2
  #import "AttributedRenderer.h"
3
3
  #import "ENRMAccessibilityLabels.h"
4
+ #import "ENRMLinkChip.h"
4
5
  #import "HTMLGenerator.h"
5
6
  #import "LinkTapUtils.h"
6
7
  #import "MarkdownASTNode.h"
@@ -90,6 +91,8 @@ static NSMutableAttributedString *ENRMTableRenderCellNode(MarkdownASTNode *cellN
90
91
  NSMutableAttributedString *attributedText = [renderer renderRoot:temporaryRoot context:context];
91
92
 
92
93
  [context applyLinkAttributesToString:attributedText];
94
+ // Cells draw with -drawWithRect:, which bypasses the layout manager that paints link chips.
95
+ ENRMApplyLinkChipBackgroundsAsTextBackground(attributedText);
93
96
 
94
97
  ENRMApplyWritingDirectionMode(attributedText, writingDirectionMode, resolvedLayoutDirection);
95
98
 
@@ -302,6 +305,24 @@ static void ENRMTableComputeLayout(NSArray<NSArray<TableCellData *> *> *rows, NS
302
305
  return _rows.count;
303
306
  }
304
307
 
308
+ - (NSInteger)linkCount
309
+ {
310
+ __block NSInteger count = 0;
311
+ for (NSArray<TableCellData *> *row in _rows) {
312
+ for (TableCellData *cell in row) {
313
+ // LinkRenderer attaches one ENRMLinkChip instance per link.
314
+ [cell.attributedText enumerateAttribute:ENRMLinkChipAttributeName
315
+ inRange:NSMakeRange(0, cell.attributedText.length)
316
+ options:0
317
+ usingBlock:^(id chip, NSRange range, BOOL *stop) {
318
+ if (chip)
319
+ count++;
320
+ }];
321
+ }
322
+ }
323
+ return count;
324
+ }
325
+
305
326
  #if !TARGET_OS_OSX
306
327
  - (void)animateNewRowsFromPreviousCount:(NSUInteger)previousRowCount duration:(NSTimeInterval)duration
307
328
  {
@@ -465,27 +486,22 @@ static void ENRMTableComputeLayout(NSArray<NSArray<TableCellData *> *> *rows, NS
465
486
  {
466
487
  // Returning nil is the UIKit-sanctioned way to suppress a context menu.
467
488
  if (self.contextMenuHidden) {
489
+ if (self.onMenuSuppressed) {
490
+ self.onMenuSuppressed(_cachedMarkdown ?: @"", [self buildPlainTextFromRows] ?: @"");
491
+ }
468
492
  return nil;
469
493
  }
494
+ __weak __typeof(self) weakSelf = self;
495
+ return ENRMBlockContextMenuConfiguration(
496
+ self.menuOptions, self.copyLabel, self.copyAsMarkdownLabel, ^{ [weakSelf copyTableToPasteboard]; },
497
+ ^{ [weakSelf copyMarkdownToPasteboard]; }, ^{ [weakSelf requestCopy]; });
498
+ }
470
499
 
471
- return [UIContextMenuConfiguration
472
- configurationWithIdentifier:nil
473
- previewProvider:nil
474
- actionProvider:^UIMenu *(NSArray<UIMenuElement *> *suggestedActions) {
475
- UIAction *copyMarkdown =
476
- [UIAction actionWithTitle:self.copyAsMarkdownLabel
477
- image:[RCTUIImage systemImageNamed:@"doc.text"]
478
- identifier:nil
479
- handler:^(__kindof UIAction *action) { [self copyMarkdownToPasteboard]; }];
480
-
481
- UIAction *copyPlainText =
482
- [UIAction actionWithTitle:self.copyLabel
483
- image:[RCTUIImage systemImageNamed:@"doc.on.doc"]
484
- identifier:nil
485
- handler:^(__kindof UIAction *action) { [self copyTableToPasteboard]; }];
486
-
487
- return [UIMenu menuWithTitle:@"" children:@[ copyPlainText, copyMarkdown ]];
488
- }];
500
+ - (void)requestCopy
501
+ {
502
+ if (self.onCopyRequest) {
503
+ self.onCopyRequest(_cachedMarkdown ?: @"", [self buildPlainTextFromRows] ?: @"");
504
+ }
489
505
  }
490
506
  #endif // !TARGET_OS_OSX
491
507
 
@@ -1,9 +1,12 @@
1
1
  import {
2
2
  codegenNativeComponent,
3
+ codegenNativeCommands,
3
4
  type ViewProps,
4
5
  type CodegenTypes,
5
6
  type ColorValue,
7
+ type HostComponent,
6
8
  } from 'react-native';
9
+ import type React from 'react';
7
10
 
8
11
  // All block styles extend this interface
9
12
  interface BaseBlockStyleInternal {
@@ -75,6 +78,11 @@ interface LinkStyleInternal {
75
78
  color: ColorValue;
76
79
  underline: boolean;
77
80
  backgroundColor: ColorValue;
81
+ borderRadius: CodegenTypes.Float;
82
+ paddingHorizontal: CodegenTypes.Float;
83
+ paddingVertical: CodegenTypes.Float;
84
+ borderColor: ColorValue;
85
+ borderWidth: CodegenTypes.Float;
78
86
  }
79
87
 
80
88
  // Mirrors EnrichedMarkdownTextNativeComponent.ts — kept in sync manually (codegen spec files must be self-contained).
@@ -83,6 +91,11 @@ interface LinkVariantEntryInternal {
83
91
  color: ColorValue;
84
92
  underline: boolean;
85
93
  backgroundColor: ColorValue;
94
+ borderRadius: CodegenTypes.Float;
95
+ paddingHorizontal: CodegenTypes.Float;
96
+ paddingVertical: CodegenTypes.Float;
97
+ borderColor: ColorValue;
98
+ borderWidth: CodegenTypes.Float;
86
99
  }
87
100
 
88
101
  interface StrongStyleInternal {
@@ -168,12 +181,14 @@ interface MathStyleInternal {
168
181
  marginBottom: CodegenTypes.Float;
169
182
  textAlign: string;
170
183
  fontWeight: string;
184
+ glyphOverflow: CodegenTypes.Float;
171
185
  }
172
186
 
173
187
  interface InlineMathStyleInternal {
174
188
  color: ColorValue;
175
189
  paddingVertical: CodegenTypes.Float;
176
190
  fontWeight: string;
191
+ glyphOverflow: CodegenTypes.Float;
177
192
  }
178
193
 
179
194
  interface SpoilerParticlesStyleInternal {
@@ -245,6 +260,59 @@ export interface LinkLongPressEvent {
245
260
  url: string;
246
261
  }
247
262
 
263
+ /**
264
+ * `fragments` holds one entry per laid-out line of a link (a wrapping link
265
+ * yields several). Rects are in dp, relative to the native view's origin, and
266
+ * cover the drawn chip (glyphs + padding). Flattened rather than nested per
267
+ * link, and written inline, because codegen only accepts inline object
268
+ * literals as event array elements.
269
+ */
270
+ export interface LinkLayoutEvent {
271
+ fragments: {
272
+ linkIndex: CodegenTypes.Int32;
273
+ url: string;
274
+ text: string;
275
+ x: CodegenTypes.Double;
276
+ y: CodegenTypes.Double;
277
+ width: CodegenTypes.Double;
278
+ height: CodegenTypes.Double;
279
+ }[];
280
+ }
281
+
282
+ /**
283
+ * Current text selection, or a long-pressed table / math / code block while
284
+ * `contextMenuHidden` is on. `active: false` means the selection was cleared
285
+ * (every other field is then empty / zero). Rects are in dp / pt, relative to
286
+ * the native view's origin; rect objects are written inline because codegen
287
+ * only accepts inline object literals in event payloads.
288
+ */
289
+ export interface SelectionChangeEvent {
290
+ active: boolean;
291
+ source: string;
292
+ text: string;
293
+ markdown: string;
294
+ whole: boolean;
295
+ rect: {
296
+ x: CodegenTypes.Double;
297
+ y: CodegenTypes.Double;
298
+ width: CodegenTypes.Double;
299
+ height: CodegenTypes.Double;
300
+ };
301
+ startRect: {
302
+ x: CodegenTypes.Double;
303
+ y: CodegenTypes.Double;
304
+ width: CodegenTypes.Double;
305
+ height: CodegenTypes.Double;
306
+ };
307
+ endRect: {
308
+ x: CodegenTypes.Double;
309
+ y: CodegenTypes.Double;
310
+ width: CodegenTypes.Double;
311
+ height: CodegenTypes.Double;
312
+ };
313
+ adjusting: boolean;
314
+ }
315
+
248
316
  export interface TaskListItemPressEvent {
249
317
  index: CodegenTypes.Int32;
250
318
  checked: boolean;
@@ -256,6 +324,14 @@ export interface CopyPressEvent {
256
324
  language: string;
257
325
  }
258
326
 
327
+ export interface CopyRequestEvent {
328
+ // 'selection' | 'math' | 'table' | 'code'
329
+ source: string;
330
+ markdown: string;
331
+ text: string;
332
+ whole: boolean;
333
+ }
334
+
259
335
  export interface ContextMenuItemConfig {
260
336
  text: string;
261
337
  icon?: string;
@@ -276,6 +352,16 @@ export interface SelectionMenuConfig {
276
352
  // Precomputed plural templates indexed by image count (0..100). Empty when no
277
353
  // pluralLabels are set; native then uses copyImageUrlLabel/copyImageUrlsLabel.
278
354
  copyImageUrlPluralTemplates: ReadonlyArray<string>;
355
+ // false removes the Copy item from the text and block menus.
356
+ copyEnabled: boolean;
357
+ // true: Copy writes Markdown (plain text) instead of rich text / raw block content.
358
+ copyFormatMarkdown: boolean;
359
+ // true: the text selection menu drops every system-provided item.
360
+ systemItemsNone: boolean;
361
+ // false hides the copy button in the code block header.
362
+ codeBlockCopyButton: boolean;
363
+ // true: the Copy item emits onCopyRequest instead of writing the clipboard.
364
+ interceptCopy: boolean;
279
365
  }
280
366
 
281
367
  export interface OnContextMenuItemPressEvent {
@@ -382,6 +468,30 @@ export interface NativeProps extends ViewProps {
382
468
  * - Android: Handles long press gestures on links.
383
469
  */
384
470
  onLinkLongPress?: CodegenTypes.BubblingEventHandler<LinkLongPressEvent>;
471
+ /**
472
+ * When true, the view reports the laid-out rects of every link through
473
+ * `onLinkLayout` (after each render and whenever its size changes).
474
+ * Set by the JS wrapper only when `renderLinkDecoration` is provided.
475
+ * @default false
476
+ */
477
+ emitLinkLayout?: CodegenTypes.WithDefault<boolean, false>;
478
+ /**
479
+ * Fired with the rects of every link fragment while `emitLinkLayout` is on.
480
+ * Deduplicated: not re-fired when the payload is unchanged.
481
+ */
482
+ onLinkLayout?: CodegenTypes.DirectEventHandler<LinkLayoutEvent>;
483
+ /**
484
+ * When true, the view reports selection changes (and, with
485
+ * `contextMenuHidden`, block long-presses) through `onSelectionChange`.
486
+ * Set by the JS wrapper only when `onSelectionChange` is provided.
487
+ * @default false
488
+ */
489
+ emitSelectionChange?: CodegenTypes.WithDefault<boolean, false>;
490
+ /**
491
+ * Fired while `emitSelectionChange` is on: coalesced to one event per frame,
492
+ * deduplicated, and `active: false` once the selection is gone.
493
+ */
494
+ onSelectionChange?: CodegenTypes.DirectEventHandler<SelectionChangeEvent>;
385
495
  /**
386
496
  * Callback fired when a task list checkbox is tapped.
387
497
  * Receives the 0-based task index, current checked state, and the item's plain text.
@@ -402,6 +512,12 @@ export interface NativeProps extends ViewProps {
402
512
  * action. Receives the copied code and its language.
403
513
  */
404
514
  onCopyPress?: CodegenTypes.BubblingEventHandler<CopyPressEvent>;
515
+ /**
516
+ * Fired instead of writing the clipboard when the Copy item of the text
517
+ * selection menu or a table / math / code block menu is pressed while
518
+ * `selectionMenuConfig.interceptCopy` is set.
519
+ */
520
+ onCopyRequest?: CodegenTypes.BubblingEventHandler<CopyRequestEvent>;
405
521
  /**
406
522
  * Controls whether the system link preview is shown on long press (iOS only).
407
523
  *
@@ -415,6 +531,16 @@ export interface NativeProps extends ViewProps {
415
531
  * @default true
416
532
  */
417
533
  enableLinkPreview?: CodegenTypes.WithDefault<boolean, true>;
534
+ /**
535
+ * When `true`, long-pressing a link selects its label and shows the
536
+ * selection edit menu instead of the system link preview (iOS only).
537
+ * Takes precedence over `enableLinkPreview` and `onLinkLongPress`.
538
+ *
539
+ * Android: No-op.
540
+ *
541
+ * @default false
542
+ */
543
+ linkLongPressSelects?: CodegenTypes.WithDefault<boolean, false>;
418
544
  /**
419
545
  * - iOS: Controls text selection and link previews on long press.
420
546
  * - Android: Controls text selection.
@@ -529,6 +655,17 @@ export interface NativeProps extends ViewProps {
529
655
  writingDirection?: CodegenTypes.WithDefault<string, 'first-strong'>;
530
656
  }
531
657
 
658
+ type ComponentType = HostComponent<NativeProps>;
659
+
660
+ interface NativeCommands {
661
+ /** Clears the text selection (and any active block) and fires `onSelectionChange` with `active: false`. */
662
+ clearSelection: (viewRef: React.ElementRef<ComponentType>) => void;
663
+ }
664
+
665
+ export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
666
+ supportedCommands: ['clearSelection'],
667
+ });
668
+
532
669
  export default codegenNativeComponent<NativeProps>('EnrichedMarkdown', {
533
670
  interfaceOnly: true,
534
- });
671
+ }) as HostComponent<NativeProps>;