@mathers101/react-native-render-rich-text 0.0.0-stage → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/README.md +140 -3
- package/ReactNativeRenderRichText.podspec +23 -0
- package/android/build.gradle +86 -0
- package/android/consumer-rules.pro +3 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/jni-adapter.cpp +191 -0
- package/android/src/main/java/com/renderrichtext/EnrichedTiptapText.kt +673 -0
- package/android/src/main/java/com/renderrichtext/EnrichedTiptapTextLayoutManager.kt +24 -0
- package/android/src/main/java/com/renderrichtext/EnrichedTiptapTextManager.kt +405 -0
- package/android/src/main/java/com/renderrichtext/EnrichedTiptapTextPackage.kt +13 -0
- package/android/src/main/java/com/renderrichtext/MeasurementStore.kt +644 -0
- package/android/src/main/java/com/renderrichtext/accessibility/AccessibilityLabels.kt +17 -0
- package/android/src/main/java/com/renderrichtext/accessibility/AccessibleTiptapTextView.kt +112 -0
- package/android/src/main/java/com/renderrichtext/accessibility/TiptapAccessibilityHelper.kt +576 -0
- package/android/src/main/java/com/renderrichtext/events/CodeBlockPressEvent.kt +24 -0
- package/android/src/main/java/com/renderrichtext/events/ContextMenuItemPressEvent.kt +28 -0
- package/android/src/main/java/com/renderrichtext/events/CopyPressEvent.kt +24 -0
- package/android/src/main/java/com/renderrichtext/events/FactoidPressEvent.kt +23 -0
- package/android/src/main/java/com/renderrichtext/events/ImagePressEvent.kt +25 -0
- package/android/src/main/java/com/renderrichtext/events/LatexErrorEvent.kt +29 -0
- package/android/src/main/java/com/renderrichtext/events/LinkLongPressEvent.kt +23 -0
- package/android/src/main/java/com/renderrichtext/events/LinkPressEvent.kt +23 -0
- package/android/src/main/java/com/renderrichtext/events/MentionLongPressEvent.kt +25 -0
- package/android/src/main/java/com/renderrichtext/events/MentionPressEvent.kt +25 -0
- package/android/src/main/java/com/renderrichtext/events/TaskListItemPressEvent.kt +26 -0
- package/android/src/main/java/com/renderrichtext/math/LatexErrorReporter.kt +9 -0
- package/android/src/main/java/com/renderrichtext/parser/Parser.kt +59 -0
- package/android/src/main/java/com/renderrichtext/parser/TextLinkMatching.kt +35 -0
- package/android/src/main/java/com/renderrichtext/parser/TiptapASTNode.kt +69 -0
- package/android/src/main/java/com/renderrichtext/renderer/BaselineShiftRenderer.kt +45 -0
- package/android/src/main/java/com/renderrichtext/renderer/BlankLineRenderer.kt +39 -0
- package/android/src/main/java/com/renderrichtext/renderer/BlockStyleContext.kt +181 -0
- package/android/src/main/java/com/renderrichtext/renderer/BlockquoteRenderer.kt +243 -0
- package/android/src/main/java/com/renderrichtext/renderer/BlockquoteTextRenderer.kt +56 -0
- package/android/src/main/java/com/renderrichtext/renderer/CodeBlockRenderer.kt +135 -0
- package/android/src/main/java/com/renderrichtext/renderer/CodeRenderer.kt +37 -0
- package/android/src/main/java/com/renderrichtext/renderer/EmphasisRenderer.kt +28 -0
- package/android/src/main/java/com/renderrichtext/renderer/FactoidRenderer.kt +97 -0
- package/android/src/main/java/com/renderrichtext/renderer/HeadingRenderer.kt +81 -0
- package/android/src/main/java/com/renderrichtext/renderer/HighlightRenderer.kt +26 -0
- package/android/src/main/java/com/renderrichtext/renderer/LineBreakRenderer.kt +17 -0
- package/android/src/main/java/com/renderrichtext/renderer/LinkRenderer.kt +90 -0
- package/android/src/main/java/com/renderrichtext/renderer/ListContextManager.kt +109 -0
- package/android/src/main/java/com/renderrichtext/renderer/ListItemRenderer.kt +211 -0
- package/android/src/main/java/com/renderrichtext/renderer/ListRenderer.kt +133 -0
- package/android/src/main/java/com/renderrichtext/renderer/MentionRenderer.kt +85 -0
- package/android/src/main/java/com/renderrichtext/renderer/NodeRenderer.kt +207 -0
- package/android/src/main/java/com/renderrichtext/renderer/ParagraphRenderer.kt +91 -0
- package/android/src/main/java/com/renderrichtext/renderer/Renderer.kt +148 -0
- package/android/src/main/java/com/renderrichtext/renderer/SoftBreakRenderer.kt +17 -0
- package/android/src/main/java/com/renderrichtext/renderer/SpanStyleCache.kt +143 -0
- package/android/src/main/java/com/renderrichtext/renderer/StrikethroughRenderer.kt +28 -0
- package/android/src/main/java/com/renderrichtext/renderer/StrongRenderer.kt +28 -0
- package/android/src/main/java/com/renderrichtext/renderer/TextRenderer.kt +31 -0
- package/android/src/main/java/com/renderrichtext/renderer/ThematicBreakRenderer.kt +46 -0
- package/android/src/main/java/com/renderrichtext/renderer/UnderlineRenderer.kt +28 -0
- package/android/src/main/java/com/renderrichtext/spans/BaseListSpan.kt +164 -0
- package/android/src/main/java/com/renderrichtext/spans/BaselineShiftSpan.kt +27 -0
- package/android/src/main/java/com/renderrichtext/spans/BlockquoteSpan.kt +302 -0
- package/android/src/main/java/com/renderrichtext/spans/CodeBackgroundSpan.kt +205 -0
- package/android/src/main/java/com/renderrichtext/spans/CodeBlockSpan.kt +216 -0
- package/android/src/main/java/com/renderrichtext/spans/CodeSpan.kt +32 -0
- package/android/src/main/java/com/renderrichtext/spans/EmphasisSpan.kt +42 -0
- package/android/src/main/java/com/renderrichtext/spans/FactoidBackgroundSpan.kt +265 -0
- package/android/src/main/java/com/renderrichtext/spans/FactoidPaddingSpan.kt +258 -0
- package/android/src/main/java/com/renderrichtext/spans/FactoidSpan.kt +32 -0
- package/android/src/main/java/com/renderrichtext/spans/FadeInSpan.kt +29 -0
- package/android/src/main/java/com/renderrichtext/spans/HeadingSpan.kt +38 -0
- package/android/src/main/java/com/renderrichtext/spans/HighlightSpan.kt +80 -0
- package/android/src/main/java/com/renderrichtext/spans/ImageSpan.kt +481 -0
- package/android/src/main/java/com/renderrichtext/spans/InlineBackgroundGeometry.kt +42 -0
- package/android/src/main/java/com/renderrichtext/spans/LineHeightSpan.kt +39 -0
- package/android/src/main/java/com/renderrichtext/spans/LinkPillIconCache.kt +154 -0
- package/android/src/main/java/com/renderrichtext/spans/LinkPillSpan.kt +381 -0
- package/android/src/main/java/com/renderrichtext/spans/LinkSpan.kt +68 -0
- package/android/src/main/java/com/renderrichtext/spans/ListItemSpacingSpan.kt +33 -0
- package/android/src/main/java/com/renderrichtext/spans/ListMarkerAnchorSpan.kt +38 -0
- package/android/src/main/java/com/renderrichtext/spans/MarginBottomSpan.kt +76 -0
- package/android/src/main/java/com/renderrichtext/spans/MentionChipSpan.kt +132 -0
- package/android/src/main/java/com/renderrichtext/spans/MentionSpan.kt +46 -0
- package/android/src/main/java/com/renderrichtext/spans/OrderedListSpan.kt +127 -0
- package/android/src/main/java/com/renderrichtext/spans/SpoilerSpan.kt +33 -0
- package/android/src/main/java/com/renderrichtext/spans/StrikethroughSpan.kt +12 -0
- package/android/src/main/java/com/renderrichtext/spans/StrongSpan.kt +39 -0
- package/android/src/main/java/com/renderrichtext/spans/TaskListSpan.kt +143 -0
- package/android/src/main/java/com/renderrichtext/spans/TextSpan.kt +26 -0
- package/android/src/main/java/com/renderrichtext/spans/ThematicBreakSpan.kt +69 -0
- package/android/src/main/java/com/renderrichtext/spans/UnorderedListSpan.kt +96 -0
- package/android/src/main/java/com/renderrichtext/spoiler/SpoilerStubs.kt +46 -0
- package/android/src/main/java/com/renderrichtext/styles/BaseBlockStyle.kt +11 -0
- package/android/src/main/java/com/renderrichtext/styles/BlockquoteStyle.kt +83 -0
- package/android/src/main/java/com/renderrichtext/styles/CodeBlockStyle.kt +80 -0
- package/android/src/main/java/com/renderrichtext/styles/CodeStyle.kt +26 -0
- package/android/src/main/java/com/renderrichtext/styles/EmphasisStyle.kt +21 -0
- package/android/src/main/java/com/renderrichtext/styles/FactoidStyle.kt +21 -0
- package/android/src/main/java/com/renderrichtext/styles/HeadingStyle.kt +33 -0
- package/android/src/main/java/com/renderrichtext/styles/HighlightStyle.kt +19 -0
- package/android/src/main/java/com/renderrichtext/styles/ImageStyle.kt +29 -0
- package/android/src/main/java/com/renderrichtext/styles/InlineImageStyle.kt +17 -0
- package/android/src/main/java/com/renderrichtext/styles/InlineMathStyle.kt +17 -0
- package/android/src/main/java/com/renderrichtext/styles/LinkPillContent.kt +36 -0
- package/android/src/main/java/com/renderrichtext/styles/LinkPillStyle.kt +44 -0
- package/android/src/main/java/com/renderrichtext/styles/LinkStyle.kt +23 -0
- package/android/src/main/java/com/renderrichtext/styles/LinkVariantEntry.kt +34 -0
- package/android/src/main/java/com/renderrichtext/styles/ListStyle.kt +65 -0
- package/android/src/main/java/com/renderrichtext/styles/MathStyle.kt +29 -0
- package/android/src/main/java/com/renderrichtext/styles/MentionStyle.kt +19 -0
- package/android/src/main/java/com/renderrichtext/styles/ParagraphStyle.kt +33 -0
- package/android/src/main/java/com/renderrichtext/styles/SpoilerStyle.kt +27 -0
- package/android/src/main/java/com/renderrichtext/styles/StrikethroughStyle.kt +17 -0
- package/android/src/main/java/com/renderrichtext/styles/StrongStyle.kt +21 -0
- package/android/src/main/java/com/renderrichtext/styles/StyleConfig.kt +411 -0
- package/android/src/main/java/com/renderrichtext/styles/StyleParser.kt +92 -0
- package/android/src/main/java/com/renderrichtext/styles/SubscriptStyle.kt +16 -0
- package/android/src/main/java/com/renderrichtext/styles/SuperscriptStyle.kt +16 -0
- package/android/src/main/java/com/renderrichtext/styles/TableStyle.kt +75 -0
- package/android/src/main/java/com/renderrichtext/styles/TaskListStyle.kt +29 -0
- package/android/src/main/java/com/renderrichtext/styles/TextAlignment.kt +32 -0
- package/android/src/main/java/com/renderrichtext/styles/ThematicBreakStyle.kt +23 -0
- package/android/src/main/java/com/renderrichtext/styles/UnderlineStyle.kt +17 -0
- package/android/src/main/java/com/renderrichtext/styles/VideoStyle.kt +31 -0
- package/android/src/main/java/com/renderrichtext/utils/common/BorderStyleUtils.kt +86 -0
- package/android/src/main/java/com/renderrichtext/utils/common/BreakStrategyUtils.kt +32 -0
- package/android/src/main/java/com/renderrichtext/utils/common/CodeBlockHighlighter.kt +36 -0
- package/android/src/main/java/com/renderrichtext/utils/common/CodeBlockNode.kt +42 -0
- package/android/src/main/java/com/renderrichtext/utils/common/EllipsizeUtils.kt +27 -0
- package/android/src/main/java/com/renderrichtext/utils/common/EnrichedTiptapAncestorUtils.kt +10 -0
- package/android/src/main/java/com/renderrichtext/utils/common/FeatureFlags.kt +9 -0
- package/android/src/main/java/com/renderrichtext/utils/common/LinkRecognitionProps.kt +31 -0
- package/android/src/main/java/com/renderrichtext/utils/common/LinkRegexConfig.kt +56 -0
- package/android/src/main/java/com/renderrichtext/utils/common/MotionPreferences.kt +18 -0
- package/android/src/main/java/com/renderrichtext/utils/common/ReadableMapExtensions.kt +28 -0
- package/android/src/main/java/com/renderrichtext/utils/common/TiptapViewManagerUtils.kt +242 -0
- package/android/src/main/java/com/renderrichtext/utils/common/layout/RTLUtils.kt +6 -0
- package/android/src/main/java/com/renderrichtext/utils/common/serialization/TiptapASTSerializer.kt +368 -0
- package/android/src/main/java/com/renderrichtext/utils/text/ImageCache.kt +88 -0
- package/android/src/main/java/com/renderrichtext/utils/text/ImageDownloader.kt +203 -0
- package/android/src/main/java/com/renderrichtext/utils/text/LocalImageLoader.kt +167 -0
- package/android/src/main/java/com/renderrichtext/utils/text/TailFadeInAnimator.kt +84 -0
- package/android/src/main/java/com/renderrichtext/utils/text/conversion/HTMLGenerator.kt +1105 -0
- package/android/src/main/java/com/renderrichtext/utils/text/conversion/TiptapExtractor.kt +423 -0
- package/android/src/main/java/com/renderrichtext/utils/text/extensions/ParagraphUtils.kt +9 -0
- package/android/src/main/java/com/renderrichtext/utils/text/extensions/SpannableExtensions.kt +9 -0
- package/android/src/main/java/com/renderrichtext/utils/text/extensions/TextPaintExtensions.kt +76 -0
- package/android/src/main/java/com/renderrichtext/utils/text/interaction/CheckboxTouchHelper.kt +61 -0
- package/android/src/main/java/com/renderrichtext/utils/text/interaction/TaskListTapUtils.kt +286 -0
- package/android/src/main/java/com/renderrichtext/utils/text/span/ApplyLineHeight.kt +39 -0
- package/android/src/main/java/com/renderrichtext/utils/text/span/BlockSpacing.kt +142 -0
- package/android/src/main/java/com/renderrichtext/utils/text/span/SpanFlags.kt +43 -0
- package/android/src/main/java/com/renderrichtext/utils/text/span/WidthAwareSpans.kt +30 -0
- package/android/src/main/java/com/renderrichtext/utils/text/view/CodeBlockPressHost.kt +15 -0
- package/android/src/main/java/com/renderrichtext/utils/text/view/ImagePressHost.kt +19 -0
- package/android/src/main/java/com/renderrichtext/utils/text/view/LinkEvents.kt +88 -0
- package/android/src/main/java/com/renderrichtext/utils/text/view/LinkLongPressMovementMethod.kt +449 -0
- package/android/src/main/java/com/renderrichtext/utils/text/view/SelectionActionMode.kt +235 -0
- package/android/src/main/java/com/renderrichtext/utils/text/view/TextSelectionColors.kt +48 -0
- package/android/src/main/java/com/renderrichtext/utils/text/view/TextViewSetup.kt +119 -0
- package/android/src/main/java/com/renderrichtext/views/ContextMenuPopup.kt +217 -0
- package/android/src/main/java/com/renderrichtext/views/CopyGlyph.kt +101 -0
- package/android/src/main/java/com/renderrichtext/views/WrapGlyph.kt +40 -0
- package/android/src/main/jni/CMakeLists.txt +52 -0
- package/android/src/main/jni/react/renderer/components/RichTextSpec/ComponentDescriptors.h +30 -0
- package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextComponentDescriptor.h +29 -0
- package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextMeasurementManager.cpp +45 -0
- package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextMeasurementManager.h +21 -0
- package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextShadowNode.cpp +38 -0
- package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextShadowNode.h +53 -0
- package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextState.cpp +9 -0
- package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextState.h +24 -0
- package/android/src/main/jni/react/renderer/components/RichTextSpec/conversions.h +40 -0
- package/cpp/parser/TiptapASTNode.hpp +68 -0
- package/cpp/parser/TiptapParser.cpp +233 -0
- package/cpp/parser/TiptapParser.hpp +13 -0
- package/cpp/vendor/nlohmann/json.hpp +24765 -0
- package/ios/EnrichedTiptapText.h +27 -0
- package/ios/EnrichedTiptapText.mm +1222 -0
- package/ios/attachments/ENRMImageAttachment.h +51 -0
- package/ios/attachments/ENRMImageAttachment.m +398 -0
- package/ios/attachments/ENRMLinkPillAttachment.h +47 -0
- package/ios/attachments/ENRMLinkPillAttachment.m +324 -0
- package/ios/attachments/ENRMLinkPillIconCache.h +28 -0
- package/ios/attachments/ENRMLinkPillIconCache.m +181 -0
- package/ios/attachments/ENRMLinkPillText.h +27 -0
- package/ios/attachments/ENRMLinkPillText.m +118 -0
- package/ios/attachments/ENRMMathInlineAttachment.h +7 -0
- package/ios/attachments/ENRMMathInlineAttachment.m +4 -0
- package/ios/attachments/ThematicBreakAttachment.h +15 -0
- package/ios/attachments/ThematicBreakAttachment.m +33 -0
- package/ios/internals/ENRMViewFreeMeasurement.h +208 -0
- package/ios/internals/EnrichedTiptapTextComponentDescriptor.h +19 -0
- package/ios/internals/EnrichedTiptapTextShadowNode.h +43 -0
- package/ios/internals/EnrichedTiptapTextShadowNode.mm +69 -0
- package/ios/internals/EnrichedTiptapTextState.h +24 -0
- package/ios/internals/MeasurementCache.h +308 -0
- package/ios/internals/ShadowMeasurementUtils.h +145 -0
- package/ios/parser/ENRMTextLinkAttributes.h +7 -0
- package/ios/parser/ENRMTiptapParser.h +33 -0
- package/ios/parser/ENRMTiptapParser.mm +73 -0
- package/ios/parser/TiptapASTNode.h +57 -0
- package/ios/parser/TiptapASTNode.m +52 -0
- package/ios/parser/TiptapParserBridge.mm +179 -0
- package/ios/renderer/AttributedRenderer.h +23 -0
- package/ios/renderer/AttributedRenderer.m +229 -0
- package/ios/renderer/BaseRenderer.h +33 -0
- package/ios/renderer/BaseRenderer.m +46 -0
- package/ios/renderer/BaselineShiftTextAttributes.h +10 -0
- package/ios/renderer/BaselineShiftTextAttributes.m +26 -0
- package/ios/renderer/BlankLineRenderer.h +4 -0
- package/ios/renderer/BlankLineRenderer.m +51 -0
- package/ios/renderer/BlockquoteRenderer.h +6 -0
- package/ios/renderer/BlockquoteRenderer.m +258 -0
- package/ios/renderer/CodeBlockRenderer.h +9 -0
- package/ios/renderer/CodeBlockRenderer.m +99 -0
- package/ios/renderer/CodeRenderer.h +9 -0
- package/ios/renderer/CodeRenderer.m +69 -0
- package/ios/renderer/ENRMBlockquoteTextRenderer.h +25 -0
- package/ios/renderer/ENRMBlockquoteTextRenderer.m +48 -0
- package/ios/renderer/EmphasisRenderer.h +5 -0
- package/ios/renderer/EmphasisRenderer.m +100 -0
- package/ios/renderer/FactoidRenderer.h +9 -0
- package/ios/renderer/FactoidRenderer.m +130 -0
- package/ios/renderer/HeadingRenderer.h +6 -0
- package/ios/renderer/HeadingRenderer.m +103 -0
- package/ios/renderer/HighlightRenderer.h +7 -0
- package/ios/renderer/HighlightRenderer.m +33 -0
- package/ios/renderer/LinkRenderer.h +6 -0
- package/ios/renderer/LinkRenderer.m +158 -0
- package/ios/renderer/ListItemRenderer.h +24 -0
- package/ios/renderer/ListItemRenderer.m +283 -0
- package/ios/renderer/ListRenderer.h +13 -0
- package/ios/renderer/ListRenderer.m +126 -0
- package/ios/renderer/MentionRenderer.h +9 -0
- package/ios/renderer/MentionRenderer.m +96 -0
- package/ios/renderer/NodeRenderer.h +8 -0
- package/ios/renderer/ParagraphRenderer.h +6 -0
- package/ios/renderer/ParagraphRenderer.m +85 -0
- package/ios/renderer/RenderContext.h +145 -0
- package/ios/renderer/RenderContext.m +403 -0
- package/ios/renderer/RendererFactory.h +12 -0
- package/ios/renderer/RendererFactory.m +158 -0
- package/ios/renderer/StrikethroughRenderer.h +5 -0
- package/ios/renderer/StrikethroughRenderer.m +30 -0
- package/ios/renderer/StrongRenderer.h +5 -0
- package/ios/renderer/StrongRenderer.m +88 -0
- package/ios/renderer/TextRenderer.h +6 -0
- package/ios/renderer/TextRenderer.m +18 -0
- package/ios/renderer/ThematicBreakRenderer.h +4 -0
- package/ios/renderer/ThematicBreakRenderer.m +42 -0
- package/ios/renderer/UnderlineRenderer.h +4 -0
- package/ios/renderer/UnderlineRenderer.m +41 -0
- package/ios/styles/ENRMFontSlot.h +12 -0
- package/ios/styles/ENRMFontSlot.m +24 -0
- package/ios/styles/StyleConfig.h +488 -0
- package/ios/styles/StyleConfig.mm +2820 -0
- package/ios/utils/AccessibilityInfo.h +35 -0
- package/ios/utils/AccessibilityInfo.m +24 -0
- package/ios/utils/BlockquoteBorder.h +26 -0
- package/ios/utils/BlockquoteBorder.m +294 -0
- package/ios/utils/CodeBackground.h +19 -0
- package/ios/utils/CodeBackground.m +197 -0
- package/ios/utils/CodeBlockBackground.h +17 -0
- package/ios/utils/CodeBlockBackground.m +103 -0
- package/ios/utils/ContextMenuUtils.h +76 -0
- package/ios/utils/ContextMenuUtils.mm +75 -0
- package/ios/utils/ENRMAccessibilityLabels.h +40 -0
- package/ios/utils/ENRMAccessibilityLabels.m +5 -0
- package/ios/utils/ENRMAsyncRenderCoordinator.h +24 -0
- package/ios/utils/ENRMAsyncRenderCoordinator.m +37 -0
- package/ios/utils/ENRMAtomicSize.h +65 -0
- package/ios/utils/ENRMCodeBlockContent.h +37 -0
- package/ios/utils/ENRMCodeBlockContent.mm +89 -0
- package/ios/utils/ENRMFeatureFlags.h +25 -0
- package/ios/utils/ENRMImageDownloader.h +30 -0
- package/ios/utils/ENRMImageDownloader.m +168 -0
- package/ios/utils/ENRMLatexErrorCoordinator.h +27 -0
- package/ios/utils/ENRMLatexErrorCoordinator.mm +75 -0
- package/ios/utils/ENRMLatexErrorReporting.h +13 -0
- package/ios/utils/ENRMLinkContextMenus.h +93 -0
- package/ios/utils/ENRMLinkContextMenus.m +178 -0
- package/ios/utils/ENRMLinkRegexConfig.h +30 -0
- package/ios/utils/ENRMLinkRegexConfig.mm +104 -0
- package/ios/utils/ENRMLinkRegexProps.h +23 -0
- package/ios/utils/ENRMLocalImageLoader.h +35 -0
- package/ios/utils/ENRMLocalImageLoader.m +111 -0
- package/ios/utils/ENRMMenuAction.h +27 -0
- package/ios/utils/ENRMMenuAction.m +33 -0
- package/ios/utils/ENRMSpoilerOverlayManager.h +15 -0
- package/ios/utils/ENRMSpoilerOverlayManager.m +19 -0
- package/ios/utils/ENRMSpoilerTapUtils.h +6 -0
- package/ios/utils/ENRMSpoilerTapUtils.m +11 -0
- package/ios/utils/ENRMTailFadeInAnimator.h +16 -0
- package/ios/utils/ENRMTailFadeInAnimator.m +161 -0
- package/ios/utils/ENRMTextHitTest.h +23 -0
- package/ios/utils/ENRMTextHitTest.m +20 -0
- package/ios/utils/ENRMTextInteractionUtils.h +20 -0
- package/ios/utils/ENRMTextInteractionUtils.m +15 -0
- package/ios/utils/ENRMTextRenderer.h +38 -0
- package/ios/utils/ENRMTextRenderer.m +54 -0
- package/ios/utils/ENRMTextViewSetup.h +132 -0
- package/ios/utils/ENRMUIKit.h +429 -0
- package/ios/utils/EditMenuUtils.h +105 -0
- package/ios/utils/EditMenuUtils.m +143 -0
- package/ios/utils/FactoidBackground.h +20 -0
- package/ios/utils/FactoidBackground.m +161 -0
- package/ios/utils/FontScaleObserver.h +10 -0
- package/ios/utils/FontScaleObserver.m +55 -0
- package/ios/utils/FontUtils.h +31 -0
- package/ios/utils/FontUtils.m +63 -0
- package/ios/utils/HTMLGenerator.h +25 -0
- package/ios/utils/HTMLGenerator.m +1041 -0
- package/ios/utils/HeightUpdateUtils.h +25 -0
- package/ios/utils/ImageRequestHeaderUtils.h +35 -0
- package/ios/utils/LastElementUtils.h +97 -0
- package/ios/utils/LinkContextMenuUtils.h +55 -0
- package/ios/utils/LinkPillContentUtils.h +46 -0
- package/ios/utils/LinkTapUtils.h +56 -0
- package/ios/utils/LinkTapUtils.m +139 -0
- package/ios/utils/ListMarkerDrawer.h +15 -0
- package/ios/utils/ListMarkerDrawer.m +239 -0
- package/ios/utils/MentionBackground.h +26 -0
- package/ios/utils/MentionBackground.m +86 -0
- package/ios/utils/ParagraphStyleUtils.h +87 -0
- package/ios/utils/ParagraphStyleUtils.m +484 -0
- package/ios/utils/PasteboardUtils.h +48 -0
- package/ios/utils/PasteboardUtils.m +166 -0
- package/ios/utils/RTFExportUtils.h +24 -0
- package/ios/utils/RTFExportUtils.m +299 -0
- package/ios/utils/RuntimeKeys.h +44 -0
- package/ios/utils/RuntimeKeys.m +13 -0
- package/ios/utils/SelectionColorUtils.h +14 -0
- package/ios/utils/StylePropsUtils.h +1340 -0
- package/ios/utils/TaskListTapUtils.h +6 -0
- package/ios/utils/TaskListTapUtils.m +14 -0
- package/ios/utils/TextViewLayoutManager.h +14 -0
- package/ios/utils/TextViewLayoutManager.mm +198 -0
- package/ios/utils/TiptapAccessibilityElementBuilder.h +43 -0
- package/ios/utils/TiptapAccessibilityElementBuilder.m +432 -0
- package/ios/utils/TiptapExtractor.h +17 -0
- package/ios/utils/TiptapExtractor.m +367 -0
- package/ios/views/ENRMTiptapTextView.h +25 -0
- package/ios/views/ENRMTiptapTextView.m +247 -0
- package/package.json +70 -6
- package/react-native.config.js +10 -0
- package/src/EnrichedTiptapTextNativeComponent.ts +739 -0
- package/src/RichText.tsx +164 -0
- package/src/admonitionDefaults.ts +58 -0
- package/src/darkModeTextStyle.ts +99 -0
- package/src/headingDefaults.ts +21 -0
- package/src/index.tsx +4 -0
- package/src/linkVariantUtils.ts +120 -0
- package/src/normalizeTiptapStyle.ts +397 -0
- package/src/rn-globals.d.ts +1 -0
- package/src/styleUtils.ts +107 -0
- package/src/types/TiptapStyle.ts +532 -0
- package/src/types/TiptapStyleInternal.ts +299 -0
- package/src/types/linkPill.ts +9 -0
- package/src/utils/isDev.ts +4 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
package com.renderrichtext.accessibility
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.Rect
|
|
5
|
+
import android.util.AttributeSet
|
|
6
|
+
import android.view.KeyEvent
|
|
7
|
+
import android.view.MotionEvent
|
|
8
|
+
import androidx.appcompat.widget.AppCompatTextView
|
|
9
|
+
import com.renderrichtext.spans.LinkPillSpan
|
|
10
|
+
import com.renderrichtext.utils.text.span.prepareWidthAwareSpans
|
|
11
|
+
|
|
12
|
+
/** AppCompatTextView with built-in TalkBack support via TiptapAccessibilityHelper. */
|
|
13
|
+
abstract class AccessibleTiptapTextView
|
|
14
|
+
@JvmOverloads
|
|
15
|
+
constructor(
|
|
16
|
+
context: Context,
|
|
17
|
+
attrs: AttributeSet? = null,
|
|
18
|
+
defStyleAttr: Int = 0,
|
|
19
|
+
) : AppCompatTextView(context, attrs, defStyleAttr) {
|
|
20
|
+
val accessibilityHelper = TiptapAccessibilityHelper(this)
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* True only while [requestFocus] is on the stack. A tap focuses this view, and the
|
|
24
|
+
* surrounding scroll view then brings the focused child on screen by its drawing
|
|
25
|
+
* rect. This view is as tall as its text, so that scroll aligns the text with the
|
|
26
|
+
* top of the viewport and the tapped line jumps upward. For that one call, the
|
|
27
|
+
* drawing rect is the part already on screen, and the scroll delta is zero.
|
|
28
|
+
* Dragging a selection still scrolls: that path asks for the caret's rectangle,
|
|
29
|
+
* not this drawing rect.
|
|
30
|
+
*/
|
|
31
|
+
private var reportingVisibleFocusRect = false
|
|
32
|
+
|
|
33
|
+
override fun setText(
|
|
34
|
+
text: CharSequence?,
|
|
35
|
+
type: BufferType?,
|
|
36
|
+
) {
|
|
37
|
+
// Prime pills before the layout for the new text is built, and register for icons
|
|
38
|
+
// that load later. Runs from the TextView constructor too: use nothing initialized after it.
|
|
39
|
+
val contentWidth = width - compoundPaddingLeft - compoundPaddingRight
|
|
40
|
+
if (contentWidth > 0) prepareWidthAwareSpans(text, contentWidth, includeImages = false)
|
|
41
|
+
LinkPillSpan.registerView(text, this)
|
|
42
|
+
super.setText(text, type)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
override fun onMeasure(
|
|
46
|
+
widthMeasureSpec: Int,
|
|
47
|
+
heightMeasureSpec: Int,
|
|
48
|
+
) {
|
|
49
|
+
if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) {
|
|
50
|
+
val width = (MeasureSpec.getSize(widthMeasureSpec) - compoundPaddingLeft - compoundPaddingRight).coerceAtLeast(1)
|
|
51
|
+
// TextView keeps its layout when the new width still fits the old lines.
|
|
52
|
+
if (prepareWidthAwareSpans(text, width, includeImages = false) && layout != null) dropTextLayout()
|
|
53
|
+
}
|
|
54
|
+
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Makes the next measure build a fresh text layout. TextView exposes no direct way:
|
|
59
|
+
* re-setting the text would copy it and clear the selection, while setting the break
|
|
60
|
+
* strategy, even to its current value, discards the layout and nothing else.
|
|
61
|
+
*/
|
|
62
|
+
private fun dropTextLayout() {
|
|
63
|
+
breakStrategy = breakStrategy
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
override fun dispatchHoverEvent(event: MotionEvent): Boolean =
|
|
67
|
+
accessibilityHelper.dispatchHoverEvent(event) || super.dispatchHoverEvent(event)
|
|
68
|
+
|
|
69
|
+
override fun dispatchKeyEvent(event: KeyEvent): Boolean = accessibilityHelper.dispatchKeyEvent(event) || super.dispatchKeyEvent(event)
|
|
70
|
+
|
|
71
|
+
override fun requestFocus(
|
|
72
|
+
direction: Int,
|
|
73
|
+
previouslyFocusedRect: Rect?,
|
|
74
|
+
): Boolean {
|
|
75
|
+
reportingVisibleFocusRect = true
|
|
76
|
+
return try {
|
|
77
|
+
super.requestFocus(direction, previouslyFocusedRect)
|
|
78
|
+
} finally {
|
|
79
|
+
reportingVisibleFocusRect = false
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
override fun getDrawingRect(outRect: Rect) {
|
|
84
|
+
super.getDrawingRect(outRect)
|
|
85
|
+
if (!reportingVisibleFocusRect) return
|
|
86
|
+
val visibleOnScreen = Rect()
|
|
87
|
+
if (!getGlobalVisibleRect(visibleOnScreen)) return
|
|
88
|
+
val screenOrigin = IntArray(2)
|
|
89
|
+
getLocationOnScreen(screenOrigin)
|
|
90
|
+
visibleOnScreen.offset(-screenOrigin[0] + scrollX, -screenOrigin[1] + scrollY)
|
|
91
|
+
if (!outRect.intersect(visibleOnScreen)) super.getDrawingRect(outRect)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
override fun onFocusChanged(
|
|
95
|
+
gainFocus: Boolean,
|
|
96
|
+
direction: Int,
|
|
97
|
+
previouslyFocusedRect: Rect?,
|
|
98
|
+
) {
|
|
99
|
+
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect)
|
|
100
|
+
accessibilityHelper.onFocusChanged(gainFocus, direction, previouslyFocusedRect)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
override fun onDetachedFromWindow() {
|
|
104
|
+
// Before super, and here rather than in a ViewManager's
|
|
105
|
+
// onDropViewInstance: getViewTreeObserver() returns the WINDOW's observer
|
|
106
|
+
// only while the view is attached. Once detached it hands back the view's
|
|
107
|
+
// own floating observer, so a removal then would target the wrong
|
|
108
|
+
// observer and leave the listener on the window.
|
|
109
|
+
accessibilityHelper.cleanup()
|
|
110
|
+
super.onDetachedFromWindow()
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
package com.renderrichtext.accessibility
|
|
2
|
+
|
|
3
|
+
import android.graphics.Rect
|
|
4
|
+
import android.os.Bundle
|
|
5
|
+
import android.text.Layout
|
|
6
|
+
import android.text.Spanned
|
|
7
|
+
import android.view.View
|
|
8
|
+
import android.view.ViewTreeObserver
|
|
9
|
+
import android.widget.TextView
|
|
10
|
+
import androidx.core.view.ViewCompat
|
|
11
|
+
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
|
|
12
|
+
import androidx.customview.widget.ExploreByTouchHelper
|
|
13
|
+
import com.renderrichtext.spans.BaseListSpan
|
|
14
|
+
import com.renderrichtext.spans.BlockquoteSpan
|
|
15
|
+
import com.renderrichtext.spans.HeadingSpan
|
|
16
|
+
import com.renderrichtext.spans.ImageSpan
|
|
17
|
+
import com.renderrichtext.spans.LinkPillSpan
|
|
18
|
+
import com.renderrichtext.spans.LinkSpan
|
|
19
|
+
import com.renderrichtext.spans.OrderedListSpan
|
|
20
|
+
|
|
21
|
+
class TiptapAccessibilityHelper(
|
|
22
|
+
private val textView: TextView,
|
|
23
|
+
) : ExploreByTouchHelper(textView) {
|
|
24
|
+
private var items: List<AccessibilityItem> = emptyList()
|
|
25
|
+
private var needsRebuild = false
|
|
26
|
+
private var lastLayoutHashCode = 0
|
|
27
|
+
private var pendingLayoutListener: ViewTreeObserver.OnGlobalLayoutListener? = null
|
|
28
|
+
|
|
29
|
+
var labels: AccessibilityLabels = AccessibilityLabels()
|
|
30
|
+
set(value) {
|
|
31
|
+
if (field == value) return
|
|
32
|
+
field = value
|
|
33
|
+
invalidateAccessibilityItems()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
data class AccessibilityItem(
|
|
37
|
+
val id: Int,
|
|
38
|
+
val text: String,
|
|
39
|
+
/** Full character range — used for hit-testing so there are no gaps between items. */
|
|
40
|
+
val start: Int,
|
|
41
|
+
val end: Int,
|
|
42
|
+
/** Trimmed character range — used for bounds calculation to avoid whitespace offsets. */
|
|
43
|
+
val visibleStart: Int = start,
|
|
44
|
+
val visibleEnd: Int = end,
|
|
45
|
+
val headingLevel: Int = 0,
|
|
46
|
+
val linkUrl: String? = null,
|
|
47
|
+
val listInfo: ListItemInfo? = null,
|
|
48
|
+
val imageAltText: String? = null,
|
|
49
|
+
) {
|
|
50
|
+
val isHeading get() = headingLevel > 0
|
|
51
|
+
val isLink get() = linkUrl != null
|
|
52
|
+
val isListItem get() = listInfo != null
|
|
53
|
+
val isImage get() = imageAltText != null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
data class ListItemInfo(
|
|
57
|
+
val isOrdered: Boolean,
|
|
58
|
+
val itemNumber: Int,
|
|
59
|
+
val depth: Int,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
private data class SpanRange(
|
|
63
|
+
val start: Int,
|
|
64
|
+
val end: Int,
|
|
65
|
+
val headingLevel: Int = 0,
|
|
66
|
+
val linkUrl: String? = null,
|
|
67
|
+
val imageAltText: String? = null,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
fun invalidateAccessibilityItems() {
|
|
71
|
+
needsRebuild = true
|
|
72
|
+
if (textView.layout != null) {
|
|
73
|
+
rebuildIfNeeded()
|
|
74
|
+
invalidateRoot()
|
|
75
|
+
} else {
|
|
76
|
+
schedulePostLayoutRebuild()
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private fun schedulePostLayoutRebuild() {
|
|
81
|
+
if (pendingLayoutListener != null) return
|
|
82
|
+
val observer = textView.viewTreeObserver
|
|
83
|
+
if (!observer.isAlive) return
|
|
84
|
+
val listener =
|
|
85
|
+
ViewTreeObserver.OnGlobalLayoutListener {
|
|
86
|
+
removePendingLayoutListener()
|
|
87
|
+
if (needsRebuild) {
|
|
88
|
+
rebuildIfNeeded()
|
|
89
|
+
invalidateRoot()
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
pendingLayoutListener = listener
|
|
93
|
+
observer.addOnGlobalLayoutListener(listener)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Drops the pending global-layout listener, if any is still registered.
|
|
98
|
+
*
|
|
99
|
+
* Must run while the view is attached: `textView.viewTreeObserver` returns
|
|
100
|
+
* the window's observer only until detach, and the listener was merged into
|
|
101
|
+
* it from the view's floating observer when the view attached.
|
|
102
|
+
*/
|
|
103
|
+
fun cleanup() {
|
|
104
|
+
removePendingLayoutListener()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private fun removePendingLayoutListener() {
|
|
108
|
+
val listener = pendingLayoutListener ?: return
|
|
109
|
+
pendingLayoutListener = null
|
|
110
|
+
val observer = textView.viewTreeObserver
|
|
111
|
+
if (observer.isAlive) {
|
|
112
|
+
observer.removeOnGlobalLayoutListener(listener)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* When virtual children exist, prevent the host TextView from appearing as a
|
|
118
|
+
* standalone focusable element in TalkBack's swipe order. Without this,
|
|
119
|
+
* `setTextIsSelectable(true)` keeps the view focusable and TalkBack reads
|
|
120
|
+
* the entire text as one element before entering the virtual hierarchy.
|
|
121
|
+
*/
|
|
122
|
+
private fun updateHostFocusability() {
|
|
123
|
+
val hasVirtualChildren = items.isNotEmpty()
|
|
124
|
+
ViewCompat.setScreenReaderFocusable(textView, !hasVirtualChildren)
|
|
125
|
+
textView.importantForAccessibility =
|
|
126
|
+
if (hasVirtualChildren) View.IMPORTANT_FOR_ACCESSIBILITY_YES else View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private fun rebuildIfNeeded() {
|
|
130
|
+
val layout = textView.layout ?: return
|
|
131
|
+
if (needsRebuild || lastLayoutHashCode != layout.hashCode()) {
|
|
132
|
+
items = buildItems()
|
|
133
|
+
needsRebuild = false
|
|
134
|
+
lastLayoutHashCode = layout.hashCode()
|
|
135
|
+
updateHostFocusability()
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private fun buildItems(): List<AccessibilityItem> {
|
|
140
|
+
val spanned = textView.text as? Spanned ?: return emptyList()
|
|
141
|
+
if (spanned.isEmpty()) return emptyList()
|
|
142
|
+
|
|
143
|
+
val text = spanned.toString()
|
|
144
|
+
val endLimit = visibleTextLength(text.length)
|
|
145
|
+
val result = mutableListOf<AccessibilityItem>()
|
|
146
|
+
var nextId = 0
|
|
147
|
+
val semanticSpans = collectSemanticSpans(spanned)
|
|
148
|
+
|
|
149
|
+
var paraStart = 0
|
|
150
|
+
while (paraStart < endLimit) {
|
|
151
|
+
val newlineIdx = text.indexOf('\n', paraStart)
|
|
152
|
+
val paraEnd = minOf(if (newlineIdx == -1) text.length else newlineIdx + 1, endLimit)
|
|
153
|
+
val trimmed = text.substring(paraStart, paraEnd).trim()
|
|
154
|
+
|
|
155
|
+
if (trimmed.isNotEmpty()) {
|
|
156
|
+
val spansInParagraph =
|
|
157
|
+
semanticSpans
|
|
158
|
+
.filter { it.start < paraEnd && it.end > paraStart }
|
|
159
|
+
.map { it.copy(end = minOf(it.end, endLimit)) }
|
|
160
|
+
|
|
161
|
+
if (spansInParagraph.isEmpty()) {
|
|
162
|
+
result.add(
|
|
163
|
+
createTextItem(nextId++, trimmed, paraStart, paraEnd, text, spanned),
|
|
164
|
+
)
|
|
165
|
+
} else {
|
|
166
|
+
nextId = addSegmentedItems(result, spanned, text, paraStart, paraEnd, spansInParagraph, nextId)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
paraStart = paraEnd
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return result.ifEmpty {
|
|
173
|
+
val visibleText = text.substring(0, endLimit).trim()
|
|
174
|
+
if (visibleText.isEmpty()) emptyList() else listOf(AccessibilityItem(0, visibleText, 0, endLimit))
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Last line that is visible; less than lineCount - 1 when height-capped (e.g. `clip`). */
|
|
179
|
+
private fun lastVisibleLineIndex(layout: Layout): Int {
|
|
180
|
+
val maxLines = textView.maxLines.coerceAtLeast(1)
|
|
181
|
+
return (minOf(layout.lineCount, maxLines) - 1).coerceIn(0, layout.lineCount - 1)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Ellipsis-aware end offset for a visible line. */
|
|
185
|
+
private fun visibleLineEnd(
|
|
186
|
+
layout: Layout,
|
|
187
|
+
line: Int,
|
|
188
|
+
): Int {
|
|
189
|
+
val lineEnd = layout.getLineEnd(line)
|
|
190
|
+
val count = layout.getEllipsisCount(line)
|
|
191
|
+
if (count == 0) return lineEnd
|
|
192
|
+
// Only a tail ellipsis hides text up to the end of the line. Head/middle hide a prefix or an
|
|
193
|
+
// inner range, so the line end is still laid out and must stay reachable.
|
|
194
|
+
val hiddenStart = layout.getLineStart(line) + layout.getEllipsisStart(line)
|
|
195
|
+
return if (hiddenStart + count >= lineEnd) hiddenStart else lineEnd
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** End offset of the laid-out (visible) text; less than the full length when truncated. */
|
|
199
|
+
private fun visibleTextLength(fullLength: Int): Int {
|
|
200
|
+
val layout = textView.layout ?: return fullLength
|
|
201
|
+
if (layout.lineCount == 0) return fullLength
|
|
202
|
+
return minOf(fullLength, maxOf(0, visibleLineEnd(layout, lastVisibleLineIndex(layout))))
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private fun collectSemanticSpans(spanned: Spanned): List<SpanRange> =
|
|
206
|
+
(
|
|
207
|
+
spanned.getSpans(0, spanned.length, HeadingSpan::class.java).map {
|
|
208
|
+
SpanRange(spanned.getSpanStart(it), spanned.getSpanEnd(it), headingLevel = it.level)
|
|
209
|
+
} +
|
|
210
|
+
spanned.getSpans(0, spanned.length, LinkSpan::class.java).map {
|
|
211
|
+
SpanRange(spanned.getSpanStart(it), spanned.getSpanEnd(it), linkUrl = it.url)
|
|
212
|
+
} +
|
|
213
|
+
spanned.getSpans(0, spanned.length, ImageSpan::class.java).map {
|
|
214
|
+
SpanRange(spanned.getSpanStart(it), spanned.getSpanEnd(it), imageAltText = it.altText)
|
|
215
|
+
}
|
|
216
|
+
).sortedBy { it.start }
|
|
217
|
+
|
|
218
|
+
private fun addSegmentedItems(
|
|
219
|
+
items: MutableList<AccessibilityItem>,
|
|
220
|
+
spanned: Spanned,
|
|
221
|
+
text: String,
|
|
222
|
+
paraStart: Int,
|
|
223
|
+
paraEnd: Int,
|
|
224
|
+
spans: List<SpanRange>,
|
|
225
|
+
startId: Int,
|
|
226
|
+
): Int {
|
|
227
|
+
var nextId = startId
|
|
228
|
+
var segmentPos = paraStart
|
|
229
|
+
|
|
230
|
+
for (span in spans) {
|
|
231
|
+
if (span.start < segmentPos) continue
|
|
232
|
+
|
|
233
|
+
// Text before the span
|
|
234
|
+
if (segmentPos < span.start) {
|
|
235
|
+
val beforeText = text.substring(segmentPos, span.start).trim()
|
|
236
|
+
if (beforeText.isNotEmpty() && beforeText.any { it.isLetterOrDigit() }) {
|
|
237
|
+
items.add(createTextItem(nextId++, beforeText, segmentPos, span.start, text, spanned))
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// The semantic span itself
|
|
242
|
+
val pill =
|
|
243
|
+
if (span.linkUrl != null) {
|
|
244
|
+
spanned.getSpans(span.start, span.end, LinkPillSpan::class.java).firstOrNull {
|
|
245
|
+
spanned.getSpanStart(it) == span.start && spanned.getSpanEnd(it) == span.end
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
null
|
|
249
|
+
}
|
|
250
|
+
val content = span.imageAltText ?: pill?.accessibilityText ?: spanned.substring(span.start, span.end).trim()
|
|
251
|
+
if (content.isNotEmpty()) {
|
|
252
|
+
items.add(createSpanItem(nextId++, content, span, spanned))
|
|
253
|
+
}
|
|
254
|
+
segmentPos = span.end
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Text after the last span
|
|
258
|
+
if (segmentPos < paraEnd) {
|
|
259
|
+
val afterText = text.substring(segmentPos, paraEnd).trim()
|
|
260
|
+
if (afterText.isNotEmpty() && afterText.any { it.isLetterOrDigit() }) {
|
|
261
|
+
items.add(createTextItem(nextId++, afterText, segmentPos, paraEnd, text, spanned))
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return nextId
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private fun createTextItem(
|
|
269
|
+
id: Int,
|
|
270
|
+
label: String,
|
|
271
|
+
start: Int,
|
|
272
|
+
end: Int,
|
|
273
|
+
text: String,
|
|
274
|
+
spanned: Spanned,
|
|
275
|
+
) = AccessibilityItem(
|
|
276
|
+
id = id,
|
|
277
|
+
text = label,
|
|
278
|
+
start = start,
|
|
279
|
+
end = end,
|
|
280
|
+
visibleStart = text.findFirstNonWhitespace(start, end),
|
|
281
|
+
visibleEnd = text.findLastNonWhitespace(start, end),
|
|
282
|
+
listInfo = getListInfoAt(spanned, start, requireStart = true),
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
private fun createSpanItem(
|
|
286
|
+
id: Int,
|
|
287
|
+
content: String,
|
|
288
|
+
span: SpanRange,
|
|
289
|
+
spanned: Spanned,
|
|
290
|
+
): AccessibilityItem {
|
|
291
|
+
val listContext =
|
|
292
|
+
if (span.headingLevel > 0 || span.imageAltText != null) {
|
|
293
|
+
null
|
|
294
|
+
} else {
|
|
295
|
+
getListInfoAt(spanned, span.start, requireStart = span.linkUrl == null)
|
|
296
|
+
}
|
|
297
|
+
return AccessibilityItem(
|
|
298
|
+
id = id,
|
|
299
|
+
text = content,
|
|
300
|
+
start = span.start,
|
|
301
|
+
end = span.end,
|
|
302
|
+
headingLevel = span.headingLevel,
|
|
303
|
+
linkUrl = span.linkUrl,
|
|
304
|
+
listInfo = listContext,
|
|
305
|
+
imageAltText = span.imageAltText,
|
|
306
|
+
)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
override fun getVirtualViewAt(
|
|
310
|
+
x: Float,
|
|
311
|
+
y: Float,
|
|
312
|
+
): Int {
|
|
313
|
+
rebuildIfNeeded()
|
|
314
|
+
if (items.isEmpty()) return HOST_ID
|
|
315
|
+
|
|
316
|
+
val offset = getCharOffsetAt(x, y)
|
|
317
|
+
|
|
318
|
+
val exact =
|
|
319
|
+
items
|
|
320
|
+
.filter { offset in it.start until it.end }
|
|
321
|
+
.minByOrNull { it.hitTestPriority }
|
|
322
|
+
if (exact != null) return exact.id
|
|
323
|
+
|
|
324
|
+
return items.minByOrNull { it.distanceTo(offset) }?.id ?: HOST_ID
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
override fun getVisibleVirtualViews(ids: MutableList<Int>) {
|
|
328
|
+
rebuildIfNeeded()
|
|
329
|
+
items.forEach { ids.add(it.id) }
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
override fun onPopulateNodeForHost(host: AccessibilityNodeInfoCompat) {
|
|
333
|
+
super.onPopulateNodeForHost(host)
|
|
334
|
+
rebuildIfNeeded()
|
|
335
|
+
if (items.isNotEmpty()) {
|
|
336
|
+
host.isScreenReaderFocusable = false
|
|
337
|
+
host.isFocusable = false
|
|
338
|
+
// Prevent TalkBack from reading the full text when swiping onto the host.
|
|
339
|
+
// Without this, the host node retains the TextView's text and TalkBack
|
|
340
|
+
// announces it as a single element before entering the virtual children.
|
|
341
|
+
host.text = null
|
|
342
|
+
host.contentDescription = null
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
override fun onPopulateNodeForVirtualView(
|
|
347
|
+
id: Int,
|
|
348
|
+
node: AccessibilityNodeInfoCompat,
|
|
349
|
+
) {
|
|
350
|
+
val item = items.getOrNull(id)
|
|
351
|
+
if (item == null) {
|
|
352
|
+
node.contentDescription = ""
|
|
353
|
+
node.setBoundsInParent(Rect())
|
|
354
|
+
return
|
|
355
|
+
}
|
|
356
|
+
node.apply {
|
|
357
|
+
text = item.text
|
|
358
|
+
contentDescription = item.text
|
|
359
|
+
isFocusable = true
|
|
360
|
+
isScreenReaderFocusable = true
|
|
361
|
+
setBoundsInParent(boundsForItem(item))
|
|
362
|
+
applySemantics(item)
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
override fun onPerformActionForVirtualView(
|
|
367
|
+
id: Int,
|
|
368
|
+
action: Int,
|
|
369
|
+
args: Bundle?,
|
|
370
|
+
): Boolean {
|
|
371
|
+
val item = items.getOrNull(id) ?: return false
|
|
372
|
+
if (action == AccessibilityNodeInfoCompat.ACTION_CLICK && item.isLink) {
|
|
373
|
+
val spanned = textView.text as? Spanned ?: return false
|
|
374
|
+
val linkSpan = spanned.getSpans(item.start, item.end, LinkSpan::class.java).firstOrNull() ?: return false
|
|
375
|
+
linkSpan.onClick(textView)
|
|
376
|
+
return true
|
|
377
|
+
}
|
|
378
|
+
return false
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
private fun AccessibilityNodeInfoCompat.applySemantics(item: AccessibilityItem) {
|
|
382
|
+
item.listInfo?.let { info ->
|
|
383
|
+
setCollectionItemInfo(
|
|
384
|
+
AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(info.itemNumber - 1, 1, 0, 1, false, false),
|
|
385
|
+
)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
val blockquoteAnnouncement = blockquoteAnnouncementFor(item)
|
|
389
|
+
when {
|
|
390
|
+
item.isHeading -> {
|
|
391
|
+
isHeading = true
|
|
392
|
+
if (blockquoteAnnouncement != null) {
|
|
393
|
+
roleDescription = blockquoteAnnouncement
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
item.isImage -> {
|
|
398
|
+
roleDescription = appendBlockquote("image", blockquoteAnnouncement)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
item.isLink -> {
|
|
402
|
+
isClickable = true
|
|
403
|
+
addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK)
|
|
404
|
+
val base = item.listInfo?.let { "link, ${it.listAnnouncement}" } ?: "link"
|
|
405
|
+
roleDescription = appendBlockquote(base, blockquoteAnnouncement)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
item.isListItem -> {
|
|
409
|
+
roleDescription = appendBlockquote(item.listInfo!!.listAnnouncement, blockquoteAnnouncement)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
blockquoteAnnouncement != null -> {
|
|
413
|
+
roleDescription = blockquoteAnnouncement
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
private fun appendBlockquote(
|
|
419
|
+
base: String,
|
|
420
|
+
blockquoteAnnouncement: String?,
|
|
421
|
+
): String = if (blockquoteAnnouncement != null) "$base, $blockquoteAnnouncement" else base
|
|
422
|
+
|
|
423
|
+
private fun blockquoteAnnouncementFor(item: AccessibilityItem): String? {
|
|
424
|
+
val spanned = textView.text as? Spanned ?: return null
|
|
425
|
+
if (spanned.isEmpty()) return null
|
|
426
|
+
val pos = item.start.coerceIn(0, spanned.length - 1).coerceAtLeast(0)
|
|
427
|
+
if (pos >= spanned.length) return null
|
|
428
|
+
val spans = spanned.getSpans(pos, pos + 1, BlockquoteSpan::class.java)
|
|
429
|
+
if (spans.isEmpty()) return null
|
|
430
|
+
val maxDepth = spans.maxOf { it.depth }
|
|
431
|
+
return if (maxDepth >= 1) labels.nestedBlockquote else labels.blockquote
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
private val ListItemInfo.listAnnouncement: String
|
|
435
|
+
get() {
|
|
436
|
+
val nested = depth > 0
|
|
437
|
+
return if (isOrdered) {
|
|
438
|
+
val template = if (nested) labels.nestedOrderedItem else labels.orderedItem
|
|
439
|
+
template.replace("{n}", itemNumber.toString())
|
|
440
|
+
} else {
|
|
441
|
+
if (nested) labels.nestedBulletPoint else labels.bulletPoint
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
private fun boundsForItem(item: AccessibilityItem): Rect {
|
|
446
|
+
val layout = textView.layout ?: return Rect()
|
|
447
|
+
if (layout.lineCount == 0) return Rect()
|
|
448
|
+
val maxOffset = visibleLineEnd(layout, lastVisibleLineIndex(layout))
|
|
449
|
+
val vs = item.visibleStart.coerceIn(0, maxOffset)
|
|
450
|
+
val ve = item.visibleEnd.coerceIn(vs, maxOffset)
|
|
451
|
+
|
|
452
|
+
val startLine = layout.getLineForOffset(vs)
|
|
453
|
+
val endLine = layout.getLineForOffset(maxOf(vs, ve - 1))
|
|
454
|
+
|
|
455
|
+
if (startLine == endLine) {
|
|
456
|
+
return singleLineBounds(vs, ve, startLine)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return multiLineBounds(vs, startLine, endLine)
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
private fun singleLineBounds(
|
|
463
|
+
start: Int,
|
|
464
|
+
end: Int,
|
|
465
|
+
line: Int,
|
|
466
|
+
): Rect {
|
|
467
|
+
val layout = textView.layout
|
|
468
|
+
val left = layout.getPrimaryHorizontal(start).toInt() + textView.paddingLeft
|
|
469
|
+
val rawRight = layout.getPrimaryHorizontal(end).toInt() + textView.paddingLeft
|
|
470
|
+
val right = if (rawRight <= left) layout.getLineRight(line).toInt() + textView.paddingLeft else rawRight
|
|
471
|
+
|
|
472
|
+
return Rect(
|
|
473
|
+
left,
|
|
474
|
+
layout.getLineTop(line) + textView.paddingTop,
|
|
475
|
+
right,
|
|
476
|
+
layout.getLineBottom(line) + textView.paddingTop,
|
|
477
|
+
)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
private fun multiLineBounds(
|
|
481
|
+
visibleStart: Int,
|
|
482
|
+
startLine: Int,
|
|
483
|
+
endLine: Int,
|
|
484
|
+
): Rect {
|
|
485
|
+
val layout = textView.layout
|
|
486
|
+
val padLeft = textView.paddingLeft
|
|
487
|
+
|
|
488
|
+
val firstLineLeft = layout.getPrimaryHorizontal(visibleStart).toInt() + padLeft
|
|
489
|
+
val firstLineRight = layout.getLineRight(startLine).toInt() + padLeft
|
|
490
|
+
|
|
491
|
+
var minLeft = firstLineLeft
|
|
492
|
+
var maxRight = firstLineRight
|
|
493
|
+
for (line in (startLine + 1)..endLine) {
|
|
494
|
+
minLeft = minOf(minLeft, layout.getLineLeft(line).toInt() + padLeft)
|
|
495
|
+
maxRight = maxOf(maxRight, layout.getLineRight(line).toInt() + padLeft)
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return Rect(
|
|
499
|
+
minOf(firstLineLeft, minLeft),
|
|
500
|
+
layout.getLineTop(startLine) + textView.paddingTop,
|
|
501
|
+
maxRight,
|
|
502
|
+
layout.getLineBottom(endLine) + textView.paddingTop,
|
|
503
|
+
)
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
private fun getCharOffsetAt(
|
|
507
|
+
x: Float,
|
|
508
|
+
y: Float,
|
|
509
|
+
): Int {
|
|
510
|
+
val layout = textView.layout ?: return 0
|
|
511
|
+
val line = layout.getLineForVertical(y.toInt()).coerceIn(0, layout.lineCount - 1)
|
|
512
|
+
return layout.getOffsetForHorizontal(line, x)
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
private val AccessibilityItem.hitTestPriority: Int
|
|
516
|
+
get() =
|
|
517
|
+
when {
|
|
518
|
+
isLink -> 0
|
|
519
|
+
isImage -> 1
|
|
520
|
+
isHeading -> 2
|
|
521
|
+
isListItem -> 3
|
|
522
|
+
else -> 4
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
private fun AccessibilityItem.distanceTo(offset: Int): Int =
|
|
526
|
+
when {
|
|
527
|
+
offset < start -> start - offset
|
|
528
|
+
offset >= end -> offset - end + 1
|
|
529
|
+
else -> 0
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
private fun getListInfoAt(
|
|
533
|
+
spanned: Spanned,
|
|
534
|
+
position: Int,
|
|
535
|
+
requireStart: Boolean,
|
|
536
|
+
): ListItemInfo? {
|
|
537
|
+
val deepest =
|
|
538
|
+
spanned
|
|
539
|
+
.getSpans(position, position + 1, BaseListSpan::class.java)
|
|
540
|
+
.maxByOrNull { it.depth } ?: return null
|
|
541
|
+
|
|
542
|
+
if (requireStart) {
|
|
543
|
+
val spanStart = spanned.getSpanStart(deepest)
|
|
544
|
+
val firstChar =
|
|
545
|
+
(spanStart until minOf(spanStart + 10, spanned.length))
|
|
546
|
+
.firstOrNull { !spanned[it].isWhitespace() } ?: spanStart
|
|
547
|
+
if (position > firstChar + 1) return null
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return ListItemInfo(
|
|
551
|
+
isOrdered = deepest is OrderedListSpan,
|
|
552
|
+
itemNumber = (deepest as? OrderedListSpan)?.itemNumber ?: 0,
|
|
553
|
+
depth = deepest.depth,
|
|
554
|
+
)
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
private fun String.findFirstNonWhitespace(
|
|
559
|
+
from: Int,
|
|
560
|
+
to: Int,
|
|
561
|
+
): Int {
|
|
562
|
+
for (i in from until to) {
|
|
563
|
+
if (!this[i].isWhitespace()) return i
|
|
564
|
+
}
|
|
565
|
+
return from
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
private fun String.findLastNonWhitespace(
|
|
569
|
+
from: Int,
|
|
570
|
+
to: Int,
|
|
571
|
+
): Int {
|
|
572
|
+
for (i in (to - 1) downTo from) {
|
|
573
|
+
if (!this[i].isWhitespace()) return i + 1
|
|
574
|
+
}
|
|
575
|
+
return to
|
|
576
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
package com.renderrichtext.events
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.WritableMap
|
|
5
|
+
import com.facebook.react.uimanager.events.Event
|
|
6
|
+
|
|
7
|
+
class CodeBlockPressEvent(
|
|
8
|
+
surfaceId: Int,
|
|
9
|
+
viewId: Int,
|
|
10
|
+
private val code: String,
|
|
11
|
+
private val language: String,
|
|
12
|
+
) : Event<CodeBlockPressEvent>(surfaceId, viewId) {
|
|
13
|
+
override fun getEventName(): String = EVENT_NAME
|
|
14
|
+
|
|
15
|
+
override fun getEventData(): WritableMap =
|
|
16
|
+
Arguments.createMap().apply {
|
|
17
|
+
putString("code", code)
|
|
18
|
+
putString("language", language)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
companion object {
|
|
22
|
+
const val EVENT_NAME: String = "onCodeBlockPress"
|
|
23
|
+
}
|
|
24
|
+
}
|