react-native-enriched-markdown-scaffold 0.6.0-scaffold.8 → 0.7.4-scaffold.2

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 (150) hide show
  1. package/ReactNativeEnrichedMarkdown.podspec +62 -11
  2. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerDelegate.java +3 -0
  3. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerInterface.java +1 -0
  4. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextInputManagerDelegate.java +9 -0
  5. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextInputManagerInterface.java +3 -0
  6. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +3 -0
  7. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +1 -0
  8. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +20 -0
  9. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +744 -0
  10. package/android/src/main/baseline-prof.txt +75 -2
  11. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +62 -0
  12. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +10 -2
  13. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +10 -0
  14. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +9 -2
  15. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +10 -0
  16. package/android/src/main/java/com/swmansion/enriched/markdown/accessibility/AccessibilityLabels.kt +15 -0
  17. package/android/src/main/java/com/swmansion/enriched/markdown/accessibility/MarkdownAccessibilityHelper.kt +44 -7
  18. package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputManager.kt +52 -0
  19. package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputView.kt +89 -11
  20. package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/FormattingStore.kt +19 -0
  21. package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/InputParser.kt +22 -3
  22. package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/InputRemend.kt +1 -0
  23. package/android/src/main/java/com/swmansion/enriched/markdown/input/layout/InputMeasurementStore.kt +7 -1
  24. package/android/src/main/java/com/swmansion/enriched/markdown/input/toolbar/InputContextMenu.kt +54 -13
  25. package/android/src/main/java/com/swmansion/enriched/markdown/parser/MarkdownASTNode.kt +17 -0
  26. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +4 -4
  27. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +30 -0
  28. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkEvents.kt +22 -0
  29. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt +36 -7
  30. package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +73 -3
  31. package/android/src/main/jni/CMakeLists.txt +15 -1
  32. package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +24 -2
  33. package/app.plugin.js +2 -1
  34. package/cpp/parser/MD4CParser.cpp +1 -1
  35. package/cpp/wasm/build.sh +9 -9
  36. package/docs/ACCESSIBILITY.md +172 -0
  37. package/docs/API_REFERENCE.md +948 -0
  38. package/docs/COPY_OPTIONS.md +127 -0
  39. package/docs/ELEMENTS_STRUCTURE.md +112 -0
  40. package/docs/IMAGE_CACHING.md +25 -0
  41. package/docs/INPUT.md +233 -0
  42. package/docs/LATEX_MATH.md +136 -0
  43. package/docs/MACOS.md +19 -0
  44. package/docs/MARKDOWN_STREAMING.md +36 -0
  45. package/docs/MENTIONS.md +97 -0
  46. package/docs/RTL.md +82 -0
  47. package/docs/STYLES.md +519 -0
  48. package/docs/TEXT.md +110 -0
  49. package/docs/WEB.md +49 -0
  50. package/ios/EnrichedMarkdown.mm +82 -25
  51. package/ios/EnrichedMarkdownText.mm +37 -5
  52. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp +20 -0
  53. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +744 -0
  54. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +15 -0
  55. package/ios/input/ENRMFormattingStore.h +5 -0
  56. package/ios/input/ENRMFormattingStore.mm +34 -0
  57. package/ios/input/ENRMInputTextView.h +2 -0
  58. package/ios/input/ENRMInputTextView.mm +8 -17
  59. package/ios/input/ENRMMarkdownSerializer.mm +1 -1
  60. package/ios/input/EnrichedMarkdownTextInput+ContextMenu.mm +71 -53
  61. package/ios/input/EnrichedMarkdownTextInput+Internal.h +26 -0
  62. package/ios/input/EnrichedMarkdownTextInput.h +1 -0
  63. package/ios/input/EnrichedMarkdownTextInput.mm +230 -29
  64. package/ios/input/styles/ENRMBoldStyleHandler.mm +3 -2
  65. package/ios/input/styles/ENRMItalicStyleHandler.mm +3 -2
  66. package/ios/renderer/AttributedRenderer.m +51 -6
  67. package/ios/renderer/ENRMMathInlineRenderer.m +5 -1
  68. package/ios/styles/ENRMFontSlot.h +1 -1
  69. package/ios/utils/ENRMAccessibilityLabels.h +36 -0
  70. package/ios/utils/ENRMAccessibilityLabels.m +5 -0
  71. package/ios/utils/EditMenuUtils+macOS.m +4 -5
  72. package/ios/utils/EditMenuUtils.h +70 -0
  73. package/ios/utils/EditMenuUtils.m +14 -11
  74. package/ios/utils/LastElementUtils.h +39 -2
  75. package/ios/utils/MarkdownAccessibilityElementBuilder.h +18 -16
  76. package/ios/utils/MarkdownAccessibilityElementBuilder.m +64 -32
  77. package/ios/utils/ParagraphStyleUtils.m +21 -2
  78. package/ios/views/ENRMMathContainerView.h +8 -0
  79. package/ios/views/ENRMMathContainerView.m +7 -5
  80. package/ios/views/ENRMTableIOSGridView.h +40 -0
  81. package/ios/views/ENRMTableIOSGridView.m +216 -0
  82. package/ios/views/EnrichedMarkdownInternalText.h +2 -0
  83. package/ios/views/EnrichedMarkdownInternalText.m +2 -0
  84. package/ios/views/TableContainerView.h +8 -0
  85. package/ios/views/TableContainerView.m +72 -128
  86. package/lib/module/EnrichedMarkdownNativeComponent.ts +37 -0
  87. package/lib/module/EnrichedMarkdownTextInput.js +48 -1
  88. package/lib/module/EnrichedMarkdownTextInput.js.map +1 -1
  89. package/lib/module/EnrichedMarkdownTextInputNativeComponent.ts +35 -0
  90. package/lib/module/EnrichedMarkdownTextNativeComponent.ts +37 -0
  91. package/lib/module/accessibilityLabelDefaults.js +64 -0
  92. package/lib/module/accessibilityLabelDefaults.js.map +1 -0
  93. package/lib/module/index.js +1 -0
  94. package/lib/module/index.js.map +1 -1
  95. package/lib/module/native/EnrichedMarkdownText.js +72 -4
  96. package/lib/module/native/EnrichedMarkdownText.js.map +1 -1
  97. package/lib/module/normalizeMenuItem.js +38 -0
  98. package/lib/module/normalizeMenuItem.js.map +1 -0
  99. package/lib/module/types/AccessibilityLabels.js +2 -0
  100. package/lib/module/types/AccessibilityLabels.js.map +1 -0
  101. package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +34 -0
  102. package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
  103. package/lib/typescript/src/EnrichedMarkdownTextInput.d.ts +55 -1
  104. package/lib/typescript/src/EnrichedMarkdownTextInput.d.ts.map +1 -1
  105. package/lib/typescript/src/EnrichedMarkdownTextInputNativeComponent.d.ts +30 -0
  106. package/lib/typescript/src/EnrichedMarkdownTextInputNativeComponent.d.ts.map +1 -1
  107. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +34 -0
  108. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
  109. package/lib/typescript/src/accessibilityLabelDefaults.d.ts +16 -0
  110. package/lib/typescript/src/accessibilityLabelDefaults.d.ts.map +1 -0
  111. package/lib/typescript/src/index.d.ts +4 -2
  112. package/lib/typescript/src/index.d.ts.map +1 -1
  113. package/lib/typescript/src/native/EnrichedMarkdownText.d.ts +3 -3
  114. package/lib/typescript/src/native/EnrichedMarkdownText.d.ts.map +1 -1
  115. package/lib/typescript/src/normalizeMenuItem.d.ts +7 -0
  116. package/lib/typescript/src/normalizeMenuItem.d.ts.map +1 -0
  117. package/lib/typescript/src/types/AccessibilityLabels.d.ts +114 -0
  118. package/lib/typescript/src/types/AccessibilityLabels.d.ts.map +1 -0
  119. package/lib/typescript/src/types/MarkdownTextProps.d.ts +78 -9
  120. package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
  121. package/package.json +4 -2
  122. package/plugin/build/withAndroidMath.js +19 -0
  123. package/plugin/build/withIosMath.js +28 -0
  124. package/plugin/build/withReactNativeEnrichedMarkdown.js +11 -0
  125. package/plugin/tsconfig.build.json +16 -0
  126. package/src/EnrichedMarkdownNativeComponent.ts +37 -0
  127. package/src/EnrichedMarkdownTextInput.tsx +117 -1
  128. package/src/EnrichedMarkdownTextInputNativeComponent.ts +35 -0
  129. package/src/EnrichedMarkdownTextNativeComponent.ts +37 -0
  130. package/src/accessibilityLabelDefaults.ts +83 -0
  131. package/src/index.tsx +11 -0
  132. package/src/native/EnrichedMarkdownText.tsx +120 -6
  133. package/src/normalizeMenuItem.ts +52 -0
  134. package/src/types/AccessibilityLabels.ts +118 -0
  135. package/src/types/MarkdownTextProps.ts +74 -9
  136. package/lib/module/plugin/withAndroidMath.js +0 -20
  137. package/lib/module/plugin/withAndroidMath.js.map +0 -1
  138. package/lib/module/plugin/withIosMath.js +0 -23
  139. package/lib/module/plugin/withIosMath.js.map +0 -1
  140. package/lib/module/plugin/withReactNativeEnrichedMarkdown.js +0 -16
  141. package/lib/module/plugin/withReactNativeEnrichedMarkdown.js.map +0 -1
  142. package/lib/typescript/src/plugin/withAndroidMath.d.ts +0 -5
  143. package/lib/typescript/src/plugin/withAndroidMath.d.ts.map +0 -1
  144. package/lib/typescript/src/plugin/withIosMath.d.ts +0 -5
  145. package/lib/typescript/src/plugin/withIosMath.d.ts.map +0 -1
  146. package/lib/typescript/src/plugin/withReactNativeEnrichedMarkdown.d.ts +0 -6
  147. package/lib/typescript/src/plugin/withReactNativeEnrichedMarkdown.d.ts.map +0 -1
  148. /package/{src/plugin → plugin/src}/withAndroidMath.ts +0 -0
  149. /package/{src/plugin → plugin/src}/withIosMath.ts +0 -0
  150. /package/{src/plugin → plugin/src}/withReactNativeEnrichedMarkdown.ts +0 -0
@@ -0,0 +1,216 @@
1
+ #import "ENRMTableIOSGridView.h"
2
+
3
+ #if !TARGET_OS_OSX
4
+
5
+ @implementation ENRMTableIOSRowData
6
+ @end
7
+
8
+ @implementation ENRMTableIOSGridView {
9
+ NSArray<ENRMTableIOSRowData *> *_tableRows;
10
+ NSArray<NSNumber *> *_columnWidths;
11
+ NSArray<NSNumber *> *_rowHeights;
12
+ UIColor *_borderColor;
13
+ CGFloat _borderWidth;
14
+ CGFloat _horizontalCellPadding;
15
+ CGFloat _verticalCellPadding;
16
+ }
17
+
18
+ - (instancetype)initWithFrame:(CGRect)frame
19
+ {
20
+ self = [super initWithFrame:frame];
21
+ if (self) {
22
+ self.backgroundColor = [UIColor clearColor];
23
+ self.contentMode = UIViewContentModeRedraw;
24
+ self.opaque = NO;
25
+ self.accessibilityElementsHidden = YES;
26
+
27
+ UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
28
+ [self addGestureRecognizer:tap];
29
+
30
+ UILongPressGestureRecognizer *longPress =
31
+ [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
32
+ [self addGestureRecognizer:longPress];
33
+ }
34
+ return self;
35
+ }
36
+
37
+ #pragma mark - Data
38
+
39
+ - (void)updateWithRows:(NSArray<ENRMTableIOSRowData *> *)rows
40
+ columnWidths:(NSArray<NSNumber *> *)columnWidths
41
+ rowHeights:(NSArray<NSNumber *> *)rowHeights
42
+ borderColor:(UIColor *)borderColor
43
+ borderWidth:(CGFloat)borderWidth
44
+ horizontalCellPadding:(CGFloat)horizontalCellPadding
45
+ verticalCellPadding:(CGFloat)verticalCellPadding
46
+ cornerRadius:(CGFloat)cornerRadius
47
+ {
48
+ _tableRows = [rows copy];
49
+ _columnWidths = [columnWidths copy];
50
+ _rowHeights = [rowHeights copy];
51
+ _borderColor = borderColor;
52
+ _borderWidth = borderWidth;
53
+ _horizontalCellPadding = horizontalCellPadding;
54
+ _verticalCellPadding = verticalCellPadding;
55
+ [self setNeedsDisplay];
56
+ }
57
+
58
+ - (void)fadeInRowsFrom:(NSUInteger)startRow duration:(NSTimeInterval)duration
59
+ {
60
+ if (startRow >= _tableRows.count)
61
+ return;
62
+
63
+ [UIView transitionWithView:self
64
+ duration:duration
65
+ options:UIViewAnimationOptionTransitionCrossDissolve
66
+ animations:^{}
67
+ completion:nil];
68
+ }
69
+
70
+ #pragma mark - Drawing
71
+
72
+ - (void)drawRect:(CGRect)dirtyRect
73
+ {
74
+ if (!_tableRows.count || !_columnWidths.count || !_rowHeights.count)
75
+ return;
76
+
77
+ CGFloat yOffset = 0;
78
+ for (NSUInteger r = 0; r < _tableRows.count; r++) {
79
+ ENRMTableIOSRowData *rowData = _tableRows[r];
80
+ CGFloat rowHeight = [_rowHeights[r] doubleValue];
81
+ CGFloat xOffset = 0;
82
+
83
+ NSUInteger colCount = MIN(rowData.cellTexts.count, _columnWidths.count);
84
+ for (NSUInteger c = 0; c < colCount; c++) {
85
+ CGFloat columnWidth = [_columnWidths[c] doubleValue];
86
+ CGRect cellRect = CGRectMake(xOffset, yOffset, columnWidth + _borderWidth, rowHeight + _borderWidth);
87
+
88
+ [rowData.backgroundColor setFill];
89
+ UIRectFill(cellRect);
90
+
91
+ [_borderColor setStroke];
92
+ UIBezierPath *border = [UIBezierPath bezierPathWithRect:cellRect];
93
+ border.lineWidth = _borderWidth;
94
+ [border stroke];
95
+
96
+ NSAttributedString *text = rowData.cellTexts[c];
97
+ if (text.length > 0) {
98
+ CGRect textRect = CGRectMake(xOffset + _horizontalCellPadding, yOffset + _verticalCellPadding,
99
+ columnWidth - _horizontalCellPadding * 2, rowHeight - _verticalCellPadding * 2);
100
+ [text drawWithRect:textRect
101
+ options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
102
+ context:nil];
103
+ }
104
+
105
+ xOffset += columnWidth;
106
+ }
107
+ yOffset += rowHeight;
108
+ }
109
+ }
110
+
111
+ #pragma mark - Link hit-testing
112
+
113
+ - (BOOL)cellAtPoint:(CGPoint)point
114
+ rowOrigin:(out CGFloat *)outRowY
115
+ rowHeight:(out CGFloat *)outRowH
116
+ colOrigin:(out CGFloat *)outColX
117
+ colWidth:(out CGFloat *)outColW
118
+ cellText:(out NSAttributedString *__autoreleasing *)outText
119
+ {
120
+ CGFloat rowY = 0;
121
+ for (NSUInteger r = 0; r < _tableRows.count; r++) {
122
+ CGFloat rh = [_rowHeights[r] doubleValue];
123
+ if (point.y >= rowY && point.y < rowY + rh) {
124
+ CGFloat colX = 0;
125
+ for (NSUInteger c = 0; c < _columnWidths.count; c++) {
126
+ CGFloat cw = [_columnWidths[c] doubleValue];
127
+ if (point.x >= colX && point.x < colX + cw) {
128
+ ENRMTableIOSRowData *rowData = _tableRows[r];
129
+ if (c >= rowData.cellTexts.count)
130
+ return NO;
131
+ *outRowY = rowY;
132
+ *outRowH = rh;
133
+ *outColX = colX;
134
+ *outColW = cw;
135
+ *outText = rowData.cellTexts[c];
136
+ return YES;
137
+ }
138
+ colX += cw;
139
+ }
140
+ return NO;
141
+ }
142
+ rowY += rh;
143
+ }
144
+ return NO;
145
+ }
146
+
147
+ static NSString *linkInAttributedString(NSAttributedString *text, CGRect textRect, CGPoint point)
148
+ {
149
+ if (text.length == 0)
150
+ return nil;
151
+
152
+ NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:text];
153
+ NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
154
+ NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:textRect.size];
155
+ textContainer.lineFragmentPadding = 0;
156
+ [layoutManager addTextContainer:textContainer];
157
+ [textStorage addLayoutManager:layoutManager];
158
+ [layoutManager ensureLayoutForTextContainer:textContainer];
159
+
160
+ CGPoint local = CGPointMake(point.x - textRect.origin.x, point.y - textRect.origin.y);
161
+ CGFloat fraction = 0;
162
+ NSUInteger glyphIndex = [layoutManager glyphIndexForPoint:local
163
+ inTextContainer:textContainer
164
+ fractionOfDistanceThroughGlyph:&fraction];
165
+ if (glyphIndex >= layoutManager.numberOfGlyphs)
166
+ return nil;
167
+ NSUInteger charIndex = [layoutManager characterIndexForGlyphAtIndex:glyphIndex];
168
+ if (charIndex >= text.length)
169
+ return nil;
170
+
171
+ return [text attribute:@"linkURL" atIndex:charIndex effectiveRange:NULL];
172
+ }
173
+
174
+ - (NSString *)linkURLAtPoint:(CGPoint)point
175
+ {
176
+ CGFloat rowY, rowH, colX, colW;
177
+ NSAttributedString *text;
178
+ if (![self cellAtPoint:point rowOrigin:&rowY rowHeight:&rowH colOrigin:&colX colWidth:&colW cellText:&text])
179
+ return nil;
180
+
181
+ CGRect textRect = CGRectMake(colX + _horizontalCellPadding, rowY + _verticalCellPadding,
182
+ colW - _horizontalCellPadding * 2, rowH - _verticalCellPadding * 2);
183
+ if (!CGRectContainsPoint(textRect, point))
184
+ return nil;
185
+
186
+ return linkInAttributedString(text, textRect, point);
187
+ }
188
+
189
+ #pragma mark - Gesture handlers
190
+
191
+ - (void)handleLinkGesture:(UIGestureRecognizer *)recognizer block:(ENRMTableIOSLinkBlock)block
192
+ {
193
+ CGPoint point = [recognizer locationInView:self];
194
+ NSString *url = [self linkURLAtPoint:point];
195
+ if (url && block) {
196
+ block(url);
197
+ }
198
+ }
199
+
200
+ - (void)handleTap:(UITapGestureRecognizer *)recognizer
201
+ {
202
+ if (recognizer.state == UIGestureRecognizerStateEnded) {
203
+ [self handleLinkGesture:recognizer block:self.onLinkTap];
204
+ }
205
+ }
206
+
207
+ - (void)handleLongPress:(UILongPressGestureRecognizer *)recognizer
208
+ {
209
+ if (recognizer.state == UIGestureRecognizerStateBegan) {
210
+ [self handleLinkGesture:recognizer block:self.onLinkLongTap];
211
+ }
212
+ }
213
+
214
+ @end
215
+
216
+ #endif // !TARGET_OS_OSX
@@ -8,6 +8,7 @@
8
8
 
9
9
  @class RenderContext;
10
10
  @class AccessibilityInfo;
11
+ @class ENRMAccessibilityLabels;
11
12
  @class ENRMSpoilerOverlayManager;
12
13
 
13
14
  NS_ASSUME_NONNULL_BEGIN
@@ -26,6 +27,7 @@ NS_ASSUME_NONNULL_BEGIN
26
27
  @property (nonatomic) ENRMSpoilerOverlay spoilerOverlay;
27
28
 
28
29
  @property (nonatomic, strong, nullable) AccessibilityInfo *accessibilityInfo;
30
+ @property (nonatomic, strong, nullable) ENRMAccessibilityLabels *accessibilityLabels;
29
31
 
30
32
  @property (nonatomic, strong) StyleConfig *config;
31
33
 
@@ -1,4 +1,5 @@
1
1
  #import "EnrichedMarkdownInternalText.h"
2
+ #import "ENRMAccessibilityLabels.h"
2
3
  #import "ENRMContextMenuTextView+macOS.h"
3
4
  #import "ENRMSpoilerOverlayManager.h"
4
5
  #import "ENRMTextViewSetup.h"
@@ -115,6 +116,7 @@
115
116
  #if !TARGET_OS_OSX
116
117
  _accessibilityElements = [MarkdownAccessibilityElementBuilder buildElementsForTextView:_textView
117
118
  info:_accessibilityInfo
119
+ labels:_accessibilityLabels
118
120
  container:self];
119
121
  #endif
120
122
  }
@@ -4,6 +4,7 @@
4
4
  #import "StyleConfig.h"
5
5
 
6
6
  @class MarkdownASTNode;
7
+ @class ENRMAccessibilityLabels;
7
8
 
8
9
  NS_ASSUME_NONNULL_BEGIN
9
10
 
@@ -30,6 +31,13 @@ typedef void (^TableLinkPressBlock)(NSString *url);
30
31
  @property (nonatomic, assign) ENRMWritingDirectionMode writingDirectionMode;
31
32
  @property (nonatomic, assign) NSWritingDirection resolvedLayoutDirection;
32
33
 
34
+ @property (nonatomic, strong, nullable) ENRMAccessibilityLabels *accessibilityLabels;
35
+
36
+ // Renamed getters avoid the Cocoa `copy` method family (which signals +1
37
+ // retained returns). Property names are unchanged so call sites stay the same.
38
+ @property (nonatomic, copy, nullable, getter=menuCopyLabel) NSString *copyLabel;
39
+ @property (nonatomic, copy, nullable, getter=menuCopyAsMarkdownLabel) NSString *copyAsMarkdownLabel;
40
+
33
41
  @property (nonatomic, readonly) NSUInteger rowCount;
34
42
 
35
43
  - (void)animateNewRowsFromPreviousCount:(NSUInteger)previousRowCount duration:(NSTimeInterval)duration;
@@ -1,5 +1,6 @@
1
1
  #import "TableContainerView.h"
2
2
  #import "AttributedRenderer.h"
3
+ #import "ENRMAccessibilityLabels.h"
3
4
  #import "HTMLGenerator.h"
4
5
  #import "LinkTapUtils.h"
5
6
  #import "MarkdownASTNode.h"
@@ -12,6 +13,8 @@
12
13
  #if TARGET_OS_OSX
13
14
  #import "ENRMMenuAction.h"
14
15
  #import "ENRMTableGridView.h"
16
+ #else
17
+ #import "ENRMTableIOSGridView.h"
15
18
  #endif
16
19
 
17
20
  @interface TableCellData : NSObject
@@ -26,7 +29,7 @@
26
29
  @end
27
30
 
28
31
  #if !TARGET_OS_OSX
29
- @interface TableContainerView () <UITextViewDelegate, UIContextMenuInteractionDelegate>
32
+ @interface TableContainerView () <UIContextMenuInteractionDelegate>
30
33
  @end
31
34
  #else
32
35
  @interface TableContainerView ()
@@ -46,6 +49,8 @@
46
49
  CGFloat _borderWidth;
47
50
 
48
51
  NSString *_cachedMarkdown;
52
+
53
+ NSArray *_cachedAccessibilityElements;
49
54
  }
50
55
 
51
56
  - (instancetype)initWithConfig:(StyleConfig *)config
@@ -72,6 +77,8 @@
72
77
  #if !TARGET_OS_OSX
73
78
  _scrollView.bounces = YES;
74
79
  _scrollView.alwaysBounceHorizontal = NO;
80
+ _scrollView.isAccessibilityElement = NO;
81
+ _scrollView.accessibilityElementsHidden = YES;
75
82
  #endif
76
83
  [self addSubview:_scrollView];
77
84
 
@@ -87,15 +94,26 @@
87
94
  if (!strongSelf)
88
95
  return nil;
89
96
  NSMenu *menu = [[NSMenu alloc] initWithTitle:@""];
90
- [menu addItem:ENRMCreateMenuItem(NSLocalizedString(@"Copy", nil), ^{ [strongSelf copyTableToPasteboard]; })];
91
- [menu addItem:ENRMCreateMenuItem(NSLocalizedString(@"Copy as Markdown", nil),
92
- ^{ [strongSelf copyMarkdownToPasteboard]; })];
97
+ [menu addItem:ENRMCreateMenuItem(strongSelf.copyLabel, ^{ [strongSelf copyTableToPasteboard]; })];
98
+ [menu addItem:ENRMCreateMenuItem(strongSelf.copyAsMarkdownLabel, ^{ [strongSelf copyMarkdownToPasteboard]; })];
93
99
  return menu;
94
100
  };
95
101
  _gridContainer = gridView;
96
102
  [(NSScrollView *)_scrollView setDocumentView:_gridContainer];
97
103
  #else
98
- _gridContainer = [[RCTUIView alloc] init];
104
+ ENRMTableIOSGridView *iosGridView = [[ENRMTableIOSGridView alloc] initWithFrame:CGRectZero];
105
+ __weak TableContainerView *weakSelf = self;
106
+ iosGridView.onLinkTap = ^(NSString *url) {
107
+ TableContainerView *strongSelf = weakSelf;
108
+ if (strongSelf && strongSelf.onLinkPress)
109
+ strongSelf.onLinkPress(url);
110
+ };
111
+ iosGridView.onLinkLongTap = ^(NSString *url) {
112
+ TableContainerView *strongSelf = weakSelf;
113
+ if (strongSelf && strongSelf.onLinkLongPress)
114
+ strongSelf.onLinkLongPress(url);
115
+ };
116
+ _gridContainer = iosGridView;
99
117
  [_scrollView addSubview:_gridContainer];
100
118
  UIContextMenuInteraction *contextMenu = [[UIContextMenuInteraction alloc] initWithDelegate:self];
101
119
  [_gridContainer addInteraction:contextMenu];
@@ -181,24 +199,7 @@
181
199
  if (self.rowCount <= previousRowCount) {
182
200
  return;
183
201
  }
184
-
185
- NSArray<RCTUIView *> *subviews = _gridContainer.subviews;
186
- NSUInteger childCount = subviews.count;
187
- if (childCount == 0 || self.rowCount == 0) {
188
- return;
189
- }
190
-
191
- NSUInteger colCount = childCount / self.rowCount;
192
- if (colCount == 0) {
193
- return;
194
- }
195
-
196
- NSUInteger firstNewCellIndex = previousRowCount * colCount;
197
- for (NSUInteger i = firstNewCellIndex; i < childCount; i++) {
198
- RCTUIView *cellView = subviews[i];
199
- cellView.alpha = 0.0;
200
- [UIView animateWithDuration:duration animations:^{ cellView.alpha = 1.0; }];
201
- }
202
+ [(ENRMTableIOSGridView *)_gridContainer fadeInRowsFrom:previousRowCount duration:duration];
202
203
  }
203
204
  #else
204
205
  - (void)animateNewRowsFromPreviousCount:(NSUInteger)previousRowCount duration:(NSTimeInterval)duration
@@ -246,6 +247,7 @@
246
247
 
247
248
  _rows = [allRows copy];
248
249
  _cachedMarkdown = [self buildMarkdownFromRows];
250
+ _cachedAccessibilityElements = nil;
249
251
  [self computeLayout];
250
252
  [self renderGrid];
251
253
  }
@@ -319,6 +321,16 @@
319
321
  return (bodyRowIndex % 2 == 0) ? self.config.tableRowEvenBackgroundColor : self.config.tableRowOddBackgroundColor;
320
322
  }
321
323
 
324
+ - (NSArray<NSAttributedString *> *)attributedTextsForRow:(NSArray<TableCellData *> *)rowCells
325
+ {
326
+ NSMutableArray<NSAttributedString *> *cellTexts = [NSMutableArray arrayWithCapacity:_colCount];
327
+ for (NSUInteger columnIndex = 0; columnIndex < _colCount; columnIndex++) {
328
+ NSAttributedString *text = (columnIndex < rowCells.count) ? rowCells[columnIndex].attributedText : nil;
329
+ [cellTexts addObject:text ?: [[NSAttributedString alloc] init]];
330
+ }
331
+ return [cellTexts copy];
332
+ }
333
+
322
334
  #if TARGET_OS_OSX
323
335
  - (void)renderGridMacOS
324
336
  {
@@ -351,16 +363,6 @@
351
363
  cornerRadius:self.config.tableBorderRadius];
352
364
  }
353
365
 
354
- - (NSArray<NSAttributedString *> *)attributedTextsForRow:(NSArray<TableCellData *> *)rowCells
355
- {
356
- NSMutableArray<NSAttributedString *> *cellTexts = [NSMutableArray arrayWithCapacity:_colCount];
357
- for (NSUInteger columnIndex = 0; columnIndex < _colCount; columnIndex++) {
358
- NSAttributedString *text = (columnIndex < rowCells.count) ? rowCells[columnIndex].attributedText : nil;
359
- [cellTexts addObject:text ?: [[NSAttributedString alloc] init]];
360
- }
361
- return [cellTexts copy];
362
- }
363
-
364
366
  #else
365
367
 
366
368
  - (void)renderGridIOS
@@ -369,108 +371,35 @@
369
371
  _gridContainer.layer.cornerRadius = self.config.tableBorderRadius;
370
372
  _gridContainer.layer.masksToBounds = YES;
371
373
 
372
- CGFloat yOffset = 0;
373
374
  NSUInteger bodyRowIndex = 0;
375
+ NSMutableArray<ENRMTableIOSRowData *> *rowDataArray = [NSMutableArray arrayWithCapacity:_rows.count];
374
376
 
375
- for (NSUInteger r = 0; r < _rows.count; r++) {
376
- NSArray<TableCellData *> *row = _rows[r];
377
- CGFloat rowHeight = [_rowHeights[r] doubleValue];
378
- BOOL isHeaderRow = (row.count > 0 && row.firstObject.isHeader);
377
+ for (NSArray<TableCellData *> *rowCells in _rows) {
378
+ BOOL isHeaderRow = (rowCells.count > 0 && rowCells.firstObject.isHeader);
379
379
 
380
- [self renderRow:row atY:yOffset height:rowHeight isHeader:isHeaderRow bodyIndex:bodyRowIndex];
380
+ ENRMTableIOSRowData *rowData = [[ENRMTableIOSRowData alloc] init];
381
+ rowData.backgroundColor = [self backgroundColorForRowIsHeader:isHeaderRow bodyRowIndex:bodyRowIndex];
382
+ rowData.cellTexts = [self attributedTextsForRow:rowCells];
383
+ [rowDataArray addObject:rowData];
381
384
 
382
- if (!isHeaderRow)
385
+ if (!isHeaderRow) {
383
386
  bodyRowIndex++;
384
- yOffset += rowHeight;
385
- }
386
- }
387
- #endif
388
-
389
- #if !TARGET_OS_OSX
390
- - (void)renderRow:(NSArray<TableCellData *> *)row
391
- atY:(CGFloat)yOffset
392
- height:(CGFloat)height
393
- isHeader:(BOOL)isHeader
394
- bodyIndex:(NSUInteger)bodyIndex
395
- {
396
- CGFloat xOffset = 0;
397
- RCTUIColor *rowBackground = [self backgroundColorForRowIsHeader:isHeader bodyRowIndex:bodyIndex];
398
-
399
- for (NSUInteger column = 0; column < _colCount; column++) {
400
- CGFloat columnWidth = [_colWidths[column] doubleValue];
401
- CGRect cellFrame = CGRectMake(xOffset, yOffset, columnWidth + _borderWidth, height + _borderWidth);
402
-
403
- RCTUIView *cellBackground = [[RCTUIView alloc] initWithFrame:cellFrame];
404
- cellBackground.backgroundColor = rowBackground;
405
- cellBackground.layer.borderColor = self.config.tableBorderColor.CGColor;
406
- cellBackground.layer.borderWidth = _borderWidth;
407
- [_gridContainer addSubview:cellBackground];
408
-
409
- if (column < row.count) {
410
- [self addTextToCell:cellBackground data:row[column] width:columnWidth height:height];
411
387
  }
412
- xOffset += columnWidth;
413
388
  }
414
- }
415
-
416
- - (void)addTextToCell:(RCTUIView *)container data:(TableCellData *)data width:(CGFloat)width height:(CGFloat)height
417
- {
418
- const CGFloat horizontalPadding = self.config.tableCellPaddingHorizontal;
419
- const CGFloat verticalPadding = self.config.tableCellPaddingVertical;
420
- CGRect contentFrame =
421
- CGRectMake(horizontalPadding, verticalPadding, width - (horizontalPadding * 2), height - (verticalPadding * 2));
422
- UITextView *cellTextView = [self createCellTextView];
423
- cellTextView.frame = contentFrame;
424
- cellTextView.attributedText = data.attributedText;
425
- [container addSubview:cellTextView];
426
- }
427
389
 
428
- - (UITextView *)createCellTextView
429
- {
430
- UITextView *textView = [[UITextView alloc] init];
431
- textView.editable = NO;
432
- textView.scrollEnabled = NO;
433
- textView.selectable = NO;
434
- textView.backgroundColor = [RCTUIColor clearColor];
435
- textView.textContainerInset = UIEdgeInsetsZero;
436
- textView.accessibilityElementsHidden = YES;
437
- textView.linkTextAttributes = @{};
438
- textView.delegate = self;
439
-
440
- UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
441
- action:@selector(cellTextTapped:)];
442
- [textView addGestureRecognizer:tapRecognizer];
443
- textView.textContainer.lineFragmentPadding = 0;
444
- return textView;
390
+ ENRMTableIOSGridView *gridView = (ENRMTableIOSGridView *)_gridContainer;
391
+ [gridView updateWithRows:[rowDataArray copy]
392
+ columnWidths:_colWidths
393
+ rowHeights:_rowHeights
394
+ borderColor:self.config.tableBorderColor
395
+ borderWidth:_borderWidth
396
+ horizontalCellPadding:self.config.tableCellPaddingHorizontal
397
+ verticalCellPadding:self.config.tableCellPaddingVertical
398
+ cornerRadius:self.config.tableBorderRadius];
445
399
  }
446
400
  #endif
447
401
 
448
402
  #if !TARGET_OS_OSX
449
- - (void)cellTextTapped:(UITapGestureRecognizer *)recognizer
450
- {
451
- UITextView *textView = (UITextView *)recognizer.view;
452
- NSString *url = linkURLAtTapLocation(textView, recognizer);
453
- if (url && self.onLinkPress)
454
- self.onLinkPress(url);
455
- }
456
-
457
- - (BOOL)textView:(UITextView *)textView
458
- shouldInteractWithURL:(NSURL *)URL
459
- inRange:(NSRange)range
460
- interaction:(UITextItemInteraction)interaction
461
- {
462
- if (interaction != UITextItemInteractionPresentActions)
463
- return YES;
464
-
465
- NSString *urlString = linkURLAtRange(textView, range);
466
- if (!urlString || self.enableLinkPreview)
467
- return YES;
468
-
469
- if (self.onLinkLongPress)
470
- self.onLinkLongPress(urlString);
471
- return NO;
472
- }
473
-
474
403
  - (UIContextMenuConfiguration *)contextMenuInteraction:(UIContextMenuInteraction *)interaction
475
404
  configurationForMenuAtLocation:(CGPoint)location
476
405
  {
@@ -479,13 +408,13 @@
479
408
  previewProvider:nil
480
409
  actionProvider:^UIMenu *(NSArray<UIMenuElement *> *suggestedActions) {
481
410
  UIAction *copyMarkdown =
482
- [UIAction actionWithTitle:@"Copy as Markdown"
411
+ [UIAction actionWithTitle:self.copyAsMarkdownLabel
483
412
  image:[RCTUIImage systemImageNamed:@"doc.text"]
484
413
  identifier:nil
485
414
  handler:^(__kindof UIAction *action) { [self copyMarkdownToPasteboard]; }];
486
415
 
487
416
  UIAction *copyPlainText =
488
- [UIAction actionWithTitle:@"Copy"
417
+ [UIAction actionWithTitle:self.copyLabel
489
418
  image:[RCTUIImage systemImageNamed:@"doc.on.doc"]
490
419
  identifier:nil
491
420
  handler:^(__kindof UIAction *action) { [self copyTableToPasteboard]; }];
@@ -643,10 +572,20 @@
643
572
  return NO;
644
573
  }
645
574
 
575
+ - (void)setAccessibilityLabels:(ENRMAccessibilityLabels *)labels
576
+ {
577
+ if (_accessibilityLabels == labels)
578
+ return;
579
+ _accessibilityLabels = labels;
580
+ _cachedAccessibilityElements = nil;
581
+ }
582
+
646
583
  - (NSArray *)accessibilityElements
647
584
  {
648
585
  if (_rows.count == 0)
649
586
  return nil;
587
+ if (_cachedAccessibilityElements != nil)
588
+ return _cachedAccessibilityElements;
650
589
 
651
590
  NSMutableArray *elements = [NSMutableArray array];
652
591
  CGFloat yOffset = 0;
@@ -664,8 +603,12 @@
664
603
  if (cellTexts.count > 0) {
665
604
  #if !TARGET_OS_OSX
666
605
  UIAccessibilityElement *element = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self];
667
- element.accessibilityLabel = [NSString
668
- stringWithFormat:@"Row %lu: %@", (unsigned long)(rowIndex + 1), [cellTexts componentsJoinedByString:@", "]];
606
+ NSString *withN = [_accessibilityLabels.tableRow
607
+ stringByReplacingOccurrencesOfString:@"{n}"
608
+ withString:[NSString stringWithFormat:@"%lu", (unsigned long)(rowIndex + 1)]];
609
+ element.accessibilityLabel =
610
+ [withN stringByReplacingOccurrencesOfString:@"{content}"
611
+ withString:[cellTexts componentsJoinedByString:@", "]];
669
612
  element.accessibilityFrameInContainerSpace = CGRectMake(0, yOffset, _totalTableWidth, rowHeight);
670
613
  element.accessibilityTraits =
671
614
  row.firstObject.isHeader ? UIAccessibilityTraitHeader : UIAccessibilityTraitStaticText;
@@ -680,7 +623,8 @@
680
623
  }
681
624
  yOffset += rowHeight;
682
625
  }
626
+ _cachedAccessibilityElements = elements;
683
627
  return elements;
684
628
  }
685
629
 
686
- @end
630
+ @end
@@ -230,6 +230,13 @@ export interface ContextMenuItemConfig {
230
230
  export interface SelectionMenuConfig {
231
231
  copyAsMarkdown: boolean;
232
232
  copyImageUrl: boolean;
233
+ copyLabel: string;
234
+ copyAsMarkdownLabel: string;
235
+ copyImageUrlLabel: string;
236
+ copyImageUrlsLabel: string;
237
+ // Precomputed plural templates indexed by image count (0..100). Empty when no
238
+ // pluralLabels are set; native then uses copyImageUrlLabel/copyImageUrlsLabel.
239
+ copyImageUrlPluralTemplates: ReadonlyArray<string>;
233
240
  }
234
241
 
235
242
  export interface OnContextMenuItemPressEvent {
@@ -239,6 +246,35 @@ export interface OnContextMenuItemPressEvent {
239
246
  selectionEnd: CodegenTypes.Int32;
240
247
  }
241
248
 
249
+ export interface AccessibilityLabelsListProps {
250
+ bulletPoint: string;
251
+ nestedBulletPoint: string;
252
+ orderedItem: string;
253
+ nestedOrderedItem: string;
254
+ }
255
+ export interface AccessibilityLabelsBlockquoteProps {
256
+ quote: string;
257
+ nestedQuote: string;
258
+ }
259
+ export interface AccessibilityLabelsTableProps {
260
+ row: string;
261
+ }
262
+ export interface AccessibilityLabelsMathProps {
263
+ equation: string;
264
+ }
265
+ export interface AccessibilityLabelsRotorProps {
266
+ headings: string;
267
+ links: string;
268
+ images: string;
269
+ }
270
+ export interface AccessibilityLabelsProps {
271
+ list: Readonly<AccessibilityLabelsListProps>;
272
+ blockquote: Readonly<AccessibilityLabelsBlockquoteProps>;
273
+ table: Readonly<AccessibilityLabelsTableProps>;
274
+ math: Readonly<AccessibilityLabelsMathProps>;
275
+ rotor: Readonly<AccessibilityLabelsRotorProps>;
276
+ }
277
+
242
278
  /**
243
279
  * MD4C parser flags configuration.
244
280
  * Controls how the markdown parser interprets certain syntax.
@@ -394,6 +430,7 @@ export interface NativeProps extends ViewProps {
394
430
  * Built-in items to show in the text selection context menu.
395
431
  */
396
432
  selectionMenuConfig: Readonly<SelectionMenuConfig>;
433
+ accessibilityLabels: Readonly<AccessibilityLabelsProps>;
397
434
  /**
398
435
  * Fired when a custom context menu item is pressed.
399
436
  * Receives the item label, the currently selected text, and the selection range.