react-native-enriched-markdown-scaffold 1.0.2-scaffold.6 → 1.0.2-scaffold.8

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 (64) hide show
  1. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +2 -0
  2. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +2 -0
  3. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +24 -0
  4. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +12 -0
  5. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +12 -0
  6. package/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt +8 -4
  7. package/android/src/main/java/com/swmansion/enriched/markdown/events/SelectionChangeEvent.kt +1 -0
  8. package/android/src/main/java/com/swmansion/enriched/markdown/styles/InlineMathStyle.kt +16 -1
  9. package/android/src/main/java/com/swmansion/enriched/markdown/styles/MathStyle.kt +3 -0
  10. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/HeadlessActionMode.kt +76 -0
  11. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionChangeReporter.kt +2 -0
  12. package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +1 -1
  13. package/android/src/math/java/com/swmansion/enriched/markdown/renderer/MathInlineRenderer.kt +1 -0
  14. package/android/src/math/java/com/swmansion/enriched/markdown/spans/MathInlineSpan.kt +13 -6
  15. package/android/src/math/java/com/swmansion/enriched/markdown/spans/MathMeasureHelper.kt +2 -1
  16. package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +11 -4
  17. package/docs/API_REFERENCE.md +4 -2
  18. package/ios/EnrichedMarkdown.mm +23 -0
  19. package/ios/EnrichedMarkdownText.mm +21 -0
  20. package/ios/attachments/ENRMMathInlineAttachment+macOS.m +2 -1
  21. package/ios/attachments/ENRMMathInlineAttachment.h +3 -0
  22. package/ios/attachments/ENRMMathInlineAttachment.m +2 -1
  23. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.cpp +2 -0
  24. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.h +2 -0
  25. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +24 -0
  26. package/ios/internals/MeasurementCache.h +3 -2
  27. package/ios/math/ENRMRaTeXBridge.swift +23 -5
  28. package/ios/renderer/ENRMMathInlineRenderer.m +1 -0
  29. package/ios/styles/StyleConfig.h +4 -0
  30. package/ios/styles/StyleConfig.mm +24 -0
  31. package/ios/utils/ENRMLinkChip.m +2 -1
  32. package/ios/utils/ENRMSelectionChange.h +24 -0
  33. package/ios/utils/ENRMSelectionChange.m +78 -0
  34. package/ios/utils/StylePropsUtils.h +10 -0
  35. package/ios/views/ENRMMathContainerView.m +4 -2
  36. package/lib/module/EnrichedMarkdownNativeComponent.ts +3 -0
  37. package/lib/module/EnrichedMarkdownTextNativeComponent.ts +3 -0
  38. package/lib/module/normalizeMarkdownStyle.js +4 -2
  39. package/lib/module/normalizeMarkdownStyle.js.map +1 -1
  40. package/lib/module/normalizeMarkdownStyle.web.js +4 -2
  41. package/lib/module/normalizeMarkdownStyle.web.js.map +1 -1
  42. package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +3 -0
  43. package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
  44. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +3 -0
  45. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
  46. package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -1
  47. package/lib/typescript/src/normalizeMarkdownStyle.web.d.ts.map +1 -1
  48. package/lib/typescript/src/types/MarkdownStyle.d.ts +22 -0
  49. package/lib/typescript/src/types/MarkdownStyle.d.ts.map +1 -1
  50. package/lib/typescript/src/types/MarkdownStyleInternal.d.ts +2 -0
  51. package/lib/typescript/src/types/MarkdownStyleInternal.d.ts.map +1 -1
  52. package/lib/typescript/src/types/MarkdownTextProps.d.ts +8 -2
  53. package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
  54. package/lib/typescript/src/types/events.d.ts +8 -0
  55. package/lib/typescript/src/types/events.d.ts.map +1 -1
  56. package/package.json +1 -1
  57. package/src/EnrichedMarkdownNativeComponent.ts +3 -0
  58. package/src/EnrichedMarkdownTextNativeComponent.ts +3 -0
  59. package/src/normalizeMarkdownStyle.ts +2 -0
  60. package/src/normalizeMarkdownStyle.web.ts +2 -0
  61. package/src/types/MarkdownStyle.ts +22 -0
  62. package/src/types/MarkdownStyleInternal.ts +2 -0
  63. package/src/types/MarkdownTextProps.ts +8 -2
  64. package/src/types/events.ts +8 -0
@@ -119,6 +119,9 @@ typedef NS_OPTIONS(NSUInteger, ENRMDirtyFlags) {
119
119
  NSArray<NSDictionary *> *_lastLinkLayout;
120
120
 
121
121
  ENRMSelectionChangeReporter *_selectionReporter;
122
+ #if !TARGET_OS_OSX
123
+ ENRMSelectionGestureTracker *_selectionGestures;
124
+ #endif
122
125
 
123
126
  NSLineBreakStrategy _lineBreakStrategy;
124
127
 
@@ -300,6 +303,19 @@ typedef NS_OPTIONS(NSUInteger, ENRMDirtyFlags) {
300
303
  emitter->onSelectionChange(
301
304
  ENRMSelectionChangeEventFromPayload<EnrichedMarkdownTextEventEmitter::OnSelectionChange>(payload));
302
305
  }];
306
+ #if !TARGET_OS_OSX
307
+ // A handle drag starting or ending is reported right away, even when the
308
+ // selection itself is unchanged.
309
+ _selectionGestures = [[ENRMSelectionGestureTracker alloc] initWithChange:^{
310
+ EnrichedMarkdownText *strongSelf = weakSelf;
311
+ if (!strongSelf)
312
+ return;
313
+ [strongSelf->_selectionGestures trackTextView:strongSelf->_textView];
314
+ [strongSelf->_selectionReporter forgetLastReported];
315
+ [strongSelf->_selectionReporter flushNow];
316
+ }];
317
+ [_selectionGestures trackTextView:_textView];
318
+ #endif
303
319
  }
304
320
 
305
321
  /// The current selection as an `onSelectionChange` payload. Markdown and
@@ -319,6 +335,7 @@ typedef NS_OPTIONS(NSUInteger, ENRMDirtyFlags) {
319
335
  payload[ENRMSelectionSourceKey] = @"selection";
320
336
  payload[ENRMSelectionMarkdownKey] = markdown.length > 0 ? markdown : text;
321
337
  payload[ENRMSelectionWholeKey] = @([selection[ENRMSelectionFullKey] boolValue] && _cachedMarkdown.length > 0);
338
+ payload[ENRMSelectionAdjustingKey] = @(_selectionGestures.adjusting);
322
339
  return payload;
323
340
  #else
324
341
  return ENRMInactiveSelectionPayload();
@@ -786,6 +803,9 @@ typedef NS_OPTIONS(NSUInteger, ENRMDirtyFlags) {
786
803
  _emitLinkLayout = NO;
787
804
  _lastLinkLayout = nil;
788
805
  _selectionReporter.enabled = NO;
806
+ #if !TARGET_OS_OSX
807
+ [_selectionGestures reset];
808
+ #endif
789
809
  if (_textView != nil) {
790
810
  ENRMSetAttributedText(_textView, [[NSAttributedString alloc] initWithString:@""]);
791
811
  _textView.hidden = YES;
@@ -968,6 +988,7 @@ Class<RCTComponentViewProtocol> EnrichedMarkdownTextCls(void)
968
988
 
969
989
  - (void)textViewDidChangeSelection:(UITextView *)textView
970
990
  {
991
+ [_selectionGestures trackTextView:textView];
971
992
  ENRMDismissSelectionEditMenuIfMoved(textView);
972
993
  [_selectionReporter selectionChanged:textView.selectedRange.length > 0];
973
994
  }
@@ -29,7 +29,8 @@
29
29
  displayMode:NO
30
30
  fontSize:self.fontSize
31
31
  color:color
32
- bold:self.bold];
32
+ bold:self.bold
33
+ glyphOverflow:self.glyphOverflow];
33
34
  if (!result)
34
35
  return;
35
36
 
@@ -14,6 +14,9 @@ NS_ASSUME_NONNULL_BEGIN
14
14
  /// Typeset the glyphs at bold weight (synthetic — see ENRMRaTeXBridge).
15
15
  @property (nonatomic, assign) BOOL bold;
16
16
 
17
+ /// Ink headroom above and below the formula box (`inlineMath.glyphOverflow`, points).
18
+ @property (nonatomic, assign) CGFloat glyphOverflow;
19
+
17
20
  /// YES if block math (`$$...$$`), NO for inline (`$...$`).
18
21
  @property (nonatomic, assign) BOOL displayMode;
19
22
 
@@ -42,7 +42,8 @@
42
42
  displayMode:NO
43
43
  fontSize:self.fontSize
44
44
  color:color
45
- bold:self.bold];
45
+ bold:self.bold
46
+ glyphOverflow:self.glyphOverflow];
46
47
  if (!result) {
47
48
  _fallbackSource = ENRMMathFallbackString(self.latex, @"$", self.fontSize, color);
48
49
  UIFont *font = [_fallbackSource attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL];
@@ -87,6 +87,7 @@ payload.setProperty(runtime, "whole", event.whole);
87
87
  endRect.setProperty(runtime, "height", event.endRect.height);
88
88
  payload.setProperty(runtime, "endRect", endRect);
89
89
  }
90
+ payload.setProperty(runtime, "adjusting", event.adjusting);
90
91
  return payload;
91
92
  });
92
93
  }
@@ -487,6 +488,7 @@ payload.setProperty(runtime, "whole", event.whole);
487
488
  endRect.setProperty(runtime, "height", event.endRect.height);
488
489
  payload.setProperty(runtime, "endRect", endRect);
489
490
  }
491
+ payload.setProperty(runtime, "adjusting", event.adjusting);
490
492
  return payload;
491
493
  });
492
494
  }
@@ -69,6 +69,7 @@ class EnrichedMarkdownEventEmitter : public ViewEventEmitter {
69
69
  OnSelectionChangeRect rect;
70
70
  OnSelectionChangeStartRect startRect;
71
71
  OnSelectionChangeEndRect endRect;
72
+ bool adjusting;
72
73
  };
73
74
 
74
75
  struct OnTaskListItemPress {
@@ -395,6 +396,7 @@ class EnrichedMarkdownTextEventEmitter : public ViewEventEmitter {
395
396
  OnSelectionChangeRect rect;
396
397
  OnSelectionChangeStartRect startRect;
397
398
  OnSelectionChangeEndRect endRect;
399
+ bool adjusting;
398
400
  };
399
401
 
400
402
  struct OnTaskListItemPress {
@@ -1761,6 +1761,7 @@ struct EnrichedMarkdownMarkdownStyleMathStruct {
1761
1761
  Float marginBottom{0.0};
1762
1762
  std::string textAlign{};
1763
1763
  std::string fontWeight{};
1764
+ Float glyphOverflow{0.0};
1764
1765
 
1765
1766
 
1766
1767
  #ifdef RN_SERIALIZABLE_STATE
@@ -1776,6 +1777,7 @@ struct EnrichedMarkdownMarkdownStyleMathStruct {
1776
1777
  result["marginBottom"] = marginBottom;
1777
1778
  result["textAlign"] = textAlign;
1778
1779
  result["fontWeight"] = fontWeight;
1780
+ result["glyphOverflow"] = glyphOverflow;
1779
1781
  return result;
1780
1782
  }
1781
1783
  #endif
@@ -1816,6 +1818,10 @@ static inline void fromRawValue(const PropsParserContext& context, const RawValu
1816
1818
  if (tmp_fontWeight != map.end()) {
1817
1819
  fromRawValue(context, tmp_fontWeight->second, result.fontWeight);
1818
1820
  }
1821
+ auto tmp_glyphOverflow = map.find("glyphOverflow");
1822
+ if (tmp_glyphOverflow != map.end()) {
1823
+ fromRawValue(context, tmp_glyphOverflow->second, result.glyphOverflow);
1824
+ }
1819
1825
  }
1820
1826
 
1821
1827
  static inline std::string toString(const EnrichedMarkdownMarkdownStyleMathStruct &value) {
@@ -1832,6 +1838,7 @@ struct EnrichedMarkdownMarkdownStyleInlineMathStruct {
1832
1838
  SharedColor color{};
1833
1839
  Float paddingVertical{0.0};
1834
1840
  std::string fontWeight{};
1841
+ Float glyphOverflow{0.0};
1835
1842
 
1836
1843
 
1837
1844
  #ifdef RN_SERIALIZABLE_STATE
@@ -1842,6 +1849,7 @@ struct EnrichedMarkdownMarkdownStyleInlineMathStruct {
1842
1849
  result["color"] = ::facebook::react::toDynamic(color);
1843
1850
  result["paddingVertical"] = paddingVertical;
1844
1851
  result["fontWeight"] = fontWeight;
1852
+ result["glyphOverflow"] = glyphOverflow;
1845
1853
  return result;
1846
1854
  }
1847
1855
  #endif
@@ -1862,6 +1870,10 @@ static inline void fromRawValue(const PropsParserContext& context, const RawValu
1862
1870
  if (tmp_fontWeight != map.end()) {
1863
1871
  fromRawValue(context, tmp_fontWeight->second, result.fontWeight);
1864
1872
  }
1873
+ auto tmp_glyphOverflow = map.find("glyphOverflow");
1874
+ if (tmp_glyphOverflow != map.end()) {
1875
+ fromRawValue(context, tmp_glyphOverflow->second, result.glyphOverflow);
1876
+ }
1865
1877
  }
1866
1878
 
1867
1879
  static inline std::string toString(const EnrichedMarkdownMarkdownStyleInlineMathStruct &value) {
@@ -5807,6 +5819,7 @@ struct EnrichedMarkdownTextMarkdownStyleMathStruct {
5807
5819
  Float marginBottom{0.0};
5808
5820
  std::string textAlign{};
5809
5821
  std::string fontWeight{};
5822
+ Float glyphOverflow{0.0};
5810
5823
 
5811
5824
 
5812
5825
  #ifdef RN_SERIALIZABLE_STATE
@@ -5822,6 +5835,7 @@ struct EnrichedMarkdownTextMarkdownStyleMathStruct {
5822
5835
  result["marginBottom"] = marginBottom;
5823
5836
  result["textAlign"] = textAlign;
5824
5837
  result["fontWeight"] = fontWeight;
5838
+ result["glyphOverflow"] = glyphOverflow;
5825
5839
  return result;
5826
5840
  }
5827
5841
  #endif
@@ -5862,6 +5876,10 @@ static inline void fromRawValue(const PropsParserContext& context, const RawValu
5862
5876
  if (tmp_fontWeight != map.end()) {
5863
5877
  fromRawValue(context, tmp_fontWeight->second, result.fontWeight);
5864
5878
  }
5879
+ auto tmp_glyphOverflow = map.find("glyphOverflow");
5880
+ if (tmp_glyphOverflow != map.end()) {
5881
+ fromRawValue(context, tmp_glyphOverflow->second, result.glyphOverflow);
5882
+ }
5865
5883
  }
5866
5884
 
5867
5885
  static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleMathStruct &value) {
@@ -5878,6 +5896,7 @@ struct EnrichedMarkdownTextMarkdownStyleInlineMathStruct {
5878
5896
  SharedColor color{};
5879
5897
  Float paddingVertical{0.0};
5880
5898
  std::string fontWeight{};
5899
+ Float glyphOverflow{0.0};
5881
5900
 
5882
5901
 
5883
5902
  #ifdef RN_SERIALIZABLE_STATE
@@ -5888,6 +5907,7 @@ struct EnrichedMarkdownTextMarkdownStyleInlineMathStruct {
5888
5907
  result["color"] = ::facebook::react::toDynamic(color);
5889
5908
  result["paddingVertical"] = paddingVertical;
5890
5909
  result["fontWeight"] = fontWeight;
5910
+ result["glyphOverflow"] = glyphOverflow;
5891
5911
  return result;
5892
5912
  }
5893
5913
  #endif
@@ -5908,6 +5928,10 @@ static inline void fromRawValue(const PropsParserContext& context, const RawValu
5908
5928
  if (tmp_fontWeight != map.end()) {
5909
5929
  fromRawValue(context, tmp_fontWeight->second, result.fontWeight);
5910
5930
  }
5931
+ auto tmp_glyphOverflow = map.find("glyphOverflow");
5932
+ if (tmp_glyphOverflow != map.end()) {
5933
+ fromRawValue(context, tmp_glyphOverflow->second, result.glyphOverflow);
5934
+ }
5911
5935
  }
5912
5936
 
5913
5937
  static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleInlineMathStruct &value) {
@@ -144,8 +144,9 @@ template <typename StyleStruct> inline size_t computeStyleFingerprint(const Styl
144
144
  hashTextLayout(s.table);
145
145
  hashFields(s.table.headerFontFamily, s.table.cellPaddingHorizontal, s.table.cellPaddingVertical, s.table.borderWidth,
146
146
  s.table.borderRadius, s.table.horizontalOverflow);
147
- hashFields(s.math.fontSize, s.math.padding, s.math.marginTop, s.math.marginBottom, s.math.textAlign);
148
- hashFields(s.inlineMath.paddingVertical);
147
+ hashFields(s.math.fontSize, s.math.padding, s.math.marginTop, s.math.marginBottom, s.math.textAlign,
148
+ s.math.glyphOverflow);
149
+ hashFields(s.inlineMath.paddingVertical, s.inlineMath.glyphOverflow);
149
150
  hashFields(s.taskList.checkboxSize, s.taskList.checkboxBorderRadius);
150
151
 
151
152
  return h;
@@ -42,23 +42,39 @@ public final class ENRMRaTeXRenderResult: NSObject {
42
42
  /// Non-zero only in bold mode: half of it grows the glyph ink on each side.
43
43
  private let boldStrokeWidth: CGFloat
44
44
  private let boldStrokeColor: CGColor?
45
+ /// Headroom added above and below RaTeX's box. RaTeX sizes a formula by its TeX
46
+ /// metric box, which digit and capital outlines (and the bold stroke) overshoot;
47
+ /// every view and attachment clips to `totalHeight`, so that ink needs room inside it.
48
+ private let glyphOverflow: CGFloat
45
49
 
46
50
  @objc public let width: CGFloat
47
51
  @objc public let ascent: CGFloat
48
52
  @objc public let descent: CGFloat
49
53
  @objc public var totalHeight: CGFloat { ascent + descent }
50
54
 
51
- init(renderer: RaTeXRenderer, boldStrokeWidth: CGFloat, boldStrokeColor: CGColor?) {
55
+ init(renderer: RaTeXRenderer, boldStrokeWidth: CGFloat, boldStrokeColor: CGColor?, glyphOverflow: CGFloat) {
52
56
  self.renderer = renderer
53
57
  self.boldStrokeWidth = boldStrokeWidth
54
58
  self.boldStrokeColor = boldStrokeColor
59
+ self.glyphOverflow = max(glyphOverflow, 0)
55
60
  self.width = renderer.width
56
- self.ascent = renderer.height
57
- self.descent = renderer.depth
61
+ self.ascent = renderer.height + self.glyphOverflow
62
+ self.descent = renderer.depth + self.glyphOverflow
58
63
  super.init()
59
64
  }
60
65
 
61
66
  @objc public func draw(in context: CGContext) {
67
+ guard glyphOverflow > 0 else {
68
+ drawFormula(in: context)
69
+ return
70
+ }
71
+ context.saveGState()
72
+ context.translateBy(x: 0, y: glyphOverflow)
73
+ drawFormula(in: context)
74
+ context.restoreGState()
75
+ }
76
+
77
+ private func drawFormula(in context: CGContext) {
62
78
  guard boldStrokeWidth > 0, let boldStrokeColor else {
63
79
  renderer.draw(in: context)
64
80
  return
@@ -90,7 +106,8 @@ public final class ENRMRaTeXBridge: NSObject {
90
106
  displayMode: Bool,
91
107
  fontSize: CGFloat,
92
108
  color: UIColor,
93
- bold: Bool
109
+ bold: Bool,
110
+ glyphOverflow: CGFloat
94
111
  ) -> ENRMRaTeXRenderResult? {
95
112
  RaTeXFontLoader.ensureLoaded()
96
113
  do {
@@ -100,7 +117,8 @@ public final class ENRMRaTeXBridge: NSObject {
100
117
  return ENRMRaTeXRenderResult(
101
118
  renderer: renderer,
102
119
  boldStrokeWidth: strokeWidth,
103
- boldStrokeColor: strokeWidth > 0 ? color.cgColor : nil
120
+ boldStrokeColor: strokeWidth > 0 ? color.cgColor : nil,
121
+ glyphOverflow: glyphOverflow
104
122
  )
105
123
  } catch {
106
124
  NSLog("[RaTeX] Failed to parse LaTeX: %@", error.localizedDescription)
@@ -30,6 +30,7 @@
30
30
  attachment.mathTextColor = [_config inlineMathColor];
31
31
  attachment.containerPaddingVertical = [_config inlineMathPaddingVertical];
32
32
  attachment.bold = ENRMMathFontWeightIsBold([_config inlineMathFontWeight]);
33
+ attachment.glyphOverflow = [_config inlineMathGlyphOverflow];
33
34
 
34
35
  #if TARGET_OS_OSX
35
36
  // On macOS, NSLayoutManager uses self.image/self.bounds rather than calling
@@ -418,6 +418,8 @@ NS_ASSUME_NONNULL_BEGIN
418
418
  - (void)setMathTextAlign:(NSString *)newValue;
419
419
  - (NSString *)mathFontWeight;
420
420
  - (void)setMathFontWeight:(NSString *)newValue;
421
+ - (CGFloat)mathGlyphOverflow;
422
+ - (void)setMathGlyphOverflow:(CGFloat)newValue;
421
423
 
422
424
  // Inline Math properties
423
425
  - (RCTUIColor *)inlineMathColor;
@@ -426,6 +428,8 @@ NS_ASSUME_NONNULL_BEGIN
426
428
  - (void)setInlineMathPaddingVertical:(CGFloat)newValue;
427
429
  - (NSString *)inlineMathFontWeight;
428
430
  - (void)setInlineMathFontWeight:(NSString *)newValue;
431
+ - (CGFloat)inlineMathGlyphOverflow;
432
+ - (void)setInlineMathGlyphOverflow:(CGFloat)newValue;
429
433
  // Spoiler properties
430
434
  - (RCTUIColor *)spoilerColor;
431
435
  - (void)setSpoilerColor:(RCTUIColor *)newValue;
@@ -244,10 +244,12 @@ static inline NSString *normalizedFontWeight(NSString *fontWeight)
244
244
  CGFloat _mathMarginBottom;
245
245
  NSString *_mathTextAlign;
246
246
  NSString *_mathFontWeight;
247
+ CGFloat _mathGlyphOverflow;
247
248
  // Inline Math properties
248
249
  RCTUIColor *_inlineMathColor;
249
250
  CGFloat _inlineMathPaddingVertical;
250
251
  NSString *_inlineMathFontWeight;
252
+ CGFloat _inlineMathGlyphOverflow;
251
253
  // Spoiler properties
252
254
  RCTUIColor *_spoilerColor;
253
255
  CGFloat _spoilerParticleDensity;
@@ -522,9 +524,11 @@ static inline NSString *normalizedFontWeight(NSString *fontWeight)
522
524
  copy->_mathMarginBottom = _mathMarginBottom;
523
525
  copy->_mathTextAlign = [_mathTextAlign copy];
524
526
  copy->_mathFontWeight = [_mathFontWeight copy];
527
+ copy->_mathGlyphOverflow = _mathGlyphOverflow;
525
528
  copy->_inlineMathColor = [_inlineMathColor copy];
526
529
  copy->_inlineMathPaddingVertical = _inlineMathPaddingVertical;
527
530
  copy->_inlineMathFontWeight = [_inlineMathFontWeight copy];
531
+ copy->_inlineMathGlyphOverflow = _inlineMathGlyphOverflow;
528
532
  copy->_spoilerColor = [_spoilerColor copy];
529
533
  copy->_spoilerParticleDensity = _spoilerParticleDensity;
530
534
  copy->_spoilerParticleSpeed = _spoilerParticleSpeed;
@@ -2658,6 +2662,26 @@ static const CGFloat kDefaultMinGap = 4.0;
2658
2662
  _inlineMathPaddingVertical = newValue;
2659
2663
  }
2660
2664
 
2665
+ - (CGFloat)mathGlyphOverflow
2666
+ {
2667
+ return _mathGlyphOverflow;
2668
+ }
2669
+
2670
+ - (void)setMathGlyphOverflow:(CGFloat)newValue
2671
+ {
2672
+ _mathGlyphOverflow = newValue;
2673
+ }
2674
+
2675
+ - (CGFloat)inlineMathGlyphOverflow
2676
+ {
2677
+ return _inlineMathGlyphOverflow;
2678
+ }
2679
+
2680
+ - (void)setInlineMathGlyphOverflow:(CGFloat)newValue
2681
+ {
2682
+ _inlineMathGlyphOverflow = newValue;
2683
+ }
2684
+
2661
2685
  // ── Spoiler ─────────────────────────────────────────────────────────────
2662
2686
 
2663
2687
  - (RCTUIColor *)spoilerColor
@@ -155,7 +155,8 @@ void ENRMEnumerateLinkChipFragments(NSLayoutManager *layoutManager, NSTextContai
155
155
 
156
156
  CGFloat right;
157
157
  if (visibleGlyphEnd < lineEnd) {
158
- right = lineRect.origin.x + [layoutManager locationForGlyphAtIndex:visibleGlyphEnd].x;
158
+ right =
159
+ lineRect.origin.x + [layoutManager locationForGlyphAtIndex:visibleGlyphEnd].x;
159
160
  } else {
160
161
  right = CGRectGetMaxX(usedRect);
161
162
  }
@@ -14,6 +14,7 @@ extern NSString *const ENRMSelectionWholeKey;
14
14
  extern NSString *const ENRMSelectionRectKey;
15
15
  extern NSString *const ENRMSelectionStartRectKey;
16
16
  extern NSString *const ENRMSelectionEndRectKey;
17
+ extern NSString *const ENRMSelectionAdjustingKey;
17
18
  /// Only in `ENRMTextSelectionInTextView` results: whether the range spans the
18
19
  /// whole text (same threshold `markdownForRange` uses for the cached markdown).
19
20
  extern NSString *const ENRMSelectionFullKey;
@@ -63,6 +64,28 @@ NSDictionary *_Nullable ENRMTextSelectionInTextView(UITextView *textView, UIView
63
64
 
64
65
  @end
65
66
 
67
+ #if !TARGET_OS_OSX
68
+ /// Tells when the user is dragging a selection handle (or holding the loupe).
69
+ /// `UITextView` doesn't call `textViewDidChangeSelection:` during the drag, so
70
+ /// this watches the text view's own selection gestures: every recognizer of
71
+ /// the view and its subviews except scroll, pinch and taps. UIKit adds the
72
+ /// handle recognizers lazily, so call `trackTextView:` again on each selection
73
+ /// change. `change` runs whenever `adjusting` flips.
74
+ @interface ENRMSelectionGestureTracker : NSObject
75
+
76
+ - (instancetype)initWithChange:(void (^)(void))change NS_DESIGNATED_INITIALIZER;
77
+ - (instancetype)init NS_UNAVAILABLE;
78
+
79
+ /// A tracked gesture is in `Began` / `Changed`.
80
+ @property (nonatomic, readonly) BOOL adjusting;
81
+
82
+ - (void)trackTextView:(UITextView *)textView;
83
+ /// Clears `adjusting` without calling `change` (view recycling).
84
+ - (void)reset;
85
+
86
+ @end
87
+ #endif
88
+
66
89
  #ifdef __cplusplus
67
90
  /// Converts a payload into a codegen `OnSelectionChange` struct. Templated
68
91
  /// because each component has its own (structurally identical) event struct.
@@ -74,6 +97,7 @@ template <typename Event> inline Event ENRMSelectionChangeEventFromPayload(NSDic
74
97
  event.text = std::string([payload[ENRMSelectionTextKey] UTF8String] ?: "");
75
98
  event.markdown = std::string([payload[ENRMSelectionMarkdownKey] UTF8String] ?: "");
76
99
  event.whole = [payload[ENRMSelectionWholeKey] boolValue];
100
+ event.adjusting = [payload[ENRMSelectionAdjustingKey] boolValue];
77
101
 
78
102
  auto assign = [](auto &target, NSValue *value) {
79
103
  #if TARGET_OS_OSX
@@ -9,6 +9,7 @@ NSString *const ENRMSelectionWholeKey = @"whole";
9
9
  NSString *const ENRMSelectionRectKey = @"rect";
10
10
  NSString *const ENRMSelectionStartRectKey = @"startRect";
11
11
  NSString *const ENRMSelectionEndRectKey = @"endRect";
12
+ NSString *const ENRMSelectionAdjustingKey = @"adjusting";
12
13
  NSString *const ENRMSelectionFullKey = @"full";
13
14
 
14
15
  static NSValue *ENRMRectValue(CGRect rect)
@@ -202,3 +203,80 @@ NSDictionary *ENRMTextSelectionInTextView(UITextView *textView, UIView *targetVi
202
203
  }
203
204
 
204
205
  @end
206
+
207
+ #if !TARGET_OS_OSX
208
+ static void ENRMCollectGestures(UIView *view, NSMutableArray<UIGestureRecognizer *> *into)
209
+ {
210
+ for (UIGestureRecognizer *recognizer in view.gestureRecognizers) {
211
+ [into addObject:recognizer];
212
+ }
213
+ for (UIView *subview in view.subviews) {
214
+ ENRMCollectGestures(subview, into);
215
+ }
216
+ }
217
+
218
+ @implementation ENRMSelectionGestureTracker {
219
+ void (^_change)(void);
220
+ NSHashTable<UIGestureRecognizer *> *_gestures;
221
+ }
222
+
223
+ - (instancetype)initWithChange:(void (^)(void))change
224
+ {
225
+ if (self = [super init]) {
226
+ _change = [change copy];
227
+ _gestures = [NSHashTable weakObjectsHashTable];
228
+ }
229
+ return self;
230
+ }
231
+
232
+ - (void)dealloc
233
+ {
234
+ // Recognizers don't retain their targets.
235
+ for (UIGestureRecognizer *recognizer in _gestures) {
236
+ [recognizer removeTarget:self action:@selector(gestureChanged:)];
237
+ }
238
+ }
239
+
240
+ - (void)trackTextView:(UITextView *)textView
241
+ {
242
+ NSMutableArray<UIGestureRecognizer *> *all = [NSMutableArray array];
243
+ ENRMCollectGestures(textView, all);
244
+ for (UIGestureRecognizer *recognizer in all) {
245
+ if ([_gestures containsObject:recognizer]) {
246
+ continue;
247
+ }
248
+ if (recognizer == textView.panGestureRecognizer || recognizer == textView.pinchGestureRecognizer) {
249
+ continue;
250
+ }
251
+ if ([recognizer isKindOfClass:[UITapGestureRecognizer class]]) {
252
+ continue;
253
+ }
254
+ [_gestures addObject:recognizer];
255
+ [recognizer addTarget:self action:@selector(gestureChanged:)];
256
+ }
257
+ }
258
+
259
+ - (void)reset
260
+ {
261
+ _adjusting = NO;
262
+ }
263
+
264
+ - (void)gestureChanged:(UIGestureRecognizer *)recognizer
265
+ {
266
+ BOOL adjusting = NO;
267
+ for (UIGestureRecognizer *tracked in _gestures) {
268
+ UIGestureRecognizerState state = tracked.state;
269
+ if (state == UIGestureRecognizerStateBegan || state == UIGestureRecognizerStateChanged) {
270
+ adjusting = YES;
271
+ break;
272
+ }
273
+ }
274
+ if (adjusting == _adjusting) {
275
+ return;
276
+ }
277
+ _adjusting = adjusting;
278
+ _change();
279
+ }
280
+
281
+ @end
282
+ #endif
@@ -1167,6 +1167,11 @@ BOOL applyMarkdownStyleToConfig(StyleConfig *config, const MarkdownStyle &newSty
1167
1167
  changed = YES;
1168
1168
  }
1169
1169
 
1170
+ if (newStyle.math.glyphOverflow != oldStyle.math.glyphOverflow) {
1171
+ [config setMathGlyphOverflow:newStyle.math.glyphOverflow];
1172
+ changed = YES;
1173
+ }
1174
+
1170
1175
  if (newStyle.math.marginTop != oldStyle.math.marginTop) {
1171
1176
  [config setMathMarginTop:newStyle.math.marginTop];
1172
1177
  changed = YES;
@@ -1215,6 +1220,11 @@ BOOL applyMarkdownStyleToConfig(StyleConfig *config, const MarkdownStyle &newSty
1215
1220
  changed = YES;
1216
1221
  }
1217
1222
 
1223
+ if (newStyle.inlineMath.glyphOverflow != oldStyle.inlineMath.glyphOverflow) {
1224
+ [config setInlineMathGlyphOverflow:newStyle.inlineMath.glyphOverflow];
1225
+ changed = YES;
1226
+ }
1227
+
1218
1228
  // ── Spoiler ─────────────────────────────────────────────────────────────
1219
1229
 
1220
1230
  if (newStyle.spoiler.color != oldStyle.spoiler.color) {
@@ -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
 
@@ -199,7 +200,8 @@
199
200
  displayMode:YES
200
201
  fontSize:config.mathFontSize
201
202
  color:config.mathColor
202
- bold:ENRMMathFontWeightIsBold(config.mathFontWeight)];
203
+ bold:ENRMMathFontWeightIsBold(config.mathFontWeight)
204
+ glyphOverflow:config.mathGlyphOverflow];
203
205
  if (result) {
204
206
  return ceil(result.totalHeight) + padding * 2;
205
207
  }
@@ -181,12 +181,14 @@ interface MathStyleInternal {
181
181
  marginBottom: CodegenTypes.Float;
182
182
  textAlign: string;
183
183
  fontWeight: string;
184
+ glyphOverflow: CodegenTypes.Float;
184
185
  }
185
186
 
186
187
  interface InlineMathStyleInternal {
187
188
  color: ColorValue;
188
189
  paddingVertical: CodegenTypes.Float;
189
190
  fontWeight: string;
191
+ glyphOverflow: CodegenTypes.Float;
190
192
  }
191
193
 
192
194
  interface SpoilerParticlesStyleInternal {
@@ -308,6 +310,7 @@ export interface SelectionChangeEvent {
308
310
  width: CodegenTypes.Double;
309
311
  height: CodegenTypes.Double;
310
312
  };
313
+ adjusting: boolean;
311
314
  }
312
315
 
313
316
  export interface TaskListItemPressEvent {
@@ -182,12 +182,14 @@ interface MathStyleInternal {
182
182
  marginBottom: CodegenTypes.Float;
183
183
  textAlign: string;
184
184
  fontWeight: string;
185
+ glyphOverflow: CodegenTypes.Float;
185
186
  }
186
187
 
187
188
  interface InlineMathStyleInternal {
188
189
  color: ColorValue;
189
190
  paddingVertical: CodegenTypes.Float;
190
191
  fontWeight: string;
192
+ glyphOverflow: CodegenTypes.Float;
191
193
  }
192
194
 
193
195
  interface SpoilerParticlesStyleInternal {
@@ -309,6 +311,7 @@ export interface SelectionChangeEvent {
309
311
  width: CodegenTypes.Double;
310
312
  height: CodegenTypes.Double;
311
313
  };
314
+ adjusting: boolean;
312
315
  }
313
316
 
314
317
  export interface TaskListItemPressEvent {
@@ -284,12 +284,14 @@ const DEFAULT_NORMALIZED_STYLE = Object.freeze({
284
284
  marginTop: 0,
285
285
  marginBottom: 16,
286
286
  textAlign: 'center',
287
- fontWeight: 'normal'
287
+ fontWeight: 'normal',
288
+ glyphOverflow: 0
288
289
  },
289
290
  inlineMath: {
290
291
  color: defaultTextColor,
291
292
  paddingVertical: 4,
292
- fontWeight: 'normal'
293
+ fontWeight: 'normal',
294
+ glyphOverflow: 0
293
295
  },
294
296
  taskList: {
295
297
  checkedColor: Platform.select({