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
@@ -9,14 +9,84 @@ NS_ASSUME_NONNULL_BEGIN
9
9
  typedef struct {
10
10
  BOOL copyAsMarkdown;
11
11
  BOOL copyImageURL;
12
+ // The owner must keep these strings alive for the duration of the call (the
13
+ // view holds them in strong ivars).
14
+ __unsafe_unretained NSString *_Nullable copyLabel;
15
+ __unsafe_unretained NSString *_Nullable copyAsMarkdownLabel;
16
+ __unsafe_unretained NSString *_Nullable copyImageUrlLabel;
17
+ __unsafe_unretained NSString *_Nullable copyImageUrlsLabel;
18
+ __unsafe_unretained NSArray<NSString *> *_Nullable copyImageUrlPluralTemplates;
12
19
  } ENRMSelectionMenuConfig;
13
20
 
21
+ /// Resolves the "Copy Image URL(s)" menu title for a given image count.
22
+ /// Uses the precomputed plural templates (indexed by count, 0..100) when present;
23
+ /// counts > 100 use the "other" form (copyImageUrlsLabel). Otherwise the
24
+ /// singular/`{count}` templates. All labels are resolved JS-side.
25
+ static inline NSString *ENRMResolveImageURLsTitle(ENRMSelectionMenuConfig config, NSUInteger count)
26
+ {
27
+ NSArray<NSString *> *templates = config.copyImageUrlPluralTemplates;
28
+ NSString *titleTemplate;
29
+ if (count < templates.count) {
30
+ titleTemplate = templates[count];
31
+ } else if (count == 1) {
32
+ titleTemplate = config.copyImageUrlLabel;
33
+ } else {
34
+ titleTemplate = config.copyImageUrlsLabel;
35
+ }
36
+ return [titleTemplate stringByReplacingOccurrencesOfString:@"{count}" withString:[@(count) stringValue]];
37
+ }
38
+
14
39
  #ifdef __cplusplus
40
+
41
+ /// Strong owners for the label strings in ENRMSelectionMenuConfig.
42
+ struct ENRMSelectionMenuLabels {
43
+ NSString *copyLabel = nil;
44
+ NSString *copyAsMarkdownLabel = nil;
45
+ NSString *copyImageUrlLabel = nil;
46
+ NSString *copyImageUrlsLabel = nil;
47
+ NSArray<NSString *> *copyImageUrlPluralTemplates = nil;
48
+ };
49
+
50
+ /// Converts a codegen SelectionMenuConfig struct into strong NSString labels.
51
+ template <typename T> static inline ENRMSelectionMenuLabels ENRMParseSelectionMenuLabels(const T &src)
52
+ {
53
+ NSMutableArray<NSString *> *templates = [NSMutableArray array];
54
+ for (const auto &s : src.copyImageUrlPluralTemplates) {
55
+ [templates addObject:[[NSString alloc] initWithUTF8String:s.c_str()]];
56
+ }
57
+ return {
58
+ [[NSString alloc] initWithUTF8String:src.copyLabel.c_str()],
59
+ [[NSString alloc] initWithUTF8String:src.copyAsMarkdownLabel.c_str()],
60
+ [[NSString alloc] initWithUTF8String:src.copyImageUrlLabel.c_str()],
61
+ [[NSString alloc] initWithUTF8String:src.copyImageUrlsLabel.c_str()],
62
+ templates,
63
+ };
64
+ }
65
+
66
+ /// Builds an ENRMSelectionMenuConfig from pre-parsed labels and boolean flags.
67
+ static inline ENRMSelectionMenuConfig ENRMBuildSelectionMenuConfig(const ENRMSelectionMenuLabels &labels,
68
+ bool copyAsMarkdown, bool copyImageUrl)
69
+ {
70
+ return (ENRMSelectionMenuConfig){
71
+ .copyAsMarkdown = copyAsMarkdown,
72
+ .copyImageURL = copyImageUrl,
73
+ .copyLabel = labels.copyLabel,
74
+ .copyAsMarkdownLabel = labels.copyAsMarkdownLabel,
75
+ .copyImageUrlLabel = labels.copyImageUrlLabel,
76
+ .copyImageUrlsLabel = labels.copyImageUrlsLabel,
77
+ .copyImageUrlPluralTemplates = labels.copyImageUrlPluralTemplates,
78
+ };
79
+ }
80
+
15
81
  extern "C" {
16
82
  #endif
17
83
 
18
84
  #if !TARGET_OS_OSX
19
85
  // TODO: Remove API_AVAILABLE(ios(16.0)) guard when the minimum iOS deployment target in RN is bumped to 16.
86
+ // TODO: selectionMenuConfig labels do NOT reach the system "Save to Camera Roll /
87
+ // Copy" sheet shown when long-pressing an NSTextAttachment image — UIKit owns
88
+ // that sheet end-to-end. Hooking UIContextMenuInteraction on image regions and
89
+ // returning a custom UIContextMenuConfiguration would let us relabel those items.
20
90
  UIMenu *buildEditMenuForSelection(NSAttributedString *attributedText, NSRange range, NSString *_Nullable cachedMarkdown,
21
91
  StyleConfig *styleConfig, NSArray<UIMenuElement *> *suggestedActions,
22
92
  NSArray<UIAction *> *_Nullable customActions,
@@ -10,9 +10,10 @@ static NSString *const kActionIdentifierCopy = @"com.swmansion.enriched.markdown
10
10
  static NSString *const kActionIdentifierCopyMarkdown = @"com.swmansion.enriched.markdown.copyMarkdown";
11
11
  static NSString *const kActionIdentifierCopyImageURL = @"com.swmansion.enriched.markdown.copyImageURL";
12
12
 
13
- static UIAction *createCopyAction(NSAttributedString *selectedText, NSString *markdown, StyleConfig *styleConfig)
13
+ static UIAction *createCopyAction(NSAttributedString *selectedText, NSString *markdown, StyleConfig *styleConfig,
14
+ NSString *copyLabel)
14
15
  {
15
- return [UIAction actionWithTitle:@"Copy"
16
+ return [UIAction actionWithTitle:copyLabel
16
17
  image:[RCTUIImage systemImageNamed:@"doc.on.doc"]
17
18
  identifier:kActionIdentifierCopy
18
19
  handler:^(__kindof UIAction *action) {
@@ -20,26 +21,25 @@ static UIAction *createCopyAction(NSAttributedString *selectedText, NSString *ma
20
21
  }];
21
22
  }
22
23
 
23
- static UIAction *_Nullable createCopyMarkdownAction(NSString *markdown)
24
+ static UIAction *_Nullable createCopyMarkdownAction(NSString *markdown, NSString *copyAsMarkdownLabel)
24
25
  {
25
26
  if (markdown.length == 0)
26
27
  return nil;
27
28
 
28
- return [UIAction actionWithTitle:@"Copy as Markdown"
29
+ return [UIAction actionWithTitle:copyAsMarkdownLabel
29
30
  image:[RCTUIImage systemImageNamed:@"doc.text"]
30
31
  identifier:kActionIdentifierCopyMarkdown
31
32
  handler:^(__kindof UIAction *action) { copyStringToPasteboard(markdown); }];
32
33
  }
33
34
 
34
- static UIAction *_Nullable createCopyImageURLAction(NSArray<NSString *> *imageURLs)
35
+ static UIAction *_Nullable createCopyImageURLAction(NSArray<NSString *> *imageURLs,
36
+ ENRMSelectionMenuConfig selectionMenuConfig)
35
37
  {
36
38
  if (imageURLs.count == 0)
37
39
  return nil;
38
40
 
39
41
  NSString *urlsToCopy = [imageURLs componentsJoinedByString:@"\n"];
40
- NSString *title = (imageURLs.count == 1)
41
- ? @"Copy Image URL"
42
- : [NSString stringWithFormat:@"Copy %lu Image URLs", (unsigned long)imageURLs.count];
42
+ NSString *title = ENRMResolveImageURLsTitle(selectionMenuConfig, imageURLs.count);
43
43
 
44
44
  return [UIAction actionWithTitle:title
45
45
  image:[RCTUIImage systemImageNamed:@"link"]
@@ -80,9 +80,12 @@ UIMenu *buildEditMenuForSelection(NSAttributedString *attributedText, NSRange ra
80
80
  NSString *markdown = markdownForRange(attributedText, range, cachedMarkdown);
81
81
  NSArray<NSString *> *imageURLs = imageURLsInRange(attributedText, range);
82
82
 
83
- UIAction *copyAction = createCopyAction(selectedText, markdown, styleConfig);
84
- UIAction *copyMarkdownAction = selectionMenuConfig.copyAsMarkdown ? createCopyMarkdownAction(markdown) : nil;
85
- UIAction *copyImageURLAction = selectionMenuConfig.copyImageURL ? createCopyImageURLAction(imageURLs) : nil;
83
+ UIAction *copyAction = createCopyAction(selectedText, markdown, styleConfig, selectionMenuConfig.copyLabel);
84
+ UIAction *copyMarkdownAction = selectionMenuConfig.copyAsMarkdown
85
+ ? createCopyMarkdownAction(markdown, selectionMenuConfig.copyAsMarkdownLabel)
86
+ : nil;
87
+ UIAction *copyImageURLAction =
88
+ selectionMenuConfig.copyImageURL ? createCopyImageURLAction(imageURLs, selectionMenuConfig) : nil;
86
89
 
87
90
  NSMutableArray<UIMenuElement *> *result = [NSMutableArray array];
88
91
  BOOL foundStandardEditMenu = NO;
@@ -6,8 +6,45 @@ NS_ASSUME_NONNULL_BEGIN
6
6
  static NSString *const CodeBlockAttributeName = @"CodeBlock";
7
7
 
8
8
  /**
9
- * Checks if the last element in the attributed string is a code block.
10
- * Used to compensate for iOS text APIs not measuring/drawing trailing newlines with custom line heights.
9
+ * Returns YES when the last semantic block in the storage is a code block, ignoring any
10
+ * trailing newline spacers (e.g. codeBlockMarginBottom) that may follow it.
11
+ *
12
+ * Detection is based on the last non-newline character: if it sits inside a CodeBlock
13
+ * attribute run, the answer is YES regardless of what newlines come after.
14
+ *
15
+ * Use this in render-time cleanup paths (e.g. removeTrailingSpacing) that need to know
16
+ * "is the document ending with a code block?" before any trailing newlines have been trimmed.
17
+ * The stricter `isLastElementCodeBlock` would answer NO in that pre-trim state because
18
+ * the CodeBlock range hasn't yet been pushed to text.length.
19
+ */
20
+ static inline BOOL isLastBlockACodeBlock(NSAttributedString *text)
21
+ {
22
+ if (text.length == 0)
23
+ return NO;
24
+
25
+ NSRange lastContent = [text.string rangeOfCharacterFromSet:[[NSCharacterSet newlineCharacterSet] invertedSet]
26
+ options:NSBackwardsSearch];
27
+ if (lastContent.location == NSNotFound)
28
+ return NO;
29
+
30
+ NSNumber *isCodeBlock = [text attribute:CodeBlockAttributeName atIndex:lastContent.location effectiveRange:nil];
31
+ return isCodeBlock.boolValue;
32
+ }
33
+
34
+ /**
35
+ * Stricter sibling of `isLastBlockACodeBlock`: requires both that the last non-newline
36
+ * character is inside a CodeBlock run AND that the CodeBlock range extends literally to
37
+ * text.length (i.e. no characters at all live after the code block).
38
+ *
39
+ * Used in the iOS measurement path (and the analogous drawing-compensation check) to
40
+ * decide whether to add `codeBlockPadding` to make up for iOS not measuring/drawing
41
+ * trailing newlines that carry custom minimum/maximum line heights.
42
+ *
43
+ * After `removeTrailingSpacing` runs, the bottom padding spacer is preserved together
44
+ * with a single trailing newline outside the code block range, so this returns NO and
45
+ * the compensation is skipped — the bottom padding spacer is no longer the trailing
46
+ * paragraph terminator, so iOS lays its line fragment out as part of usedRect normally.
47
+ * The check still exists as a safety net for unexpected storage shapes.
11
48
  */
12
49
  static inline BOOL isLastElementCodeBlock(NSAttributedString *text)
13
50
  {
@@ -2,38 +2,40 @@
2
2
  #include <TargetConditionals.h>
3
3
 
4
4
  @class AccessibilityInfo;
5
+ @class ENRMAccessibilityLabels;
5
6
 
6
7
  NS_ASSUME_NONNULL_BEGIN
7
8
 
8
- /**
9
- * Builds accessibility elements from markdown content for assistive technologies.
10
- * On iOS this provides VoiceOver support; on macOS this is a no-op stub pending full NSAccessibility implementation.
11
- */
12
9
  @interface MarkdownAccessibilityElementBuilder : NSObject
13
10
 
14
11
  #if !TARGET_OS_OSX
15
- /**
16
- * Builds UIAccessibilityElement objects from markdown content for VoiceOver.
17
- */
18
12
  + (NSMutableArray<UIAccessibilityElement *> *)buildElementsForTextView:(UITextView *)textView
19
13
  info:(AccessibilityInfo *)info
14
+ labels:(ENRMAccessibilityLabels *)labels
20
15
  container:(id)container;
21
16
  + (NSArray<UIAccessibilityElement *> *)filterHeadingElements:(NSArray<UIAccessibilityElement *> *)elements;
22
17
  + (NSArray<UIAccessibilityElement *> *)filterLinkElements:(NSArray<UIAccessibilityElement *> *)elements;
23
18
  + (NSArray<UIAccessibilityElement *> *)filterImageElements:(NSArray<UIAccessibilityElement *> *)elements;
24
- + (UIAccessibilityCustomRotor *)createHeadingRotorWithElements:(NSArray<UIAccessibilityElement *> *)elements;
25
- + (UIAccessibilityCustomRotor *)createLinkRotorWithElements:(NSArray<UIAccessibilityElement *> *)elements;
26
- + (UIAccessibilityCustomRotor *)createImageRotorWithElements:(NSArray<UIAccessibilityElement *> *)elements;
27
- + (NSArray<UIAccessibilityCustomRotor *> *)buildRotorsFromElements:(NSArray<UIAccessibilityElement *> *)elements;
19
+ + (UIAccessibilityCustomRotor *)createHeadingRotorWithElements:(NSArray<UIAccessibilityElement *> *)elements
20
+ name:(NSString *)name;
21
+ + (UIAccessibilityCustomRotor *)createLinkRotorWithElements:(NSArray<UIAccessibilityElement *> *)elements
22
+ name:(NSString *)name;
23
+ + (UIAccessibilityCustomRotor *)createImageRotorWithElements:(NSArray<UIAccessibilityElement *> *)elements
24
+ name:(NSString *)name;
25
+ + (NSArray<UIAccessibilityCustomRotor *> *)buildRotorsFromElements:(NSArray<UIAccessibilityElement *> *)elements
26
+ labels:(ENRMAccessibilityLabels *)labels;
28
27
  #else
29
- + (NSMutableArray *)buildElementsForTextView:(id)textView info:(AccessibilityInfo *)info container:(id)container;
28
+ + (NSMutableArray *)buildElementsForTextView:(id)textView
29
+ info:(AccessibilityInfo *)info
30
+ labels:(ENRMAccessibilityLabels *)labels
31
+ container:(id)container;
30
32
  + (NSArray *)filterHeadingElements:(NSArray *)elements;
31
33
  + (NSArray *)filterLinkElements:(NSArray *)elements;
32
34
  + (NSArray *)filterImageElements:(NSArray *)elements;
33
- + (id _Nullable)createHeadingRotorWithElements:(NSArray *)elements;
34
- + (id _Nullable)createLinkRotorWithElements:(NSArray *)elements;
35
- + (id _Nullable)createImageRotorWithElements:(NSArray *)elements;
36
- + (NSArray *)buildRotorsFromElements:(NSArray *)elements;
35
+ + (id _Nullable)createHeadingRotorWithElements:(NSArray *)elements name:(NSString *)name;
36
+ + (id _Nullable)createLinkRotorWithElements:(NSArray *)elements name:(NSString *)name;
37
+ + (id _Nullable)createImageRotorWithElements:(NSArray *)elements name:(NSString *)name;
38
+ + (NSArray *)buildRotorsFromElements:(NSArray *)elements labels:(ENRMAccessibilityLabels *)labels;
37
39
  #endif
38
40
 
39
41
  @end
@@ -1,5 +1,7 @@
1
1
  #import "MarkdownAccessibilityElementBuilder.h"
2
2
  #import "AccessibilityInfo.h"
3
+ #import "BlockquoteBorder.h"
4
+ #import "ENRMAccessibilityLabels.h"
3
5
  #include <TargetConditionals.h>
4
6
 
5
7
  typedef NS_ENUM(NSInteger, ElementType) { ElementTypeText, ElementTypeLink, ElementTypeImage };
@@ -14,6 +16,7 @@ static const CGFloat kFocusRectPadding = 2.0;
14
16
 
15
17
  + (NSMutableArray<UIAccessibilityElement *> *)buildElementsForTextView:(UITextView *)textView
16
18
  info:(AccessibilityInfo *)info
19
+ labels:(ENRMAccessibilityLabels *)labels
17
20
  container:(id)container
18
21
  {
19
22
  NSString *fullString = textView.attributedText.string;
@@ -45,6 +48,7 @@ static const CGFloat kFocusRectPadding = 2.0;
45
48
  isLinked:NO
46
49
  heading:level
47
50
  listInfo:list
51
+ labels:labels
48
52
  view:textView
49
53
  container:container]];
50
54
  } else {
@@ -53,6 +57,7 @@ static const CGFloat kFocusRectPadding = 2.0;
53
57
  headingLevel:level
54
58
  listInfo:list
55
59
  specials:specials
60
+ labels:labels
56
61
  inTextView:textView
57
62
  container:container]];
58
63
  }
@@ -77,6 +82,7 @@ static const CGFloat kFocusRectPadding = 2.0;
77
82
  headingLevel:(NSInteger)headingLevel
78
83
  listInfo:(NSDictionary *)listInfo
79
84
  specials:(NSArray *)specials
85
+ labels:(ENRMAccessibilityLabels *)labels
80
86
  inTextView:(UITextView *)textView
81
87
  container:(id)container
82
88
  {
@@ -100,6 +106,7 @@ static const CGFloat kFocusRectPadding = 2.0;
100
106
  isLinked:NO
101
107
  heading:headingLevel
102
108
  listInfo:listInfo
109
+ labels:labels
103
110
  view:textView
104
111
  container:container]];
105
112
  }
@@ -113,6 +120,7 @@ static const CGFloat kFocusRectPadding = 2.0;
113
120
  isLinked:isImg ? [item[@"isLinked"] boolValue] : YES
114
121
  heading:0
115
122
  listInfo:listInfo
123
+ labels:labels
116
124
  view:textView
117
125
  container:container]];
118
126
  segmentStart = NSMaxRange(itemRange);
@@ -129,6 +137,7 @@ static const CGFloat kFocusRectPadding = 2.0;
129
137
  isLinked:NO
130
138
  heading:headingLevel
131
139
  listInfo:listInfo
140
+ labels:labels
132
141
  view:textView
133
142
  container:container]];
134
143
  }
@@ -144,11 +153,13 @@ static const CGFloat kFocusRectPadding = 2.0;
144
153
  isLinked:(BOOL)linked
145
154
  heading:(NSInteger)level
146
155
  listInfo:(NSDictionary *)listInfo
156
+ labels:(ENRMAccessibilityLabels *)labels
147
157
  view:(UITextView *)textView
148
158
  container:(id)container
149
159
  {
150
160
  UIAccessibilityElement *element = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:container];
151
- element.accessibilityLabel = (type == ElementTypeImage && text.length == 0) ? NSLocalizedString(@"Image", @"") : text;
161
+ if (text.length > 0)
162
+ element.accessibilityLabel = text;
152
163
 
153
164
  UIBezierPath *path = [self accessibilityPathForRange:range inTextView:textView];
154
165
  if (path) {
@@ -157,41 +168,58 @@ static const CGFloat kFocusRectPadding = 2.0;
157
168
  element.accessibilityFrameInContainerSpace = [self frameForRange:range inTextView:textView container:container];
158
169
  }
159
170
 
160
- NSMutableArray *values = [NSMutableArray array];
161
-
162
171
  if (type == ElementTypeImage) {
163
172
  element.accessibilityTraits =
164
173
  linked ? (UIAccessibilityTraitImage | UIAccessibilityTraitLink) : UIAccessibilityTraitImage;
174
+
165
175
  } else if (type == ElementTypeLink) {
166
176
  element.accessibilityTraits = UIAccessibilityTraitLink;
167
177
  } else if (level > 0) {
168
178
  element.accessibilityTraits = UIAccessibilityTraitHeader;
169
- [values addObject:[NSString stringWithFormat:NSLocalizedString(@"heading level %ld", @""), (long)level]];
170
- }
171
-
172
- if (element.accessibilityTraits & UIAccessibilityTraitLink) {
173
- element.accessibilityHint = NSLocalizedString(@"Tap to open link", @"");
174
179
  }
175
180
 
181
+ NSMutableArray<NSString *> *valueParts = [NSMutableArray array];
176
182
  if (listInfo && type != ElementTypeImage) {
177
- [values addObject:[self formatListAnnouncement:listInfo]];
183
+ [valueParts addObject:[self formatListAnnouncement:listInfo labels:labels]];
178
184
  }
179
-
180
- if (values.count > 0) {
181
- element.accessibilityValue = [values componentsJoinedByString:@", "];
185
+ NSString *blockquoteAnnouncement = [self blockquoteAnnouncementForRange:range textView:textView labels:labels];
186
+ if (blockquoteAnnouncement) {
187
+ [valueParts addObject:blockquoteAnnouncement];
188
+ }
189
+ if (valueParts.count > 0) {
190
+ element.accessibilityValue = [valueParts componentsJoinedByString:@", "];
182
191
  }
183
192
 
184
193
  return element;
185
194
  }
186
195
 
196
+ + (NSString *)blockquoteAnnouncementForRange:(NSRange)range
197
+ textView:(UITextView *)textView
198
+ labels:(ENRMAccessibilityLabels *)labels
199
+ {
200
+ if (textView.attributedText.length == 0 || range.location >= textView.attributedText.length)
201
+ return nil;
202
+ NSRange effective;
203
+ NSNumber *depth = [textView.attributedText attribute:BlockquoteDepthAttributeName
204
+ atIndex:range.location
205
+ effectiveRange:&effective];
206
+ if (depth == nil)
207
+ return nil;
208
+ return depth.integerValue >= 1 ? labels.nestedBlockquote : labels.blockquote;
209
+ }
210
+
187
211
  #pragma mark - Helpers
188
212
 
189
- + (NSString *)formatListAnnouncement:(NSDictionary *)info
213
+ + (NSString *)formatListAnnouncement:(NSDictionary *)info labels:(ENRMAccessibilityLabels *)labels
190
214
  {
191
- NSString *prefix = [info[@"depth"] integerValue] > 1 ? @"nested " : @"";
192
- return [info[@"isOrdered"] boolValue]
193
- ? [NSString stringWithFormat:@"%@list item %ld", prefix, (long)[info[@"position"] integerValue]]
194
- : [NSString stringWithFormat:@"%@bullet point", prefix];
215
+ BOOL nested = [info[@"depth"] integerValue] > 1;
216
+ if ([info[@"isOrdered"] boolValue]) {
217
+ long position = (long)[info[@"position"] integerValue];
218
+ NSString *template = nested ? labels.nestedOrderedItem : labels.orderedItem;
219
+ return [template stringByReplacingOccurrencesOfString:@"{n}"
220
+ withString:[NSString stringWithFormat:@"%ld", position]];
221
+ }
222
+ return nested ? labels.nestedBulletPoint : labels.bulletPoint;
195
223
  }
196
224
 
197
225
  + (NSRange)clampedRange:(NSRange)range forText:(NSString *)text
@@ -370,36 +398,37 @@ static const CGFloat kFocusRectPadding = 2.0;
370
398
  {
371
399
  return [self filterElements:elements withTrait:UIAccessibilityTraitImage];
372
400
  }
373
- + (UIAccessibilityCustomRotor *)createHeadingRotorWithElements:(NSArray *)elements
401
+ + (UIAccessibilityCustomRotor *)createHeadingRotorWithElements:(NSArray *)elements name:(NSString *)name
374
402
  {
375
- return [self createRotorWithName:NSLocalizedString(@"Headings", @"") elements:elements];
403
+ return [self createRotorWithName:name elements:elements];
376
404
  }
377
- + (UIAccessibilityCustomRotor *)createLinkRotorWithElements:(NSArray *)elements
405
+ + (UIAccessibilityCustomRotor *)createLinkRotorWithElements:(NSArray *)elements name:(NSString *)name
378
406
  {
379
- return [self createRotorWithName:NSLocalizedString(@"Links", @"") elements:elements];
407
+ return [self createRotorWithName:name elements:elements];
380
408
  }
381
- + (UIAccessibilityCustomRotor *)createImageRotorWithElements:(NSArray *)elements
409
+ + (UIAccessibilityCustomRotor *)createImageRotorWithElements:(NSArray *)elements name:(NSString *)name
382
410
  {
383
- return [self createRotorWithName:NSLocalizedString(@"Images", @"") elements:elements];
411
+ return [self createRotorWithName:name elements:elements];
384
412
  }
385
413
 
386
414
  + (NSArray<UIAccessibilityCustomRotor *> *)buildRotorsFromElements:(NSArray<UIAccessibilityElement *> *)elements
415
+ labels:(ENRMAccessibilityLabels *)labels
387
416
  {
388
417
  NSMutableArray<UIAccessibilityCustomRotor *> *rotors = [NSMutableArray array];
389
418
 
390
419
  NSArray<UIAccessibilityElement *> *headingElements = [self filterHeadingElements:elements];
391
420
  if (headingElements.count > 0) {
392
- [rotors addObject:[self createHeadingRotorWithElements:headingElements]];
421
+ [rotors addObject:[self createHeadingRotorWithElements:headingElements name:labels.rotorHeadings]];
393
422
  }
394
423
 
395
424
  NSArray<UIAccessibilityElement *> *linkElements = [self filterLinkElements:elements];
396
425
  if (linkElements.count > 0) {
397
- [rotors addObject:[self createLinkRotorWithElements:linkElements]];
426
+ [rotors addObject:[self createLinkRotorWithElements:linkElements name:labels.rotorLinks]];
398
427
  }
399
428
 
400
429
  NSArray<UIAccessibilityElement *> *imageElements = [self filterImageElements:elements];
401
430
  if (imageElements.count > 0) {
402
- [rotors addObject:[self createImageRotorWithElements:imageElements]];
431
+ [rotors addObject:[self createImageRotorWithElements:imageElements name:labels.rotorImages]];
403
432
  }
404
433
 
405
434
  return rotors;
@@ -412,7 +441,10 @@ static const CGFloat kFocusRectPadding = 2.0;
412
441
  // attributes, and exposing them via NSAccessibilityElement so VoiceOver can navigate the
413
442
  // rendered markdown content. The iOS implementation above can serve as a reference.
414
443
 
415
- + (NSMutableArray *)buildElementsForTextView:(id)textView info:(AccessibilityInfo *)info container:(id)container
444
+ + (NSMutableArray *)buildElementsForTextView:(id)textView
445
+ info:(AccessibilityInfo *)info
446
+ labels:(ENRMAccessibilityLabels *)labels
447
+ container:(id)container
416
448
  {
417
449
  return [NSMutableArray array];
418
450
  }
@@ -428,23 +460,23 @@ static const CGFloat kFocusRectPadding = 2.0;
428
460
  {
429
461
  return @[];
430
462
  }
431
- + (id)createHeadingRotorWithElements:(NSArray *)elements
463
+ + (id)createHeadingRotorWithElements:(NSArray *)elements name:(NSString *)name
432
464
  {
433
465
  return nil;
434
466
  }
435
- + (id)createLinkRotorWithElements:(NSArray *)elements
467
+ + (id)createLinkRotorWithElements:(NSArray *)elements name:(NSString *)name
436
468
  {
437
469
  return nil;
438
470
  }
439
- + (id)createImageRotorWithElements:(NSArray *)elements
471
+ + (id)createImageRotorWithElements:(NSArray *)elements name:(NSString *)name
440
472
  {
441
473
  return nil;
442
474
  }
443
- + (NSArray *)buildRotorsFromElements:(NSArray *)elements
475
+ + (NSArray *)buildRotorsFromElements:(NSArray *)elements labels:(ENRMAccessibilityLabels *)labels
444
476
  {
445
477
  return @[];
446
478
  }
447
479
 
448
480
  #endif
449
481
 
450
- @end
482
+ @end
@@ -1,7 +1,12 @@
1
1
  #import "ParagraphStyleUtils.h"
2
+ #import "ENRMFeatureFlags.h"
2
3
  #import "LastElementUtils.h"
3
4
  #import <React/RCTI18nUtil.h>
4
5
 
6
+ #if ENRICHED_MARKDOWN_MATH
7
+ #import "ENRMMathInlineAttachment.h"
8
+ #endif
9
+
5
10
  NSAttributedString *kNewlineAttributedString;
6
11
  static NSParagraphStyle *kBlockSpacerTemplate;
7
12
 
@@ -247,10 +252,24 @@ void applyLineHeight(NSMutableAttributedString *output, NSRange range, CGFloat l
247
252
  return;
248
253
  }
249
254
 
255
+ __block BOOL hasMath = NO;
256
+
257
+ #if ENRICHED_MARKDOWN_MATH
258
+ [output enumerateAttribute:NSAttachmentAttributeName
259
+ inRange:range
260
+ options:0
261
+ usingBlock:^(id value, __unused NSRange attrRange, BOOL *stop) {
262
+ if ([value isKindOfClass:[ENRMMathInlineAttachment class]]) {
263
+ hasMath = YES;
264
+ *stop = YES;
265
+ }
266
+ }];
267
+ #endif
268
+
250
269
  NSMutableParagraphStyle *style = getOrCreateParagraphStyle(output, range.location);
251
270
 
252
271
  style.minimumLineHeight = lineHeight;
253
- style.maximumLineHeight = lineHeight;
272
+ style.maximumLineHeight = hasMath ? 0 : lineHeight;
254
273
 
255
274
  [output addAttribute:NSParagraphStyleAttributeName value:style range:range];
256
275
  }
@@ -288,7 +307,7 @@ void applyBaselineOffset(NSMutableAttributedString *output, NSRange range)
288
307
  if (!font) {
289
308
  return;
290
309
  }
291
- maximumFontLineHeight = MAX(font.lineHeight, maximumFontLineHeight);
310
+ maximumFontLineHeight = MAX(UIFontLineHeight(font), maximumFontLineHeight);
292
311
  }];
293
312
 
294
313
  if (maximumFontLineHeight <= 0 || maximumLineHeight < maximumFontLineHeight) {
@@ -2,6 +2,8 @@
2
2
  #import "ENRMUIKit.h"
3
3
  #import "StyleConfig.h"
4
4
 
5
+ @class ENRMAccessibilityLabels;
6
+
5
7
  NS_ASSUME_NONNULL_BEGIN
6
8
 
7
9
  @interface ENRMMathContainerView : RCTUIView
@@ -14,6 +16,12 @@ NS_ASSUME_NONNULL_BEGIN
14
16
 
15
17
  @property (nonatomic, strong) StyleConfig *config;
16
18
  @property (nonatomic, copy, readonly) NSString *cachedLatex;
19
+ @property (nonatomic, strong, nullable) ENRMAccessibilityLabels *accessibilityLabels;
20
+
21
+ // Renamed getters avoid the Cocoa `copy` method family (which signals +1
22
+ // retained returns). Property names are unchanged so call sites stay the same.
23
+ @property (nonatomic, copy, nullable, getter=menuCopyLabel) NSString *copyLabel;
24
+ @property (nonatomic, copy, nullable, getter=menuCopyAsMarkdownLabel) NSString *copyAsMarkdownLabel;
17
25
 
18
26
  @end
19
27
 
@@ -1,4 +1,5 @@
1
1
  #import "ENRMMathContainerView.h"
2
+ #import "ENRMAccessibilityLabels.h"
2
3
  #import "ENRMFeatureFlags.h"
3
4
  #include <TargetConditionals.h>
4
5
 
@@ -119,13 +120,13 @@
119
120
  previewProvider:nil
120
121
  actionProvider:^UIMenu *(NSArray<UIMenuElement *> *suggestedActions) {
121
122
  UIAction *copyPlainText =
122
- [UIAction actionWithTitle:@"Copy"
123
+ [UIAction actionWithTitle:self.copyLabel
123
124
  image:[RCTUIImage systemImageNamed:@"doc.on.doc"]
124
125
  identifier:nil
125
126
  handler:^(__kindof UIAction *action) { [self copyLatexToPasteboard]; }];
126
127
 
127
128
  UIAction *copyMarkdown =
128
- [UIAction actionWithTitle:@"Copy as Markdown"
129
+ [UIAction actionWithTitle:self.copyAsMarkdownLabel
129
130
  image:[RCTUIImage systemImageNamed:@"doc.text"]
130
131
  identifier:nil
131
132
  handler:^(__kindof UIAction *action) { [self copyMarkdownToPasteboard]; }];
@@ -139,8 +140,8 @@
139
140
  - (NSMenu *)menuForEvent:(NSEvent *)event
140
141
  {
141
142
  NSMenu *menu = [[NSMenu alloc] initWithTitle:@""];
142
- [menu addItem:ENRMCreateMenuItem(NSLocalizedString(@"Copy", nil), ^{ [self copyLatexToPasteboard]; })];
143
- [menu addItem:ENRMCreateMenuItem(NSLocalizedString(@"Copy as Markdown", nil), ^{ [self copyMarkdownToPasteboard]; })];
143
+ [menu addItem:ENRMCreateMenuItem(self.copyLabel, ^{ [self copyLatexToPasteboard]; })];
144
+ [menu addItem:ENRMCreateMenuItem(self.copyAsMarkdownLabel, ^{ [self copyMarkdownToPasteboard]; })];
144
145
  return menu;
145
146
  }
146
147
  #endif
@@ -212,7 +213,8 @@
212
213
 
213
214
  - (NSString *)accessibilityLabel
214
215
  {
215
- return [NSString stringWithFormat:@"Math equation: %@", _cachedLatex];
216
+ return [_accessibilityLabels.mathEquation stringByReplacingOccurrencesOfString:@"{latex}"
217
+ withString:_cachedLatex ?: @""];
216
218
  }
217
219
 
218
220
  #if !TARGET_OS_OSX
@@ -0,0 +1,40 @@
1
+ #pragma once
2
+
3
+ #include <TargetConditionals.h>
4
+ #if !TARGET_OS_OSX
5
+
6
+ #import <UIKit/UIKit.h>
7
+
8
+ NS_ASSUME_NONNULL_BEGIN
9
+
10
+ typedef void (^ENRMTableIOSLinkBlock)(NSString *url);
11
+
12
+ @interface ENRMTableIOSRowData : NSObject
13
+ @property (nonatomic, strong) NSArray<NSAttributedString *> *cellTexts;
14
+ @property (nonatomic, strong) UIColor *backgroundColor;
15
+ @end
16
+
17
+ /// Draws the entire table grid in a single drawRect: pass, avoiding
18
+ /// per-cell UITextView allocation and the TextKit layout storms that
19
+ /// cause multi-second main-thread hangs on large tables.
20
+ @interface ENRMTableIOSGridView : UIView
21
+
22
+ @property (nonatomic, copy, nullable) ENRMTableIOSLinkBlock onLinkTap;
23
+ @property (nonatomic, copy, nullable) ENRMTableIOSLinkBlock onLinkLongTap;
24
+
25
+ - (void)updateWithRows:(NSArray<ENRMTableIOSRowData *> *)rows
26
+ columnWidths:(NSArray<NSNumber *> *)columnWidths
27
+ rowHeights:(NSArray<NSNumber *> *)rowHeights
28
+ borderColor:(UIColor *)borderColor
29
+ borderWidth:(CGFloat)borderWidth
30
+ horizontalCellPadding:(CGFloat)horizontalCellPadding
31
+ verticalCellPadding:(CGFloat)verticalCellPadding
32
+ cornerRadius:(CGFloat)cornerRadius;
33
+
34
+ - (void)fadeInRowsFrom:(NSUInteger)startRow duration:(NSTimeInterval)duration;
35
+
36
+ @end
37
+
38
+ NS_ASSUME_NONNULL_END
39
+
40
+ #endif // !TARGET_OS_OSX