@mathers101/react-native-render-rich-text 0.1.10 → 0.1.14

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 (327) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +140 -140
  3. package/ReactNativeRenderRichText.podspec +23 -23
  4. package/android/build.gradle +86 -86
  5. package/android/consumer-rules.pro +3 -3
  6. package/android/src/main/AndroidManifest.xml +2 -2
  7. package/android/src/main/java/com/renderrichtext/EnrichedTiptapTextLayoutManager.kt +24 -24
  8. package/android/src/main/java/com/renderrichtext/EnrichedTiptapTextPackage.kt +13 -13
  9. package/android/src/main/java/com/renderrichtext/MeasurementStore.kt +644 -644
  10. package/android/src/main/java/com/renderrichtext/accessibility/AccessibilityLabels.kt +17 -17
  11. package/android/src/main/java/com/renderrichtext/accessibility/AccessibleTiptapTextView.kt +112 -112
  12. package/android/src/main/java/com/renderrichtext/accessibility/TiptapAccessibilityHelper.kt +576 -576
  13. package/android/src/main/java/com/renderrichtext/events/CodeBlockPressEvent.kt +24 -24
  14. package/android/src/main/java/com/renderrichtext/events/ContextMenuItemPressEvent.kt +28 -28
  15. package/android/src/main/java/com/renderrichtext/events/CopyPressEvent.kt +24 -24
  16. package/android/src/main/java/com/renderrichtext/events/ImagePressEvent.kt +25 -25
  17. package/android/src/main/java/com/renderrichtext/events/LatexErrorEvent.kt +29 -29
  18. package/android/src/main/java/com/renderrichtext/events/LinkLongPressEvent.kt +23 -23
  19. package/android/src/main/java/com/renderrichtext/events/LinkPressEvent.kt +23 -23
  20. package/android/src/main/java/com/renderrichtext/events/MentionLongPressEvent.kt +25 -25
  21. package/android/src/main/java/com/renderrichtext/events/MentionPressEvent.kt +25 -25
  22. package/android/src/main/java/com/renderrichtext/events/TaskListItemPressEvent.kt +26 -26
  23. package/android/src/main/java/com/renderrichtext/math/LatexErrorReporter.kt +9 -9
  24. package/android/src/main/java/com/renderrichtext/parser/Parser.kt +59 -59
  25. package/android/src/main/java/com/renderrichtext/parser/TextLinkMatching.kt +35 -35
  26. package/android/src/main/java/com/renderrichtext/renderer/BaselineShiftRenderer.kt +45 -45
  27. package/android/src/main/java/com/renderrichtext/renderer/BlankLineRenderer.kt +39 -39
  28. package/android/src/main/java/com/renderrichtext/renderer/BlockquoteRenderer.kt +243 -243
  29. package/android/src/main/java/com/renderrichtext/renderer/BlockquoteTextRenderer.kt +56 -56
  30. package/android/src/main/java/com/renderrichtext/renderer/CodeBlockRenderer.kt +135 -135
  31. package/android/src/main/java/com/renderrichtext/renderer/CodeRenderer.kt +37 -37
  32. package/android/src/main/java/com/renderrichtext/renderer/EmphasisRenderer.kt +28 -28
  33. package/android/src/main/java/com/renderrichtext/renderer/HeadingRenderer.kt +81 -81
  34. package/android/src/main/java/com/renderrichtext/renderer/HighlightRenderer.kt +26 -26
  35. package/android/src/main/java/com/renderrichtext/renderer/LineBreakRenderer.kt +17 -17
  36. package/android/src/main/java/com/renderrichtext/renderer/LinkRenderer.kt +90 -90
  37. package/android/src/main/java/com/renderrichtext/renderer/ListContextManager.kt +109 -109
  38. package/android/src/main/java/com/renderrichtext/renderer/ListItemRenderer.kt +211 -211
  39. package/android/src/main/java/com/renderrichtext/renderer/ListRenderer.kt +133 -133
  40. package/android/src/main/java/com/renderrichtext/renderer/MentionRenderer.kt +85 -85
  41. package/android/src/main/java/com/renderrichtext/renderer/ParagraphRenderer.kt +91 -91
  42. package/android/src/main/java/com/renderrichtext/renderer/Renderer.kt +148 -148
  43. package/android/src/main/java/com/renderrichtext/renderer/SoftBreakRenderer.kt +17 -17
  44. package/android/src/main/java/com/renderrichtext/renderer/SpanStyleCache.kt +143 -143
  45. package/android/src/main/java/com/renderrichtext/renderer/StrikethroughRenderer.kt +28 -28
  46. package/android/src/main/java/com/renderrichtext/renderer/StrongRenderer.kt +28 -28
  47. package/android/src/main/java/com/renderrichtext/renderer/TextRenderer.kt +31 -31
  48. package/android/src/main/java/com/renderrichtext/renderer/ThematicBreakRenderer.kt +46 -46
  49. package/android/src/main/java/com/renderrichtext/renderer/UnderlineRenderer.kt +28 -28
  50. package/android/src/main/java/com/renderrichtext/spans/BaseListSpan.kt +164 -164
  51. package/android/src/main/java/com/renderrichtext/spans/BaselineShiftSpan.kt +27 -27
  52. package/android/src/main/java/com/renderrichtext/spans/BlockquoteSpan.kt +302 -302
  53. package/android/src/main/java/com/renderrichtext/spans/CodeBackgroundSpan.kt +205 -205
  54. package/android/src/main/java/com/renderrichtext/spans/CodeBlockSpan.kt +216 -216
  55. package/android/src/main/java/com/renderrichtext/spans/CodeSpan.kt +32 -32
  56. package/android/src/main/java/com/renderrichtext/spans/EmphasisSpan.kt +42 -42
  57. package/android/src/main/java/com/renderrichtext/spans/FadeInSpan.kt +29 -29
  58. package/android/src/main/java/com/renderrichtext/spans/HeadingSpan.kt +38 -38
  59. package/android/src/main/java/com/renderrichtext/spans/HighlightSpan.kt +80 -80
  60. package/android/src/main/java/com/renderrichtext/spans/ImageSpan.kt +481 -481
  61. package/android/src/main/java/com/renderrichtext/spans/InlineBackgroundGeometry.kt +42 -42
  62. package/android/src/main/java/com/renderrichtext/spans/LineHeightSpan.kt +39 -39
  63. package/android/src/main/java/com/renderrichtext/spans/LinkPillIconCache.kt +154 -154
  64. package/android/src/main/java/com/renderrichtext/spans/LinkPillSpan.kt +381 -381
  65. package/android/src/main/java/com/renderrichtext/spans/LinkSpan.kt +68 -68
  66. package/android/src/main/java/com/renderrichtext/spans/ListItemSpacingSpan.kt +33 -33
  67. package/android/src/main/java/com/renderrichtext/spans/ListMarkerAnchorSpan.kt +38 -38
  68. package/android/src/main/java/com/renderrichtext/spans/MarginBottomSpan.kt +76 -76
  69. package/android/src/main/java/com/renderrichtext/spans/MentionChipSpan.kt +132 -132
  70. package/android/src/main/java/com/renderrichtext/spans/MentionSpan.kt +46 -46
  71. package/android/src/main/java/com/renderrichtext/spans/OrderedListSpan.kt +127 -127
  72. package/android/src/main/java/com/renderrichtext/spans/SpoilerSpan.kt +33 -33
  73. package/android/src/main/java/com/renderrichtext/spans/StrikethroughSpan.kt +12 -12
  74. package/android/src/main/java/com/renderrichtext/spans/StrongSpan.kt +39 -39
  75. package/android/src/main/java/com/renderrichtext/spans/TaskListSpan.kt +143 -143
  76. package/android/src/main/java/com/renderrichtext/spans/TextSpan.kt +26 -26
  77. package/android/src/main/java/com/renderrichtext/spans/ThematicBreakSpan.kt +69 -69
  78. package/android/src/main/java/com/renderrichtext/spans/UnorderedListSpan.kt +96 -96
  79. package/android/src/main/java/com/renderrichtext/spoiler/SpoilerStubs.kt +46 -46
  80. package/android/src/main/java/com/renderrichtext/styles/BaseBlockStyle.kt +11 -11
  81. package/android/src/main/java/com/renderrichtext/styles/BlockquoteStyle.kt +83 -83
  82. package/android/src/main/java/com/renderrichtext/styles/CodeBlockStyle.kt +80 -80
  83. package/android/src/main/java/com/renderrichtext/styles/CodeStyle.kt +26 -26
  84. package/android/src/main/java/com/renderrichtext/styles/EmphasisStyle.kt +21 -21
  85. package/android/src/main/java/com/renderrichtext/styles/FactoidStyle.kt +21 -21
  86. package/android/src/main/java/com/renderrichtext/styles/HeadingStyle.kt +33 -33
  87. package/android/src/main/java/com/renderrichtext/styles/HighlightStyle.kt +19 -19
  88. package/android/src/main/java/com/renderrichtext/styles/ImageStyle.kt +29 -29
  89. package/android/src/main/java/com/renderrichtext/styles/InlineImageStyle.kt +17 -17
  90. package/android/src/main/java/com/renderrichtext/styles/InlineMathStyle.kt +17 -17
  91. package/android/src/main/java/com/renderrichtext/styles/LinkPillContent.kt +36 -36
  92. package/android/src/main/java/com/renderrichtext/styles/LinkPillStyle.kt +44 -44
  93. package/android/src/main/java/com/renderrichtext/styles/LinkStyle.kt +23 -23
  94. package/android/src/main/java/com/renderrichtext/styles/LinkVariantEntry.kt +34 -34
  95. package/android/src/main/java/com/renderrichtext/styles/ListStyle.kt +65 -65
  96. package/android/src/main/java/com/renderrichtext/styles/MathStyle.kt +29 -29
  97. package/android/src/main/java/com/renderrichtext/styles/MentionStyle.kt +19 -19
  98. package/android/src/main/java/com/renderrichtext/styles/ParagraphStyle.kt +33 -33
  99. package/android/src/main/java/com/renderrichtext/styles/SpoilerStyle.kt +27 -27
  100. package/android/src/main/java/com/renderrichtext/styles/StrikethroughStyle.kt +17 -17
  101. package/android/src/main/java/com/renderrichtext/styles/StrongStyle.kt +21 -21
  102. package/android/src/main/java/com/renderrichtext/styles/StyleConfig.kt +411 -411
  103. package/android/src/main/java/com/renderrichtext/styles/StyleParser.kt +92 -92
  104. package/android/src/main/java/com/renderrichtext/styles/SubscriptStyle.kt +16 -16
  105. package/android/src/main/java/com/renderrichtext/styles/SuperscriptStyle.kt +16 -16
  106. package/android/src/main/java/com/renderrichtext/styles/TableStyle.kt +75 -75
  107. package/android/src/main/java/com/renderrichtext/styles/TaskListStyle.kt +29 -29
  108. package/android/src/main/java/com/renderrichtext/styles/TextAlignment.kt +32 -32
  109. package/android/src/main/java/com/renderrichtext/styles/ThematicBreakStyle.kt +23 -23
  110. package/android/src/main/java/com/renderrichtext/styles/UnderlineStyle.kt +17 -17
  111. package/android/src/main/java/com/renderrichtext/styles/VideoStyle.kt +31 -31
  112. package/android/src/main/java/com/renderrichtext/utils/common/BorderStyleUtils.kt +86 -86
  113. package/android/src/main/java/com/renderrichtext/utils/common/BreakStrategyUtils.kt +32 -32
  114. package/android/src/main/java/com/renderrichtext/utils/common/CodeBlockHighlighter.kt +36 -36
  115. package/android/src/main/java/com/renderrichtext/utils/common/CodeBlockNode.kt +42 -42
  116. package/android/src/main/java/com/renderrichtext/utils/common/EllipsizeUtils.kt +27 -27
  117. package/android/src/main/java/com/renderrichtext/utils/common/EnrichedTiptapAncestorUtils.kt +10 -10
  118. package/android/src/main/java/com/renderrichtext/utils/common/FeatureFlags.kt +9 -9
  119. package/android/src/main/java/com/renderrichtext/utils/common/LinkRecognitionProps.kt +31 -31
  120. package/android/src/main/java/com/renderrichtext/utils/common/LinkRegexConfig.kt +56 -56
  121. package/android/src/main/java/com/renderrichtext/utils/common/MotionPreferences.kt +18 -18
  122. package/android/src/main/java/com/renderrichtext/utils/common/ReadableMapExtensions.kt +28 -28
  123. package/android/src/main/java/com/renderrichtext/utils/common/layout/RTLUtils.kt +6 -6
  124. package/android/src/main/java/com/renderrichtext/utils/common/serialization/TiptapASTSerializer.kt +368 -368
  125. package/android/src/main/java/com/renderrichtext/utils/text/ImageCache.kt +88 -88
  126. package/android/src/main/java/com/renderrichtext/utils/text/ImageDownloader.kt +203 -203
  127. package/android/src/main/java/com/renderrichtext/utils/text/LocalImageLoader.kt +167 -167
  128. package/android/src/main/java/com/renderrichtext/utils/text/TailFadeInAnimator.kt +84 -84
  129. package/android/src/main/java/com/renderrichtext/utils/text/conversion/HTMLGenerator.kt +1105 -1105
  130. package/android/src/main/java/com/renderrichtext/utils/text/conversion/TiptapExtractor.kt +423 -423
  131. package/android/src/main/java/com/renderrichtext/utils/text/extensions/ParagraphUtils.kt +9 -9
  132. package/android/src/main/java/com/renderrichtext/utils/text/extensions/SpannableExtensions.kt +9 -9
  133. package/android/src/main/java/com/renderrichtext/utils/text/extensions/TextPaintExtensions.kt +76 -76
  134. package/android/src/main/java/com/renderrichtext/utils/text/interaction/CheckboxTouchHelper.kt +61 -61
  135. package/android/src/main/java/com/renderrichtext/utils/text/interaction/TaskListTapUtils.kt +286 -286
  136. package/android/src/main/java/com/renderrichtext/utils/text/span/ApplyLineHeight.kt +39 -39
  137. package/android/src/main/java/com/renderrichtext/utils/text/span/BlockSpacing.kt +142 -142
  138. package/android/src/main/java/com/renderrichtext/utils/text/span/SpanFlags.kt +43 -43
  139. package/android/src/main/java/com/renderrichtext/utils/text/span/WidthAwareSpans.kt +30 -30
  140. package/android/src/main/java/com/renderrichtext/utils/text/view/CodeBlockPressHost.kt +15 -15
  141. package/android/src/main/java/com/renderrichtext/utils/text/view/ImagePressHost.kt +19 -19
  142. package/android/src/main/java/com/renderrichtext/utils/text/view/SelectionActionMode.kt +235 -235
  143. package/android/src/main/java/com/renderrichtext/utils/text/view/TextSelectionColors.kt +48 -48
  144. package/android/src/main/java/com/renderrichtext/utils/text/view/TextViewSetup.kt +119 -119
  145. package/android/src/main/java/com/renderrichtext/views/ContextMenuPopup.kt +217 -217
  146. package/android/src/main/java/com/renderrichtext/views/CopyGlyph.kt +101 -101
  147. package/android/src/main/java/com/renderrichtext/views/WrapGlyph.kt +40 -40
  148. package/android/src/main/jni/CMakeLists.txt +52 -52
  149. package/android/src/main/jni/react/renderer/components/RichTextSpec/ComponentDescriptors.h +30 -30
  150. package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextComponentDescriptor.h +29 -29
  151. package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextMeasurementManager.cpp +45 -45
  152. package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextMeasurementManager.h +21 -21
  153. package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextShadowNode.cpp +38 -38
  154. package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextShadowNode.h +53 -53
  155. package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextState.cpp +9 -9
  156. package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextState.h +24 -24
  157. package/android/src/main/jni/react/renderer/components/RichTextSpec/conversions.h +40 -40
  158. package/cpp/parser/TiptapParser.hpp +13 -13
  159. package/cpp/vendor/nlohmann/json.hpp +24765 -24765
  160. package/ios/EnrichedTiptapText.h +27 -27
  161. package/ios/attachments/ENRMImageAttachment.h +51 -51
  162. package/ios/attachments/ENRMImageAttachment.m +398 -398
  163. package/ios/attachments/ENRMLinkPillAttachment.h +47 -47
  164. package/ios/attachments/ENRMLinkPillAttachment.m +324 -324
  165. package/ios/attachments/ENRMLinkPillIconCache.h +28 -28
  166. package/ios/attachments/ENRMLinkPillIconCache.m +181 -181
  167. package/ios/attachments/ENRMLinkPillText.h +27 -27
  168. package/ios/attachments/ENRMLinkPillText.m +118 -118
  169. package/ios/attachments/ENRMMathInlineAttachment.h +7 -7
  170. package/ios/attachments/ENRMMathInlineAttachment.m +4 -4
  171. package/ios/attachments/ThematicBreakAttachment.h +15 -15
  172. package/ios/attachments/ThematicBreakAttachment.m +33 -33
  173. package/ios/internals/ENRMViewFreeMeasurement.h +208 -208
  174. package/ios/internals/EnrichedTiptapTextComponentDescriptor.h +19 -19
  175. package/ios/internals/EnrichedTiptapTextShadowNode.h +43 -43
  176. package/ios/internals/EnrichedTiptapTextShadowNode.mm +69 -69
  177. package/ios/internals/EnrichedTiptapTextState.h +24 -24
  178. package/ios/internals/MeasurementCache.h +308 -308
  179. package/ios/internals/ShadowMeasurementUtils.h +145 -145
  180. package/ios/parser/ENRMTextLinkAttributes.h +7 -7
  181. package/ios/parser/ENRMTiptapParser.h +33 -33
  182. package/ios/parser/ENRMTiptapParser.mm +73 -73
  183. package/ios/parser/TiptapASTNode.m +52 -52
  184. package/ios/renderer/AttributedRenderer.h +23 -23
  185. package/ios/renderer/AttributedRenderer.m +229 -229
  186. package/ios/renderer/BaseRenderer.h +33 -33
  187. package/ios/renderer/BaseRenderer.m +46 -46
  188. package/ios/renderer/BaselineShiftTextAttributes.h +10 -10
  189. package/ios/renderer/BaselineShiftTextAttributes.m +26 -26
  190. package/ios/renderer/BlankLineRenderer.h +4 -4
  191. package/ios/renderer/BlankLineRenderer.m +51 -51
  192. package/ios/renderer/BlockquoteRenderer.h +6 -6
  193. package/ios/renderer/BlockquoteRenderer.m +258 -258
  194. package/ios/renderer/CodeBlockRenderer.h +9 -9
  195. package/ios/renderer/CodeBlockRenderer.m +99 -99
  196. package/ios/renderer/CodeRenderer.h +9 -9
  197. package/ios/renderer/CodeRenderer.m +69 -69
  198. package/ios/renderer/ENRMBlockquoteTextRenderer.h +25 -25
  199. package/ios/renderer/ENRMBlockquoteTextRenderer.m +48 -48
  200. package/ios/renderer/EmphasisRenderer.h +5 -5
  201. package/ios/renderer/EmphasisRenderer.m +99 -99
  202. package/ios/renderer/HeadingRenderer.h +6 -6
  203. package/ios/renderer/HeadingRenderer.m +102 -102
  204. package/ios/renderer/HighlightRenderer.h +7 -7
  205. package/ios/renderer/HighlightRenderer.m +33 -33
  206. package/ios/renderer/LinkRenderer.h +6 -6
  207. package/ios/renderer/LinkRenderer.m +157 -157
  208. package/ios/renderer/ListItemRenderer.h +24 -24
  209. package/ios/renderer/ListItemRenderer.m +283 -283
  210. package/ios/renderer/ListRenderer.h +13 -13
  211. package/ios/renderer/ListRenderer.m +126 -126
  212. package/ios/renderer/MentionRenderer.h +9 -9
  213. package/ios/renderer/MentionRenderer.m +96 -96
  214. package/ios/renderer/NodeRenderer.h +8 -8
  215. package/ios/renderer/ParagraphRenderer.h +6 -6
  216. package/ios/renderer/ParagraphRenderer.m +85 -85
  217. package/ios/renderer/RenderContext.h +145 -145
  218. package/ios/renderer/RenderContext.m +402 -402
  219. package/ios/renderer/RendererFactory.h +12 -12
  220. package/ios/renderer/StrikethroughRenderer.h +5 -5
  221. package/ios/renderer/StrikethroughRenderer.m +30 -30
  222. package/ios/renderer/StrongRenderer.h +5 -5
  223. package/ios/renderer/StrongRenderer.m +87 -87
  224. package/ios/renderer/TextRenderer.h +6 -6
  225. package/ios/renderer/TextRenderer.m +18 -18
  226. package/ios/renderer/ThematicBreakRenderer.h +4 -4
  227. package/ios/renderer/ThematicBreakRenderer.m +41 -41
  228. package/ios/renderer/UnderlineRenderer.h +4 -4
  229. package/ios/renderer/UnderlineRenderer.m +41 -41
  230. package/ios/styles/ENRMFontSlot.h +12 -12
  231. package/ios/styles/ENRMFontSlot.m +24 -24
  232. package/ios/styles/StyleConfig.h +488 -488
  233. package/ios/styles/StyleConfig.mm +2820 -2820
  234. package/ios/utils/AccessibilityInfo.h +35 -35
  235. package/ios/utils/AccessibilityInfo.m +24 -24
  236. package/ios/utils/BlockquoteBorder.h +26 -26
  237. package/ios/utils/BlockquoteBorder.m +294 -294
  238. package/ios/utils/CodeBackground.h +19 -19
  239. package/ios/utils/CodeBackground.m +196 -196
  240. package/ios/utils/CodeBlockBackground.h +17 -17
  241. package/ios/utils/CodeBlockBackground.m +102 -102
  242. package/ios/utils/ContextMenuUtils.h +76 -76
  243. package/ios/utils/ContextMenuUtils.mm +75 -75
  244. package/ios/utils/ENRMAccessibilityLabels.h +40 -40
  245. package/ios/utils/ENRMAccessibilityLabels.m +5 -5
  246. package/ios/utils/ENRMAsyncRenderCoordinator.h +24 -24
  247. package/ios/utils/ENRMAsyncRenderCoordinator.m +37 -37
  248. package/ios/utils/ENRMAtomicSize.h +65 -65
  249. package/ios/utils/ENRMCodeBlockContent.h +37 -37
  250. package/ios/utils/ENRMCodeBlockContent.mm +89 -89
  251. package/ios/utils/ENRMFeatureFlags.h +25 -25
  252. package/ios/utils/ENRMImageDownloader.h +30 -30
  253. package/ios/utils/ENRMImageDownloader.m +168 -168
  254. package/ios/utils/ENRMLatexErrorCoordinator.h +27 -27
  255. package/ios/utils/ENRMLatexErrorCoordinator.mm +75 -75
  256. package/ios/utils/ENRMLatexErrorReporting.h +13 -13
  257. package/ios/utils/ENRMLinkContextMenus.h +93 -93
  258. package/ios/utils/ENRMLinkContextMenus.m +178 -178
  259. package/ios/utils/ENRMLinkRegexConfig.h +30 -30
  260. package/ios/utils/ENRMLinkRegexConfig.mm +104 -104
  261. package/ios/utils/ENRMLinkRegexProps.h +23 -23
  262. package/ios/utils/ENRMLocalImageLoader.h +35 -35
  263. package/ios/utils/ENRMLocalImageLoader.m +111 -111
  264. package/ios/utils/ENRMMenuAction.h +27 -27
  265. package/ios/utils/ENRMMenuAction.m +33 -33
  266. package/ios/utils/ENRMSpoilerOverlayManager.h +15 -15
  267. package/ios/utils/ENRMSpoilerOverlayManager.m +19 -19
  268. package/ios/utils/ENRMSpoilerTapUtils.h +6 -6
  269. package/ios/utils/ENRMSpoilerTapUtils.m +11 -11
  270. package/ios/utils/ENRMTailFadeInAnimator.h +16 -16
  271. package/ios/utils/ENRMTailFadeInAnimator.m +161 -161
  272. package/ios/utils/ENRMTextHitTest.h +23 -23
  273. package/ios/utils/ENRMTextHitTest.m +20 -20
  274. package/ios/utils/ENRMTextInteractionUtils.h +20 -20
  275. package/ios/utils/ENRMTextInteractionUtils.m +15 -15
  276. package/ios/utils/ENRMTextRenderer.h +38 -38
  277. package/ios/utils/ENRMTextRenderer.m +54 -54
  278. package/ios/utils/ENRMTextViewSetup.h +132 -132
  279. package/ios/utils/ENRMUIKit.h +429 -429
  280. package/ios/utils/EditMenuUtils.h +105 -105
  281. package/ios/utils/EditMenuUtils.m +143 -143
  282. package/ios/utils/FontScaleObserver.h +10 -10
  283. package/ios/utils/FontScaleObserver.m +55 -55
  284. package/ios/utils/FontUtils.h +31 -31
  285. package/ios/utils/FontUtils.m +63 -63
  286. package/ios/utils/HTMLGenerator.h +25 -25
  287. package/ios/utils/HTMLGenerator.m +1041 -1041
  288. package/ios/utils/HeightUpdateUtils.h +25 -25
  289. package/ios/utils/ImageRequestHeaderUtils.h +35 -35
  290. package/ios/utils/LastElementUtils.h +97 -97
  291. package/ios/utils/LinkContextMenuUtils.h +55 -55
  292. package/ios/utils/LinkPillContentUtils.h +46 -46
  293. package/ios/utils/ListMarkerDrawer.h +15 -15
  294. package/ios/utils/ListMarkerDrawer.m +239 -239
  295. package/ios/utils/MentionBackground.h +26 -26
  296. package/ios/utils/MentionBackground.m +86 -86
  297. package/ios/utils/ParagraphStyleUtils.h +87 -87
  298. package/ios/utils/ParagraphStyleUtils.m +484 -484
  299. package/ios/utils/PasteboardUtils.h +48 -48
  300. package/ios/utils/PasteboardUtils.m +166 -166
  301. package/ios/utils/RTFExportUtils.h +24 -24
  302. package/ios/utils/RTFExportUtils.m +299 -299
  303. package/ios/utils/SelectionColorUtils.h +14 -14
  304. package/ios/utils/StylePropsUtils.h +1340 -1340
  305. package/ios/utils/TaskListTapUtils.h +6 -6
  306. package/ios/utils/TaskListTapUtils.m +14 -14
  307. package/ios/utils/TextViewLayoutManager.h +14 -14
  308. package/ios/utils/TiptapAccessibilityElementBuilder.h +43 -43
  309. package/ios/utils/TiptapAccessibilityElementBuilder.m +432 -432
  310. package/ios/utils/TiptapExtractor.h +17 -17
  311. package/ios/utils/TiptapExtractor.m +367 -367
  312. package/ios/views/ENRMTiptapTextView.h +25 -25
  313. package/ios/views/ENRMTiptapTextView.m +247 -247
  314. package/package.json +65 -70
  315. package/react-native.config.js +10 -10
  316. package/src/admonitionDefaults.ts +58 -58
  317. package/src/darkModeTextStyle.ts +99 -99
  318. package/src/headingDefaults.ts +21 -21
  319. package/src/index.tsx +4 -4
  320. package/src/linkVariantUtils.ts +120 -120
  321. package/src/normalizeTiptapStyle.ts +397 -397
  322. package/src/rn-globals.d.ts +1 -1
  323. package/src/styleUtils.ts +107 -107
  324. package/src/types/TiptapStyle.ts +532 -532
  325. package/src/types/TiptapStyleInternal.ts +299 -299
  326. package/src/types/linkPill.ts +9 -9
  327. package/src/utils/isDev.ts +4 -4
@@ -1,432 +1,432 @@
1
- #import "TiptapAccessibilityElementBuilder.h"
2
- #import "AccessibilityInfo.h"
3
- #import "BlockquoteBorder.h"
4
- #import "ENRMAccessibilityLabels.h"
5
- #import "ENRMLinkPillAttachment.h"
6
- #include <TargetConditionals.h>
7
-
8
- typedef NS_ENUM(NSInteger, ElementType) { ElementTypeText, ElementTypeLink, ElementTypeImage };
9
-
10
- static const CGFloat kFocusRectPadding = 2.0;
11
-
12
- @implementation TiptapAccessibilityElementBuilder
13
-
14
- #if !TARGET_OS_OSX
15
-
16
- #pragma mark - Public API
17
-
18
- + (NSMutableArray<UIAccessibilityElement *> *)buildElementsForTextView:(UITextView *)textView
19
- info:(AccessibilityInfo *)info
20
- labels:(ENRMAccessibilityLabels *)labels
21
- container:(id)container
22
- {
23
- NSString *fullString = textView.attributedText.string;
24
- if (fullString.length == 0)
25
- return [NSMutableArray array];
26
-
27
- [textView.layoutManager ensureLayoutForTextContainer:textView.textContainer];
28
-
29
- NSMutableArray<UIAccessibilityElement *> *elements = [NSMutableArray array];
30
- NSUInteger currentPos = 0;
31
-
32
- while (currentPos < fullString.length) {
33
- NSRange paragraphRange = [fullString paragraphRangeForRange:NSMakeRange(currentPos, 0)];
34
- NSString *trimmed = [[fullString substringWithRange:paragraphRange]
35
- stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
36
-
37
- if (trimmed.length > 0) {
38
- NSArray *links = [self linksInRange:paragraphRange info:info];
39
- NSArray *images = [self imagesInRange:paragraphRange info:info];
40
- NSArray *specials = [links arrayByAddingObjectsFromArray:images];
41
-
42
- NSInteger level = [self headingLevelForRange:paragraphRange info:info];
43
- NSDictionary *list = [self listItemInfoForRange:paragraphRange info:info];
44
-
45
- if (specials.count == 0) {
46
- [elements addObject:[self createElementForRange:paragraphRange
47
- type:ElementTypeText
48
- text:trimmed
49
- isLinked:NO
50
- heading:level
51
- listInfo:list
52
- labels:labels
53
- view:textView
54
- container:container]];
55
- } else {
56
- [elements addObjectsFromArray:[self segmentedElementsForParagraph:paragraphRange
57
- fullText:fullString
58
- headingLevel:level
59
- listInfo:list
60
- specials:specials
61
- labels:labels
62
- inTextView:textView
63
- container:container]];
64
- }
65
- }
66
- currentPos = NSMaxRange(paragraphRange);
67
- }
68
- return elements;
69
- }
70
-
71
- #pragma mark - Segmentation
72
-
73
- + (BOOL)hasAlphanumericContent:(NSString *)text
74
- {
75
- static NSCharacterSet *alphanumericSet;
76
- static dispatch_once_t onceToken;
77
- dispatch_once(&onceToken, ^{ alphanumericSet = [NSCharacterSet alphanumericCharacterSet]; });
78
- return [text rangeOfCharacterFromSet:alphanumericSet].location != NSNotFound;
79
- }
80
-
81
- + (NSArray<UIAccessibilityElement *> *)segmentedElementsForParagraph:(NSRange)paragraphRange
82
- fullText:(NSString *)fullText
83
- headingLevel:(NSInteger)headingLevel
84
- listInfo:(NSDictionary *)listInfo
85
- specials:(NSArray *)specials
86
- labels:(ENRMAccessibilityLabels *)labels
87
- inTextView:(UITextView *)textView
88
- container:(id)container
89
- {
90
- NSMutableArray<UIAccessibilityElement *> *elements = [NSMutableArray array];
91
- NSArray *sortedSpecials = [specials sortedArrayUsingComparator:^NSComparisonResult(NSDictionary *a, NSDictionary *b) {
92
- return [@([a[@"range"] rangeValue].location) compare:@([b[@"range"] rangeValue].location)];
93
- }];
94
-
95
- NSUInteger segmentStart = paragraphRange.location;
96
- for (NSDictionary *item in sortedSpecials) {
97
- NSRange itemRange = [item[@"range"] rangeValue];
98
-
99
- if (itemRange.location > segmentStart) {
100
- NSRange beforeRange = NSMakeRange(segmentStart, itemRange.location - segmentStart);
101
- NSString *beforeText = [[fullText substringWithRange:beforeRange]
102
- stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
103
- if (beforeText.length > 0 && [self hasAlphanumericContent:beforeText]) {
104
- [elements addObject:[self createElementForRange:beforeRange
105
- type:ElementTypeText
106
- text:beforeText
107
- isLinked:NO
108
- heading:headingLevel
109
- listInfo:listInfo
110
- labels:labels
111
- view:textView
112
- container:container]];
113
- }
114
- }
115
-
116
- BOOL isImg = item[@"altText"] != nil;
117
- NSString *label = isImg ? item[@"altText"] : [fullText substringWithRange:itemRange];
118
- if (!isImg) {
119
- id attachment = [textView.textStorage attribute:NSAttachmentAttributeName
120
- atIndex:itemRange.location
121
- effectiveRange:NULL];
122
- if ([attachment isKindOfClass:ENRMLinkPillAttachment.class])
123
- label = [(ENRMLinkPillAttachment *)attachment linkAccessibilityLabel];
124
- }
125
- [elements addObject:[self createElementForRange:itemRange
126
- type:isImg ? ElementTypeImage : ElementTypeLink
127
- text:label
128
- isLinked:isImg ? [item[@"isLinked"] boolValue] : YES
129
- heading:0
130
- listInfo:listInfo
131
- labels:labels
132
- view:textView
133
- container:container]];
134
- segmentStart = NSMaxRange(itemRange);
135
- }
136
-
137
- if (segmentStart < NSMaxRange(paragraphRange)) {
138
- NSRange afterRange = NSMakeRange(segmentStart, NSMaxRange(paragraphRange) - segmentStart);
139
- NSString *afterText = [[fullText substringWithRange:afterRange]
140
- stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
141
- if (afterText.length > 0 && [self hasAlphanumericContent:afterText]) {
142
- [elements addObject:[self createElementForRange:afterRange
143
- type:ElementTypeText
144
- text:afterText
145
- isLinked:NO
146
- heading:headingLevel
147
- listInfo:listInfo
148
- labels:labels
149
- view:textView
150
- container:container]];
151
- }
152
- }
153
- return elements;
154
- }
155
-
156
- #pragma mark - Factory
157
-
158
- + (UIAccessibilityElement *)createElementForRange:(NSRange)range
159
- type:(ElementType)type
160
- text:(NSString *)text
161
- isLinked:(BOOL)linked
162
- heading:(NSInteger)level
163
- listInfo:(NSDictionary *)listInfo
164
- labels:(ENRMAccessibilityLabels *)labels
165
- view:(UITextView *)textView
166
- container:(id)container
167
- {
168
- UIAccessibilityElement *element = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:container];
169
- if (text.length > 0)
170
- element.accessibilityLabel = text;
171
-
172
- element.accessibilityFrameInContainerSpace = [self frameForRange:range inTextView:textView container:container];
173
-
174
- if (type == ElementTypeImage) {
175
- element.accessibilityTraits =
176
- linked ? (UIAccessibilityTraitImage | UIAccessibilityTraitLink) : UIAccessibilityTraitImage;
177
-
178
- } else if (type == ElementTypeLink) {
179
- element.accessibilityTraits = UIAccessibilityTraitLink;
180
- } else if (level > 0) {
181
- element.accessibilityTraits = UIAccessibilityTraitHeader;
182
- }
183
-
184
- NSMutableArray<NSString *> *valueParts = [NSMutableArray array];
185
- NSString *listAnnouncement =
186
- (listInfo && type != ElementTypeImage) ? [self formatListAnnouncement:listInfo labels:labels] : nil;
187
- if (listAnnouncement) {
188
- [valueParts addObject:listAnnouncement];
189
- }
190
- NSString *blockquoteAnnouncement = [self blockquoteAnnouncementForRange:range textView:textView labels:labels];
191
- if (blockquoteAnnouncement) {
192
- [valueParts addObject:blockquoteAnnouncement];
193
- }
194
- if (valueParts.count > 0) {
195
- element.accessibilityValue = [valueParts componentsJoinedByString:@", "];
196
- }
197
-
198
- return element;
199
- }
200
-
201
- + (NSString *)blockquoteAnnouncementForRange:(NSRange)range
202
- textView:(UITextView *)textView
203
- labels:(ENRMAccessibilityLabels *)labels
204
- {
205
- NSTextStorage *storage = textView.textStorage;
206
- if (storage.length == 0 || range.location >= storage.length)
207
- return nil;
208
- NSRange effective;
209
- NSNumber *depth = [storage attribute:BlockquoteDepthAttributeName atIndex:range.location effectiveRange:&effective];
210
- if (depth == nil)
211
- return nil;
212
- return depth.integerValue >= 1 ? labels.nestedBlockquote : labels.blockquote;
213
- }
214
-
215
- #pragma mark - Helpers
216
-
217
- + (NSString *)formatListAnnouncement:(NSDictionary *)info labels:(ENRMAccessibilityLabels *)labels
218
- {
219
- BOOL nested = [info[@"depth"] integerValue] > 1;
220
- if ([info[@"isOrdered"] boolValue]) {
221
- long position = (long)[info[@"position"] integerValue];
222
- NSString *template = nested ? labels.nestedOrderedItem : labels.orderedItem;
223
- return [template stringByReplacingOccurrencesOfString:@"{n}"
224
- withString:[NSString stringWithFormat:@"%ld", position]];
225
- }
226
- return nested ? labels.nestedBulletPoint : labels.bulletPoint;
227
- }
228
-
229
- + (NSRange)clampedRange:(NSRange)range forText:(NSString *)text
230
- {
231
- if (text.length == 0 || range.location >= text.length)
232
- return NSMakeRange(NSNotFound, 0);
233
- return NSMakeRange(range.location, MIN(range.length, text.length - range.location));
234
- }
235
-
236
- + (CGRect)frameForRange:(NSRange)range inTextView:(UITextView *)textView container:(id)container
237
- {
238
- NSRange clamped = [self clampedRange:range forText:textView.textStorage.string];
239
- if (clamped.location == NSNotFound)
240
- return CGRectZero;
241
-
242
- NSRange glyphRange = [textView.layoutManager glyphRangeForCharacterRange:clamped actualCharacterRange:NULL];
243
- CGRect rect = [textView.layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textView.textContainer];
244
- rect = CGRectInset(rect, -kFocusRectPadding, -kFocusRectPadding);
245
- rect = CGRectOffset(rect, textView.textContainerInset.left, textView.textContainerInset.top);
246
- return [(UIView *)container convertRect:CGRectIntegral(rect) fromView:textView];
247
- }
248
-
249
- #pragma mark - Data Helpers
250
-
251
- + (NSInteger)headingLevelForRange:(NSRange)range info:(AccessibilityInfo *)info
252
- {
253
- for (NSUInteger i = 0; i < info.headingRanges.count; i++) {
254
- if (NSIntersectionRange(range, [info.headingRanges[i] rangeValue]).length > 0)
255
- return [info.headingLevels[i] integerValue];
256
- }
257
- return 0;
258
- }
259
-
260
- + (NSArray *)linksInRange:(NSRange)range info:(AccessibilityInfo *)info
261
- {
262
- NSMutableArray *links = [NSMutableArray array];
263
- for (NSUInteger i = 0; i < info.linkRanges.count; i++) {
264
- if (NSIntersectionRange(range, [info.linkRanges[i] rangeValue]).length > 0) {
265
- [links addObject:@{@"range" : info.linkRanges[i], @"url" : info.linkURLs[i] ?: @""}];
266
- }
267
- }
268
- return links;
269
- }
270
-
271
- + (NSArray *)imagesInRange:(NSRange)range info:(AccessibilityInfo *)info
272
- {
273
- NSMutableArray *images = [NSMutableArray array];
274
- for (NSUInteger i = 0; i < info.imageRanges.count; i++) {
275
- NSRange imgRange = [info.imageRanges[i] rangeValue];
276
- if (NSIntersectionRange(range, imgRange).length > 0) {
277
- BOOL linked = NO;
278
- for (NSValue *linkRange in info.linkRanges)
279
- if (NSIntersectionRange(imgRange, linkRange.rangeValue).length > 0) {
280
- linked = YES;
281
- break;
282
- }
283
- [images addObject:@{
284
- @"range" : info.imageRanges[i],
285
- @"altText" : info.imageAltTexts[i] ?: @"",
286
- @"isLinked" : @(linked)
287
- }];
288
- }
289
- }
290
- return images;
291
- }
292
-
293
- + (NSDictionary *)listItemInfoForRange:(NSRange)range info:(AccessibilityInfo *)info
294
- {
295
- if (!info)
296
- return nil;
297
- for (NSUInteger i = 0; i < info.listItemRanges.count; i++) {
298
- if (NSIntersectionRange(range, [info.listItemRanges[i] rangeValue]).length > 0) {
299
- return @{
300
- @"position" : info.listItemPositions[i],
301
- @"depth" : info.listItemDepths[i],
302
- @"isOrdered" : info.listItemOrdered[i]
303
- };
304
- }
305
- }
306
- return nil;
307
- }
308
-
309
- #pragma mark - Rotors
310
-
311
- + (NSArray *)filterElements:(NSArray *)elements withTrait:(UIAccessibilityTraits)trait
312
- {
313
- return [elements
314
- filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(UIAccessibilityElement *element, id bindings) {
315
- return (element.accessibilityTraits & trait) != 0;
316
- }]];
317
- }
318
-
319
- + (UIAccessibilityCustomRotor *)createRotorWithName:(NSString *)name elements:(NSArray *)elements
320
- {
321
- return [[UIAccessibilityCustomRotor alloc]
322
- initWithName:name
323
- itemSearchBlock:^UIAccessibilityCustomRotorItemResult *(UIAccessibilityCustomRotorSearchPredicate *predicate) {
324
- if (elements.count == 0)
325
- return nil;
326
- NSInteger currentIndex = predicate.currentItem.targetElement
327
- ? [elements indexOfObject:predicate.currentItem.targetElement]
328
- : NSNotFound;
329
- NSInteger nextIndex = (predicate.searchDirection == UIAccessibilityCustomRotorDirectionNext)
330
- ? (currentIndex == NSNotFound ? 0 : currentIndex + 1)
331
- : (currentIndex == NSNotFound ? (NSInteger)elements.count - 1 : currentIndex - 1);
332
- return (nextIndex >= 0 && nextIndex < (NSInteger)elements.count)
333
- ? [[UIAccessibilityCustomRotorItemResult alloc] initWithTargetElement:elements[nextIndex]
334
- targetRange:nil]
335
- : nil;
336
- }];
337
- }
338
-
339
- + (NSArray<UIAccessibilityElement *> *)filterHeadingElements:(NSArray *)elements
340
- {
341
- return [self filterElements:elements withTrait:UIAccessibilityTraitHeader];
342
- }
343
- + (NSArray<UIAccessibilityElement *> *)filterLinkElements:(NSArray *)elements
344
- {
345
- return [self filterElements:elements withTrait:UIAccessibilityTraitLink];
346
- }
347
- + (NSArray<UIAccessibilityElement *> *)filterImageElements:(NSArray *)elements
348
- {
349
- return [self filterElements:elements withTrait:UIAccessibilityTraitImage];
350
- }
351
- + (UIAccessibilityCustomRotor *)createHeadingRotorWithElements:(NSArray *)elements name:(NSString *)name
352
- {
353
- return [self createRotorWithName:name elements:elements];
354
- }
355
- + (UIAccessibilityCustomRotor *)createLinkRotorWithElements:(NSArray *)elements name:(NSString *)name
356
- {
357
- return [self createRotorWithName:name elements:elements];
358
- }
359
- + (UIAccessibilityCustomRotor *)createImageRotorWithElements:(NSArray *)elements name:(NSString *)name
360
- {
361
- return [self createRotorWithName:name elements:elements];
362
- }
363
-
364
- + (NSArray<UIAccessibilityCustomRotor *> *)buildRotorsFromElements:(NSArray<UIAccessibilityElement *> *)elements
365
- labels:(ENRMAccessibilityLabels *)labels
366
- {
367
- NSMutableArray<UIAccessibilityCustomRotor *> *rotors = [NSMutableArray array];
368
-
369
- NSArray<UIAccessibilityElement *> *headingElements = [self filterHeadingElements:elements];
370
- if (headingElements.count > 0) {
371
- [rotors addObject:[self createHeadingRotorWithElements:headingElements name:labels.rotorHeadings]];
372
- }
373
-
374
- NSArray<UIAccessibilityElement *> *linkElements = [self filterLinkElements:elements];
375
- if (linkElements.count > 0) {
376
- [rotors addObject:[self createLinkRotorWithElements:linkElements name:labels.rotorLinks]];
377
- }
378
-
379
- NSArray<UIAccessibilityElement *> *imageElements = [self filterImageElements:elements];
380
- if (imageElements.count > 0) {
381
- [rotors addObject:[self createImageRotorWithElements:imageElements name:labels.rotorImages]];
382
- }
383
-
384
- return rotors;
385
- }
386
-
387
- #else
388
-
389
- // TODO: Implement VoiceOver accessibility elements for macOS using NSAccessibility.
390
- // This includes building heading, link, and image accessibility elements from AttributedString
391
- // attributes, and exposing them via NSAccessibilityElement so VoiceOver can navigate the
392
- // rendered tiptap content. The iOS implementation above can serve as a reference.
393
-
394
- + (NSMutableArray *)buildElementsForTextView:(id)textView
395
- info:(AccessibilityInfo *)info
396
- labels:(ENRMAccessibilityLabels *)labels
397
- container:(id)container
398
- {
399
- return [NSMutableArray array];
400
- }
401
- + (NSArray *)filterHeadingElements:(NSArray *)elements
402
- {
403
- return @[];
404
- }
405
- + (NSArray *)filterLinkElements:(NSArray *)elements
406
- {
407
- return @[];
408
- }
409
- + (NSArray *)filterImageElements:(NSArray *)elements
410
- {
411
- return @[];
412
- }
413
- + (id)createHeadingRotorWithElements:(NSArray *)elements name:(NSString *)name
414
- {
415
- return nil;
416
- }
417
- + (id)createLinkRotorWithElements:(NSArray *)elements name:(NSString *)name
418
- {
419
- return nil;
420
- }
421
- + (id)createImageRotorWithElements:(NSArray *)elements name:(NSString *)name
422
- {
423
- return nil;
424
- }
425
- + (NSArray *)buildRotorsFromElements:(NSArray *)elements labels:(ENRMAccessibilityLabels *)labels
426
- {
427
- return @[];
428
- }
429
-
430
- #endif
431
-
432
- @end
1
+ #import "TiptapAccessibilityElementBuilder.h"
2
+ #import "AccessibilityInfo.h"
3
+ #import "BlockquoteBorder.h"
4
+ #import "ENRMAccessibilityLabels.h"
5
+ #import "ENRMLinkPillAttachment.h"
6
+ #include <TargetConditionals.h>
7
+
8
+ typedef NS_ENUM(NSInteger, ElementType) { ElementTypeText, ElementTypeLink, ElementTypeImage };
9
+
10
+ static const CGFloat kFocusRectPadding = 2.0;
11
+
12
+ @implementation TiptapAccessibilityElementBuilder
13
+
14
+ #if !TARGET_OS_OSX
15
+
16
+ #pragma mark - Public API
17
+
18
+ + (NSMutableArray<UIAccessibilityElement *> *)buildElementsForTextView:(UITextView *)textView
19
+ info:(AccessibilityInfo *)info
20
+ labels:(ENRMAccessibilityLabels *)labels
21
+ container:(id)container
22
+ {
23
+ NSString *fullString = textView.attributedText.string;
24
+ if (fullString.length == 0)
25
+ return [NSMutableArray array];
26
+
27
+ [textView.layoutManager ensureLayoutForTextContainer:textView.textContainer];
28
+
29
+ NSMutableArray<UIAccessibilityElement *> *elements = [NSMutableArray array];
30
+ NSUInteger currentPos = 0;
31
+
32
+ while (currentPos < fullString.length) {
33
+ NSRange paragraphRange = [fullString paragraphRangeForRange:NSMakeRange(currentPos, 0)];
34
+ NSString *trimmed = [[fullString substringWithRange:paragraphRange]
35
+ stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
36
+
37
+ if (trimmed.length > 0) {
38
+ NSArray *links = [self linksInRange:paragraphRange info:info];
39
+ NSArray *images = [self imagesInRange:paragraphRange info:info];
40
+ NSArray *specials = [links arrayByAddingObjectsFromArray:images];
41
+
42
+ NSInteger level = [self headingLevelForRange:paragraphRange info:info];
43
+ NSDictionary *list = [self listItemInfoForRange:paragraphRange info:info];
44
+
45
+ if (specials.count == 0) {
46
+ [elements addObject:[self createElementForRange:paragraphRange
47
+ type:ElementTypeText
48
+ text:trimmed
49
+ isLinked:NO
50
+ heading:level
51
+ listInfo:list
52
+ labels:labels
53
+ view:textView
54
+ container:container]];
55
+ } else {
56
+ [elements addObjectsFromArray:[self segmentedElementsForParagraph:paragraphRange
57
+ fullText:fullString
58
+ headingLevel:level
59
+ listInfo:list
60
+ specials:specials
61
+ labels:labels
62
+ inTextView:textView
63
+ container:container]];
64
+ }
65
+ }
66
+ currentPos = NSMaxRange(paragraphRange);
67
+ }
68
+ return elements;
69
+ }
70
+
71
+ #pragma mark - Segmentation
72
+
73
+ + (BOOL)hasAlphanumericContent:(NSString *)text
74
+ {
75
+ static NSCharacterSet *alphanumericSet;
76
+ static dispatch_once_t onceToken;
77
+ dispatch_once(&onceToken, ^{ alphanumericSet = [NSCharacterSet alphanumericCharacterSet]; });
78
+ return [text rangeOfCharacterFromSet:alphanumericSet].location != NSNotFound;
79
+ }
80
+
81
+ + (NSArray<UIAccessibilityElement *> *)segmentedElementsForParagraph:(NSRange)paragraphRange
82
+ fullText:(NSString *)fullText
83
+ headingLevel:(NSInteger)headingLevel
84
+ listInfo:(NSDictionary *)listInfo
85
+ specials:(NSArray *)specials
86
+ labels:(ENRMAccessibilityLabels *)labels
87
+ inTextView:(UITextView *)textView
88
+ container:(id)container
89
+ {
90
+ NSMutableArray<UIAccessibilityElement *> *elements = [NSMutableArray array];
91
+ NSArray *sortedSpecials = [specials sortedArrayUsingComparator:^NSComparisonResult(NSDictionary *a, NSDictionary *b) {
92
+ return [@([a[@"range"] rangeValue].location) compare:@([b[@"range"] rangeValue].location)];
93
+ }];
94
+
95
+ NSUInteger segmentStart = paragraphRange.location;
96
+ for (NSDictionary *item in sortedSpecials) {
97
+ NSRange itemRange = [item[@"range"] rangeValue];
98
+
99
+ if (itemRange.location > segmentStart) {
100
+ NSRange beforeRange = NSMakeRange(segmentStart, itemRange.location - segmentStart);
101
+ NSString *beforeText = [[fullText substringWithRange:beforeRange]
102
+ stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
103
+ if (beforeText.length > 0 && [self hasAlphanumericContent:beforeText]) {
104
+ [elements addObject:[self createElementForRange:beforeRange
105
+ type:ElementTypeText
106
+ text:beforeText
107
+ isLinked:NO
108
+ heading:headingLevel
109
+ listInfo:listInfo
110
+ labels:labels
111
+ view:textView
112
+ container:container]];
113
+ }
114
+ }
115
+
116
+ BOOL isImg = item[@"altText"] != nil;
117
+ NSString *label = isImg ? item[@"altText"] : [fullText substringWithRange:itemRange];
118
+ if (!isImg) {
119
+ id attachment = [textView.textStorage attribute:NSAttachmentAttributeName
120
+ atIndex:itemRange.location
121
+ effectiveRange:NULL];
122
+ if ([attachment isKindOfClass:ENRMLinkPillAttachment.class])
123
+ label = [(ENRMLinkPillAttachment *)attachment linkAccessibilityLabel];
124
+ }
125
+ [elements addObject:[self createElementForRange:itemRange
126
+ type:isImg ? ElementTypeImage : ElementTypeLink
127
+ text:label
128
+ isLinked:isImg ? [item[@"isLinked"] boolValue] : YES
129
+ heading:0
130
+ listInfo:listInfo
131
+ labels:labels
132
+ view:textView
133
+ container:container]];
134
+ segmentStart = NSMaxRange(itemRange);
135
+ }
136
+
137
+ if (segmentStart < NSMaxRange(paragraphRange)) {
138
+ NSRange afterRange = NSMakeRange(segmentStart, NSMaxRange(paragraphRange) - segmentStart);
139
+ NSString *afterText = [[fullText substringWithRange:afterRange]
140
+ stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
141
+ if (afterText.length > 0 && [self hasAlphanumericContent:afterText]) {
142
+ [elements addObject:[self createElementForRange:afterRange
143
+ type:ElementTypeText
144
+ text:afterText
145
+ isLinked:NO
146
+ heading:headingLevel
147
+ listInfo:listInfo
148
+ labels:labels
149
+ view:textView
150
+ container:container]];
151
+ }
152
+ }
153
+ return elements;
154
+ }
155
+
156
+ #pragma mark - Factory
157
+
158
+ + (UIAccessibilityElement *)createElementForRange:(NSRange)range
159
+ type:(ElementType)type
160
+ text:(NSString *)text
161
+ isLinked:(BOOL)linked
162
+ heading:(NSInteger)level
163
+ listInfo:(NSDictionary *)listInfo
164
+ labels:(ENRMAccessibilityLabels *)labels
165
+ view:(UITextView *)textView
166
+ container:(id)container
167
+ {
168
+ UIAccessibilityElement *element = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:container];
169
+ if (text.length > 0)
170
+ element.accessibilityLabel = text;
171
+
172
+ element.accessibilityFrameInContainerSpace = [self frameForRange:range inTextView:textView container:container];
173
+
174
+ if (type == ElementTypeImage) {
175
+ element.accessibilityTraits =
176
+ linked ? (UIAccessibilityTraitImage | UIAccessibilityTraitLink) : UIAccessibilityTraitImage;
177
+
178
+ } else if (type == ElementTypeLink) {
179
+ element.accessibilityTraits = UIAccessibilityTraitLink;
180
+ } else if (level > 0) {
181
+ element.accessibilityTraits = UIAccessibilityTraitHeader;
182
+ }
183
+
184
+ NSMutableArray<NSString *> *valueParts = [NSMutableArray array];
185
+ NSString *listAnnouncement =
186
+ (listInfo && type != ElementTypeImage) ? [self formatListAnnouncement:listInfo labels:labels] : nil;
187
+ if (listAnnouncement) {
188
+ [valueParts addObject:listAnnouncement];
189
+ }
190
+ NSString *blockquoteAnnouncement = [self blockquoteAnnouncementForRange:range textView:textView labels:labels];
191
+ if (blockquoteAnnouncement) {
192
+ [valueParts addObject:blockquoteAnnouncement];
193
+ }
194
+ if (valueParts.count > 0) {
195
+ element.accessibilityValue = [valueParts componentsJoinedByString:@", "];
196
+ }
197
+
198
+ return element;
199
+ }
200
+
201
+ + (NSString *)blockquoteAnnouncementForRange:(NSRange)range
202
+ textView:(UITextView *)textView
203
+ labels:(ENRMAccessibilityLabels *)labels
204
+ {
205
+ NSTextStorage *storage = textView.textStorage;
206
+ if (storage.length == 0 || range.location >= storage.length)
207
+ return nil;
208
+ NSRange effective;
209
+ NSNumber *depth = [storage attribute:BlockquoteDepthAttributeName atIndex:range.location effectiveRange:&effective];
210
+ if (depth == nil)
211
+ return nil;
212
+ return depth.integerValue >= 1 ? labels.nestedBlockquote : labels.blockquote;
213
+ }
214
+
215
+ #pragma mark - Helpers
216
+
217
+ + (NSString *)formatListAnnouncement:(NSDictionary *)info labels:(ENRMAccessibilityLabels *)labels
218
+ {
219
+ BOOL nested = [info[@"depth"] integerValue] > 1;
220
+ if ([info[@"isOrdered"] boolValue]) {
221
+ long position = (long)[info[@"position"] integerValue];
222
+ NSString *template = nested ? labels.nestedOrderedItem : labels.orderedItem;
223
+ return [template stringByReplacingOccurrencesOfString:@"{n}"
224
+ withString:[NSString stringWithFormat:@"%ld", position]];
225
+ }
226
+ return nested ? labels.nestedBulletPoint : labels.bulletPoint;
227
+ }
228
+
229
+ + (NSRange)clampedRange:(NSRange)range forText:(NSString *)text
230
+ {
231
+ if (text.length == 0 || range.location >= text.length)
232
+ return NSMakeRange(NSNotFound, 0);
233
+ return NSMakeRange(range.location, MIN(range.length, text.length - range.location));
234
+ }
235
+
236
+ + (CGRect)frameForRange:(NSRange)range inTextView:(UITextView *)textView container:(id)container
237
+ {
238
+ NSRange clamped = [self clampedRange:range forText:textView.textStorage.string];
239
+ if (clamped.location == NSNotFound)
240
+ return CGRectZero;
241
+
242
+ NSRange glyphRange = [textView.layoutManager glyphRangeForCharacterRange:clamped actualCharacterRange:NULL];
243
+ CGRect rect = [textView.layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textView.textContainer];
244
+ rect = CGRectInset(rect, -kFocusRectPadding, -kFocusRectPadding);
245
+ rect = CGRectOffset(rect, textView.textContainerInset.left, textView.textContainerInset.top);
246
+ return [(UIView *)container convertRect:CGRectIntegral(rect) fromView:textView];
247
+ }
248
+
249
+ #pragma mark - Data Helpers
250
+
251
+ + (NSInteger)headingLevelForRange:(NSRange)range info:(AccessibilityInfo *)info
252
+ {
253
+ for (NSUInteger i = 0; i < info.headingRanges.count; i++) {
254
+ if (NSIntersectionRange(range, [info.headingRanges[i] rangeValue]).length > 0)
255
+ return [info.headingLevels[i] integerValue];
256
+ }
257
+ return 0;
258
+ }
259
+
260
+ + (NSArray *)linksInRange:(NSRange)range info:(AccessibilityInfo *)info
261
+ {
262
+ NSMutableArray *links = [NSMutableArray array];
263
+ for (NSUInteger i = 0; i < info.linkRanges.count; i++) {
264
+ if (NSIntersectionRange(range, [info.linkRanges[i] rangeValue]).length > 0) {
265
+ [links addObject:@{@"range" : info.linkRanges[i], @"url" : info.linkURLs[i] ?: @""}];
266
+ }
267
+ }
268
+ return links;
269
+ }
270
+
271
+ + (NSArray *)imagesInRange:(NSRange)range info:(AccessibilityInfo *)info
272
+ {
273
+ NSMutableArray *images = [NSMutableArray array];
274
+ for (NSUInteger i = 0; i < info.imageRanges.count; i++) {
275
+ NSRange imgRange = [info.imageRanges[i] rangeValue];
276
+ if (NSIntersectionRange(range, imgRange).length > 0) {
277
+ BOOL linked = NO;
278
+ for (NSValue *linkRange in info.linkRanges)
279
+ if (NSIntersectionRange(imgRange, linkRange.rangeValue).length > 0) {
280
+ linked = YES;
281
+ break;
282
+ }
283
+ [images addObject:@{
284
+ @"range" : info.imageRanges[i],
285
+ @"altText" : info.imageAltTexts[i] ?: @"",
286
+ @"isLinked" : @(linked)
287
+ }];
288
+ }
289
+ }
290
+ return images;
291
+ }
292
+
293
+ + (NSDictionary *)listItemInfoForRange:(NSRange)range info:(AccessibilityInfo *)info
294
+ {
295
+ if (!info)
296
+ return nil;
297
+ for (NSUInteger i = 0; i < info.listItemRanges.count; i++) {
298
+ if (NSIntersectionRange(range, [info.listItemRanges[i] rangeValue]).length > 0) {
299
+ return @{
300
+ @"position" : info.listItemPositions[i],
301
+ @"depth" : info.listItemDepths[i],
302
+ @"isOrdered" : info.listItemOrdered[i]
303
+ };
304
+ }
305
+ }
306
+ return nil;
307
+ }
308
+
309
+ #pragma mark - Rotors
310
+
311
+ + (NSArray *)filterElements:(NSArray *)elements withTrait:(UIAccessibilityTraits)trait
312
+ {
313
+ return [elements
314
+ filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(UIAccessibilityElement *element, id bindings) {
315
+ return (element.accessibilityTraits & trait) != 0;
316
+ }]];
317
+ }
318
+
319
+ + (UIAccessibilityCustomRotor *)createRotorWithName:(NSString *)name elements:(NSArray *)elements
320
+ {
321
+ return [[UIAccessibilityCustomRotor alloc]
322
+ initWithName:name
323
+ itemSearchBlock:^UIAccessibilityCustomRotorItemResult *(UIAccessibilityCustomRotorSearchPredicate *predicate) {
324
+ if (elements.count == 0)
325
+ return nil;
326
+ NSInteger currentIndex = predicate.currentItem.targetElement
327
+ ? [elements indexOfObject:predicate.currentItem.targetElement]
328
+ : NSNotFound;
329
+ NSInteger nextIndex = (predicate.searchDirection == UIAccessibilityCustomRotorDirectionNext)
330
+ ? (currentIndex == NSNotFound ? 0 : currentIndex + 1)
331
+ : (currentIndex == NSNotFound ? (NSInteger)elements.count - 1 : currentIndex - 1);
332
+ return (nextIndex >= 0 && nextIndex < (NSInteger)elements.count)
333
+ ? [[UIAccessibilityCustomRotorItemResult alloc] initWithTargetElement:elements[nextIndex]
334
+ targetRange:nil]
335
+ : nil;
336
+ }];
337
+ }
338
+
339
+ + (NSArray<UIAccessibilityElement *> *)filterHeadingElements:(NSArray *)elements
340
+ {
341
+ return [self filterElements:elements withTrait:UIAccessibilityTraitHeader];
342
+ }
343
+ + (NSArray<UIAccessibilityElement *> *)filterLinkElements:(NSArray *)elements
344
+ {
345
+ return [self filterElements:elements withTrait:UIAccessibilityTraitLink];
346
+ }
347
+ + (NSArray<UIAccessibilityElement *> *)filterImageElements:(NSArray *)elements
348
+ {
349
+ return [self filterElements:elements withTrait:UIAccessibilityTraitImage];
350
+ }
351
+ + (UIAccessibilityCustomRotor *)createHeadingRotorWithElements:(NSArray *)elements name:(NSString *)name
352
+ {
353
+ return [self createRotorWithName:name elements:elements];
354
+ }
355
+ + (UIAccessibilityCustomRotor *)createLinkRotorWithElements:(NSArray *)elements name:(NSString *)name
356
+ {
357
+ return [self createRotorWithName:name elements:elements];
358
+ }
359
+ + (UIAccessibilityCustomRotor *)createImageRotorWithElements:(NSArray *)elements name:(NSString *)name
360
+ {
361
+ return [self createRotorWithName:name elements:elements];
362
+ }
363
+
364
+ + (NSArray<UIAccessibilityCustomRotor *> *)buildRotorsFromElements:(NSArray<UIAccessibilityElement *> *)elements
365
+ labels:(ENRMAccessibilityLabels *)labels
366
+ {
367
+ NSMutableArray<UIAccessibilityCustomRotor *> *rotors = [NSMutableArray array];
368
+
369
+ NSArray<UIAccessibilityElement *> *headingElements = [self filterHeadingElements:elements];
370
+ if (headingElements.count > 0) {
371
+ [rotors addObject:[self createHeadingRotorWithElements:headingElements name:labels.rotorHeadings]];
372
+ }
373
+
374
+ NSArray<UIAccessibilityElement *> *linkElements = [self filterLinkElements:elements];
375
+ if (linkElements.count > 0) {
376
+ [rotors addObject:[self createLinkRotorWithElements:linkElements name:labels.rotorLinks]];
377
+ }
378
+
379
+ NSArray<UIAccessibilityElement *> *imageElements = [self filterImageElements:elements];
380
+ if (imageElements.count > 0) {
381
+ [rotors addObject:[self createImageRotorWithElements:imageElements name:labels.rotorImages]];
382
+ }
383
+
384
+ return rotors;
385
+ }
386
+
387
+ #else
388
+
389
+ // TODO: Implement VoiceOver accessibility elements for macOS using NSAccessibility.
390
+ // This includes building heading, link, and image accessibility elements from AttributedString
391
+ // attributes, and exposing them via NSAccessibilityElement so VoiceOver can navigate the
392
+ // rendered tiptap content. The iOS implementation above can serve as a reference.
393
+
394
+ + (NSMutableArray *)buildElementsForTextView:(id)textView
395
+ info:(AccessibilityInfo *)info
396
+ labels:(ENRMAccessibilityLabels *)labels
397
+ container:(id)container
398
+ {
399
+ return [NSMutableArray array];
400
+ }
401
+ + (NSArray *)filterHeadingElements:(NSArray *)elements
402
+ {
403
+ return @[];
404
+ }
405
+ + (NSArray *)filterLinkElements:(NSArray *)elements
406
+ {
407
+ return @[];
408
+ }
409
+ + (NSArray *)filterImageElements:(NSArray *)elements
410
+ {
411
+ return @[];
412
+ }
413
+ + (id)createHeadingRotorWithElements:(NSArray *)elements name:(NSString *)name
414
+ {
415
+ return nil;
416
+ }
417
+ + (id)createLinkRotorWithElements:(NSArray *)elements name:(NSString *)name
418
+ {
419
+ return nil;
420
+ }
421
+ + (id)createImageRotorWithElements:(NSArray *)elements name:(NSString *)name
422
+ {
423
+ return nil;
424
+ }
425
+ + (NSArray *)buildRotorsFromElements:(NSArray *)elements labels:(ENRMAccessibilityLabels *)labels
426
+ {
427
+ return @[];
428
+ }
429
+
430
+ #endif
431
+
432
+ @end