react-native-enriched-markdown 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/README.md +215 -0
- package/app.plugin.js +2 -1
- package/docs/ACCESSIBILITY.md +172 -0
- package/docs/API_REFERENCE.md +948 -0
- package/docs/COPY_OPTIONS.md +127 -0
- package/docs/ELEMENTS_STRUCTURE.md +112 -0
- package/docs/IMAGE_CACHING.md +25 -0
- package/docs/INPUT.md +233 -0
- package/docs/LATEX_MATH.md +136 -0
- package/docs/MACOS.md +19 -0
- package/docs/MARKDOWN_STREAMING.md +36 -0
- package/docs/MENTIONS.md +97 -0
- package/docs/RTL.md +82 -0
- package/docs/STYLES.md +519 -0
- package/docs/TEXT.md +110 -0
- package/docs/WEB.md +49 -0
- package/package.json +7 -4
- package/plugin/build/withAndroidMath.js +19 -0
- package/plugin/build/withIosMath.js +28 -0
- package/plugin/build/withReactNativeEnrichedMarkdown.js +11 -0
- package/plugin/tsconfig.build.json +16 -0
- package/lib/module/plugin/withAndroidMath.js +0 -20
- package/lib/module/plugin/withAndroidMath.js.map +0 -1
- package/lib/module/plugin/withIosMath.js +0 -23
- package/lib/module/plugin/withIosMath.js.map +0 -1
- package/lib/module/plugin/withReactNativeEnrichedMarkdown.js +0 -16
- package/lib/module/plugin/withReactNativeEnrichedMarkdown.js.map +0 -1
- package/lib/typescript/src/plugin/withAndroidMath.d.ts +0 -5
- package/lib/typescript/src/plugin/withAndroidMath.d.ts.map +0 -1
- package/lib/typescript/src/plugin/withIosMath.d.ts +0 -5
- package/lib/typescript/src/plugin/withIosMath.d.ts.map +0 -1
- package/lib/typescript/src/plugin/withReactNativeEnrichedMarkdown.d.ts +0 -6
- package/lib/typescript/src/plugin/withReactNativeEnrichedMarkdown.d.ts.map +0 -1
- /package/{src/plugin → plugin/src}/withAndroidMath.ts +0 -0
- /package/{src/plugin → plugin/src}/withIosMath.ts +0 -0
- /package/{src/plugin → plugin/src}/withReactNativeEnrichedMarkdown.ts +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Software Mansion
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
<img src="https://github.com/user-attachments/assets/83cb462c-17df-4809-8b8a-fa4abb258cb3" alt="react-native-enriched-markdown by Software Mansion" width="100%">
|
|
2
|
+
<a href="https://swm-delivery.com/www/delivery/ck-slug.php?zoneid=zone-gh-react-native-enriched-1&n=1"><img src="https://swm-delivery.com/www/images/zone-gh-react-native-enriched-1?n=1" /></a>
|
|
3
|
+
<a href="https://swm-delivery.com/www/delivery/ck-slug.php?zoneid=zone-gh-react-native-enriched-2&n=1"><img src="https://swm-delivery.com/www/images/zone-gh-react-native-enriched-2?n=1" /></a>
|
|
4
|
+
<a href="https://swm-delivery.com/www/delivery/ck-slug.php?zoneid=zone-gh-react-native-enriched-3&n=1"><img src="https://swm-delivery.com/www/images/zone-gh-react-native-enriched-3?n=1" /></a>
|
|
5
|
+
|
|
6
|
+
# react-native-enriched-markdown
|
|
7
|
+
|
|
8
|
+
`react-native-enriched-markdown` is a powerful React Native library that renders Markdown content as native text and provides a rich text input with Markdown output. It supports iOS, Android, macOS, and Web, and requires the New Architecture (Fabric) for native platforms.
|
|
9
|
+
|
|
10
|
+
### EnrichedMarkdownText
|
|
11
|
+
|
|
12
|
+
- ⚡ Fully native text rendering (no WebView)
|
|
13
|
+
- 🌐 Web support via [react-native-web](https://necolas.github.io/react-native-web/) + [md4c](https://github.com/mity/md4c) compiled to WebAssembly
|
|
14
|
+
- 🎯 High-performance Markdown parsing with [md4c](https://github.com/mity/md4c)
|
|
15
|
+
- 📐 CommonMark standard compliant
|
|
16
|
+
- 📊 GitHub Flavored Markdown (GFM)
|
|
17
|
+
- 🧮 LaTeX math rendering (block `$$...$$` with `flavor="github"`, inline `$...$` in all flavors)
|
|
18
|
+
- 🔀 [Markdown Streaming](docs/MARKDOWN_STREAMING.md) support (via [react-native-streamdown](https://github.com/software-mansion-labs/react-native-streamdown))
|
|
19
|
+
- 🎨 Fully customizable styles for all elements
|
|
20
|
+
- ✨ Text selection and copy support
|
|
21
|
+
- 📌 Custom text selection context menu items
|
|
22
|
+
- 🔗 Interactive link handling with [per-URL-pattern styling](docs/MENTIONS.md#link-variants-styling) (`linkVariants`)
|
|
23
|
+
- 👤 Renders mentions as styled links (compatible with `EnrichedMarkdownTextInput` mention output)
|
|
24
|
+
- 🙈 Spoiler text with animated particle overlay and tap-to-reveal
|
|
25
|
+
- 🖼️ Native image interactions (iOS: Copy, Save to Camera Roll)
|
|
26
|
+
- 🌐 Native platform features (Translate, Look Up, Search Web, Share)
|
|
27
|
+
- 🗣️ Accessibility support (VoiceOver on iOS, TalkBack on Android, semantic HTML on web)
|
|
28
|
+
- 🔄 Full RTL (right-to-left) support including text, lists, blockquotes, tables, and task lists
|
|
29
|
+
|
|
30
|
+
### EnrichedMarkdownTextInput
|
|
31
|
+
|
|
32
|
+
- ✏️ Rich text input with Markdown output
|
|
33
|
+
- 🕹️ Imperative API for toggling styles and managing links
|
|
34
|
+
- 📋 Native context menu with formatting submenu
|
|
35
|
+
- 🔍 Real-time style state detection
|
|
36
|
+
- 🔗 Auto-link detection with customizable regex
|
|
37
|
+
- 🔄 Smart copy/paste with Markdown preservation
|
|
38
|
+
- 🎨 Customizable bold, italic, and link colors
|
|
39
|
+
- 👤 [Mentions](docs/MENTIONS.md) with configurable indicators, suggestion lifecycle events, and per-pattern link styling
|
|
40
|
+
|
|
41
|
+
Since 2012 [Software Mansion](https://swmansion.com) is a software agency with experience in building web and mobile apps. We are Core React Native Contributors and experts in dealing with all kinds of React Native issues.
|
|
42
|
+
We can help you build your next dream product –
|
|
43
|
+
[Hire us](https://swmansion.com/contact/projects?utm_source=react-native-enriched-markdown&utm_medium=readme).
|
|
44
|
+
|
|
45
|
+
## Table of Contents
|
|
46
|
+
|
|
47
|
+
- [Prerequisites](#prerequisites)
|
|
48
|
+
- [Installation](#installation)
|
|
49
|
+
- [EnrichedMarkdownText](#enrichedmarkdowntext-1)
|
|
50
|
+
- [Usage](docs/TEXT.md#usage)
|
|
51
|
+
- [Supported Markdown Elements](docs/TEXT.md#supported-markdown-elements)
|
|
52
|
+
- [Copy Options](docs/TEXT.md#copy-options)
|
|
53
|
+
- [Accessibility](docs/TEXT.md#accessibility)
|
|
54
|
+
- [RTL Support](docs/TEXT.md#rtl-support)
|
|
55
|
+
- [Customizing Styles](docs/TEXT.md#customizing-styles)
|
|
56
|
+
- [LaTeX Math](docs/LATEX_MATH.md)
|
|
57
|
+
- [Image Caching](docs/IMAGE_CACHING.md)
|
|
58
|
+
- [Markdown Streaming](docs/MARKDOWN_STREAMING.md)
|
|
59
|
+
- [EnrichedMarkdownTextInput](#enrichedmarkdowntextinput-1)
|
|
60
|
+
- [Usage](docs/INPUT.md#usage)
|
|
61
|
+
- [Inline Styles](docs/INPUT.md#inline-styles)
|
|
62
|
+
- [Links](docs/INPUT.md#links)
|
|
63
|
+
- [Auto-Link Detection](docs/INPUT.md#auto-link-detection)
|
|
64
|
+
- [Mentions](docs/MENTIONS.md)
|
|
65
|
+
- [Style Detection](docs/INPUT.md#style-detection)
|
|
66
|
+
- [Other Events](docs/INPUT.md#other-events)
|
|
67
|
+
- [Customizing Styles](docs/INPUT.md#customizing-enrichedmarkdowntextinput--styles)
|
|
68
|
+
- [API Reference](#api-reference)
|
|
69
|
+
- [Web Support](docs/WEB.md)
|
|
70
|
+
- [macOS Support](docs/MACOS.md)
|
|
71
|
+
- [Compatibility Table](#compatibility-table)
|
|
72
|
+
- [Contributing](#contributing)
|
|
73
|
+
- [Future Plans](#future-plans)
|
|
74
|
+
- [License](#license)
|
|
75
|
+
|
|
76
|
+
## Prerequisites
|
|
77
|
+
|
|
78
|
+
**Native (iOS / Android / macOS)**
|
|
79
|
+
|
|
80
|
+
- Requires [the React Native New Architecture (Fabric)](https://reactnative.dev/architecture/landing-page)
|
|
81
|
+
- See [Compatibility Table](#compatibility-table) for supported React Native versions
|
|
82
|
+
- macOS support via [react-native-macos](https://github.com/microsoft/react-native-macos) `0.81+`
|
|
83
|
+
|
|
84
|
+
**Web**
|
|
85
|
+
|
|
86
|
+
- Requires [`react-native-web`](https://necolas.github.io/react-native-web/) and Metro (or another bundler with `.web.tsx` platform resolution)
|
|
87
|
+
- No New Architecture requirement — the web renderer runs entirely in JavaScript via WebAssembly
|
|
88
|
+
- Only `EnrichedMarkdownText` is supported on web (`EnrichedMarkdownTextInput` is native-only)
|
|
89
|
+
- LaTeX math requires the optional [`katex`](https://katex.org/) peer dependency
|
|
90
|
+
|
|
91
|
+
## Installation
|
|
92
|
+
|
|
93
|
+
### Web
|
|
94
|
+
|
|
95
|
+
No steps beyond having `react-native-web` configured. For LaTeX math, install the optional peer dependency:
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
npm install katex
|
|
99
|
+
# or
|
|
100
|
+
yarn add katex
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
See [Web Support](docs/WEB.md) for full setup details, supported features, and prop behaviour.
|
|
104
|
+
|
|
105
|
+
### Bare React Native app (iOS / Android)
|
|
106
|
+
|
|
107
|
+
#### 1. Install the library
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
yarn add react-native-enriched-markdown
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
> [!TIP]
|
|
114
|
+
> To try the latest features before they land in a stable release, install the nightly build:
|
|
115
|
+
>
|
|
116
|
+
> ```sh
|
|
117
|
+
> yarn add react-native-enriched-markdown@nightly
|
|
118
|
+
> ```
|
|
119
|
+
>
|
|
120
|
+
> Nightly versions are published to npm automatically and may contain breaking changes.
|
|
121
|
+
|
|
122
|
+
#### 2. Install iOS / macOS dependencies
|
|
123
|
+
|
|
124
|
+
The library includes native code so you will need to re-build the native app.
|
|
125
|
+
|
|
126
|
+
```sh
|
|
127
|
+
# iOS
|
|
128
|
+
cd ios && bundle install && bundle exec pod install
|
|
129
|
+
|
|
130
|
+
# macOS (react-native-macos)
|
|
131
|
+
cd macos && bundle install && bundle exec pod install
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Expo app
|
|
135
|
+
|
|
136
|
+
#### 1. Install the library
|
|
137
|
+
|
|
138
|
+
```sh
|
|
139
|
+
npx expo install react-native-enriched-markdown
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### 2. Run prebuild
|
|
143
|
+
|
|
144
|
+
The library includes native code so you will need to re-build the native app.
|
|
145
|
+
|
|
146
|
+
```sh
|
|
147
|
+
npx expo prebuild
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
> [!NOTE]
|
|
151
|
+
> The library won't work in Expo Go as it needs native changes.
|
|
152
|
+
|
|
153
|
+
> [!IMPORTANT]
|
|
154
|
+
> **iOS: Save to Camera Roll**
|
|
155
|
+
>
|
|
156
|
+
> If your Markdown content includes images and you want users to save them to their photo library, add the following to your `Info.plist`:
|
|
157
|
+
>
|
|
158
|
+
> ```xml
|
|
159
|
+
> <key>NSPhotoLibraryAddUsageDescription</key>
|
|
160
|
+
> <string>This app needs access to your photo library to save images.</string>
|
|
161
|
+
> ```
|
|
162
|
+
|
|
163
|
+
## EnrichedMarkdownText
|
|
164
|
+
|
|
165
|
+
See [EnrichedMarkdownText](docs/TEXT.md) for detailed documentation on usage examples, GFM tables, task lists, link handling, supported elements, copy options, accessibility, RTL support, and customizing styles. Mentions created by `EnrichedMarkdownTextInput` render as styled links — use [`linkVariants`](docs/MENTIONS.md#link-variants-styling) to customize their appearance.
|
|
166
|
+
|
|
167
|
+
## EnrichedMarkdownTextInput
|
|
168
|
+
|
|
169
|
+
See [EnrichedMarkdownTextInput](docs/INPUT.md) for detailed documentation on usage examples, inline styles, links, style detection, events, and customizing styles.
|
|
170
|
+
|
|
171
|
+
## API Reference
|
|
172
|
+
|
|
173
|
+
See the [API Reference](docs/API_REFERENCE.md) for a detailed overview of all the props, methods, and events available.
|
|
174
|
+
|
|
175
|
+
## Web Support
|
|
176
|
+
|
|
177
|
+
See [Web Support](docs/WEB.md) for details on supported features, web-specific prop behaviour, and known limitations.
|
|
178
|
+
|
|
179
|
+
## macOS Support
|
|
180
|
+
|
|
181
|
+
`react-native-enriched-markdown` supports macOS via [react-native-macos](https://github.com/microsoft/react-native-macos). See [macOS Support](docs/MACOS.md) for details on macOS-specific features, known limitations, and the example app.
|
|
182
|
+
|
|
183
|
+
## Future Plans
|
|
184
|
+
|
|
185
|
+
We're actively working on expanding the capabilities of `react-native-enriched-markdown`. Here's what's on the roadmap:
|
|
186
|
+
|
|
187
|
+
- `EnrichedMarkdownTextInput`: headings, lists, blockquotes, code blocks, inline images
|
|
188
|
+
- `EnrichedMarkdownTextInput` web support
|
|
189
|
+
- macOS: block math rendering, VoiceOver accessibility, tail fade-in animation
|
|
190
|
+
- Web: spoiler text, streaming animation, configurable link `target`, copy options (Copy as Markdown, multi-format clipboard)
|
|
191
|
+
|
|
192
|
+
## Compatibility Table
|
|
193
|
+
|
|
194
|
+
| | 0.82 | 0.83 | 0.84 | 0.85 | 0.86 |
|
|
195
|
+
|---|:---:|:---:|:---:|:---:|:---:|
|
|
196
|
+
| **nightly** | ⛔ | ✅ | ✅ | ✅ | ✅ |
|
|
197
|
+
| **0.7.0** | ⛔ | ✅ | ✅ | ✅ | ✅ |
|
|
198
|
+
| **0.6.0** | ⛔ | ✅ | ✅ | ✅ | ⛔ |
|
|
199
|
+
| **0.5.0** | ⛔ | ✅ | ✅ | ✅ | ⛔ |
|
|
200
|
+
| **0.4.x** | ✅ | ✅ | ✅ | ⛔ | ⛔ |
|
|
201
|
+
| **0.3.0** | ✅ | ✅ | ✅ | ⛔ | ⛔ |
|
|
202
|
+
|
|
203
|
+
## Contributing
|
|
204
|
+
|
|
205
|
+
See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
|
|
206
|
+
|
|
207
|
+
## License
|
|
208
|
+
|
|
209
|
+
`react-native-enriched-markdown` library is licensed under [The MIT License](./LICENSE).
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
Built by [Software Mansion](https://swmansion.com/).
|
|
214
|
+
|
|
215
|
+
[<img width="128" height="69" alt="Software Mansion Logo" src="https://github.com/user-attachments/assets/f0e18471-a7aa-4e80-86ac-87686a86fe56" />](https://swmansion.com/)
|
package/app.plugin.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
module.exports =
|
|
1
|
+
module.exports =
|
|
2
|
+
require('./plugin/build/withReactNativeEnrichedMarkdown').default;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Accessibility
|
|
2
|
+
|
|
3
|
+
`react-native-enriched-markdown` provides comprehensive accessibility support for screen readers on both iOS and Android platforms.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The library implements native accessibility features that enable screen readers (VoiceOver on iOS and TalkBack on Android) to properly navigate and understand Markdown content. This includes semantic labeling, custom navigation controls, and proper announcements for all supported elements.
|
|
8
|
+
|
|
9
|
+
## Text Announcements
|
|
10
|
+
|
|
11
|
+
Plain text paragraphs without inline links or images are announced as a single VoiceOver element per paragraph. Paragraphs containing links or images are segmented into text, link, and image parts so that each remains independently navigable. List items follow the same logic — a list item without inline specials is a single element, while one containing a link is split accordingly. Whitespace-only segments between elements are filtered out to avoid empty announcements.
|
|
12
|
+
|
|
13
|
+
## Translating announcements — `accessibilityLabels`
|
|
14
|
+
|
|
15
|
+
All strings spoken by the screen reader (list announcements, blockquote suffix, table rows, math equation prefix, and iOS rotor names) can be overridden via the optional `accessibilityLabels` prop on `EnrichedMarkdownText`. The defaults are English; consumers wire in their own i18n pipeline.
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { EnrichedMarkdownText } from 'react-native-enriched-markdown';
|
|
19
|
+
|
|
20
|
+
<EnrichedMarkdownText
|
|
21
|
+
markdown={markdown}
|
|
22
|
+
accessibilityLabels={{
|
|
23
|
+
list: {
|
|
24
|
+
bulletPoint: 'Punkt',
|
|
25
|
+
nestedBulletPoint: 'Eingebetteter Punkt',
|
|
26
|
+
orderedItem: 'Listenelement {n}',
|
|
27
|
+
nestedOrderedItem: 'Eingebettetes Listenelement {n}',
|
|
28
|
+
},
|
|
29
|
+
blockquote: {
|
|
30
|
+
quote: 'Zitat',
|
|
31
|
+
nestedQuote: 'Eingebettetes Zitat',
|
|
32
|
+
},
|
|
33
|
+
table: { row: 'Zeile {n}: {content}' },
|
|
34
|
+
math: { equation: 'Formel: {latex}' },
|
|
35
|
+
rotor: {
|
|
36
|
+
headings: 'Überschriften',
|
|
37
|
+
links: 'Links',
|
|
38
|
+
images: 'Bilder',
|
|
39
|
+
},
|
|
40
|
+
}}
|
|
41
|
+
/>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Every field is optional. Any field you omit falls back to the English default below. Defaults are resolved on the JS side before being forwarded to native, so once you set even a single override the rest stay on their built-ins automatically.
|
|
45
|
+
|
|
46
|
+
### Defaults
|
|
47
|
+
|
|
48
|
+
| Field | Default | Platform |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| `list.bulletPoint` | `"Bullet point"` | iOS + Android |
|
|
51
|
+
| `list.nestedBulletPoint` | `"Nested bullet point"` | iOS + Android |
|
|
52
|
+
| `list.orderedItem` | `"List item {n}"` | iOS + Android |
|
|
53
|
+
| `list.nestedOrderedItem` | `"Nested list item {n}"` | iOS + Android |
|
|
54
|
+
| `blockquote.quote` | `"Blockquote"` | iOS + Android |
|
|
55
|
+
| `blockquote.nestedQuote` | `"Nested blockquote"` | iOS + Android |
|
|
56
|
+
| `table.row` | `"Row {n}: {content}"` | iOS + Android |
|
|
57
|
+
| `math.equation` | `"Math: {latex}"` | iOS + Android |
|
|
58
|
+
| `rotor.headings` | `"Headings"` | iOS only |
|
|
59
|
+
| `rotor.links` | `"Links"` | iOS only |
|
|
60
|
+
| `rotor.images` | `"Images"` | iOS only |
|
|
61
|
+
|
|
62
|
+
### Placeholder syntax
|
|
63
|
+
|
|
64
|
+
- `{n}` — 1-based index (list item number, table row index). Substituted at speak time.
|
|
65
|
+
- `{content}` — comma-joined cell texts for a table row.
|
|
66
|
+
- `{latex}` — math equation source.
|
|
67
|
+
|
|
68
|
+
Placeholder names must be preserved in translations exactly. Defaults intentionally use the no-plural cardinal form (`"List item 2"`, `"Row 2"`) instead of ordinals or count-aware variants — a single template works in every language without per-locale plural rules.
|
|
69
|
+
|
|
70
|
+
## Supported Elements
|
|
71
|
+
|
|
72
|
+
| Element | VoiceOver (iOS) | TalkBack (Android) |
|
|
73
|
+
|---------|-----------------|---------------------|
|
|
74
|
+
| **Headings (h1-h6)** | Rotor navigation, `UIAccessibilityTraitHeader` → "heading" suffix | Reading controls navigation, `isHeading = true` → "heading" suffix |
|
|
75
|
+
| **Links** | Rotor navigation, activatable, "link" suffix | Reading controls navigation, activatable, "link" suffix |
|
|
76
|
+
| **Images** | Alt text announced, rotor navigation, "image" suffix | Alt text announced, "image" role |
|
|
77
|
+
| **Unordered list items** | "Bullet point" / "Nested bullet point" appended (`accessibilityValue`) | Same string appended via `roleDescription` |
|
|
78
|
+
| **Ordered list items** | "List item N" / "Nested list item N" appended | Same string appended via `roleDescription` |
|
|
79
|
+
| **Blockquote content** | "Blockquote" / "Nested blockquote" appended | Same string appended via `roleDescription` |
|
|
80
|
+
| **Table rows** | One focusable element per row, `"Row N: <cells>"` label, header row carries `UIAccessibilityTraitHeader` | One focusable overlay per row, same template, header row carries `setAccessibilityHeading(true)` |
|
|
81
|
+
| **Math equations** | `"Math: <latex>"` (latex read verbatim — no TTS engine) | Same |
|
|
82
|
+
|
|
83
|
+
## Architecture
|
|
84
|
+
|
|
85
|
+
### iOS (VoiceOver)
|
|
86
|
+
|
|
87
|
+
**Custom Rotors:**
|
|
88
|
+
- **Headings Rotor**: Navigate between all headings in the document
|
|
89
|
+
- **Links Rotor**: Jump between all links
|
|
90
|
+
- **Images Rotor**: Navigate through all images
|
|
91
|
+
|
|
92
|
+
Rotor names are translated via `accessibilityLabels.rotor.*`. When VoiceOver is active, a two-finger twist on the screen cycles through these rotors; swipe up/down jumps between elements of the currently-selected type.
|
|
93
|
+
|
|
94
|
+
**Semantic Traits:**
|
|
95
|
+
- Headings: `UIAccessibilityTraitHeader | UIAccessibilityTraitStaticText`
|
|
96
|
+
- Links: `UIAccessibilityTraitLink | UIAccessibilityTraitStaticText`
|
|
97
|
+
- Images: `UIAccessibilityTraitImage` (+ `UIAccessibilityTraitLink` if the image is linked)
|
|
98
|
+
- Plain text: `UIAccessibilityTraitStaticText`
|
|
99
|
+
|
|
100
|
+
List, blockquote and table row labels are exposed via `accessibilityValue` so VoiceOver speaks them after the main text.
|
|
101
|
+
|
|
102
|
+
### Android (TalkBack)
|
|
103
|
+
|
|
104
|
+
**Reading Controls:**
|
|
105
|
+
- Headings, links, and images are available in TalkBack's reading controls for quick navigation
|
|
106
|
+
- List items are properly announced with their position and type (ordered vs unordered)
|
|
107
|
+
|
|
108
|
+
**Accessibility Node Info:**
|
|
109
|
+
- Headings: `isHeading = true` — TalkBack speaks "heading" as the role
|
|
110
|
+
- Links: `isClickable = true` + `roleDescription = "link"`
|
|
111
|
+
- Images: alt text becomes `contentDescription`, `roleDescription = "image"`
|
|
112
|
+
- List items: `CollectionItemInfoCompat` is set so TalkBack also announces position-in-collection; the localized label is appended via `roleDescription`
|
|
113
|
+
- Blockquote content: localized label appended via `roleDescription` (combined with link/list role when nested)
|
|
114
|
+
- Table rows: one transparent overlay per row exposes the row's joined content via `contentDescription`; header rows additionally call `ViewCompat.setAccessibilityHeading(true)`
|
|
115
|
+
- Math: `MathContainerView` exposes the full equation via `contentDescription` (`"Math: <latex>"` template)
|
|
116
|
+
|
|
117
|
+
## Element Details
|
|
118
|
+
|
|
119
|
+
### Headings
|
|
120
|
+
|
|
121
|
+
Headings are marked with the platform's "heading" trait/role. Screen readers announce the heading text followed by "heading" — the heading **level** is not spoken by default (the trait already conveys "this is a heading"; spelling the level out adds friction). The level remains available programmatically so platform navigation controls (rotor on iOS, reading controls on Android) can jump between headings of any level.
|
|
122
|
+
|
|
123
|
+
**Example announcement:**
|
|
124
|
+
- "Welcome to Markdown, heading"
|
|
125
|
+
- "Getting Started, heading"
|
|
126
|
+
|
|
127
|
+
### Links
|
|
128
|
+
|
|
129
|
+
Links are fully interactive and can be activated through screen reader gestures. The link text is announced followed by "link".
|
|
130
|
+
|
|
131
|
+
**Example announcement:**
|
|
132
|
+
- "React Native, link"
|
|
133
|
+
|
|
134
|
+
### Images
|
|
135
|
+
|
|
136
|
+
Images with alt text announce the alt text + "image". Images without alt text are intentionally **silent** (no fallback string) — if you want them announced, supply alt text in the markdown source.
|
|
137
|
+
|
|
138
|
+
**Example announcement:**
|
|
139
|
+
- "Misty forest at sunrise, image"
|
|
140
|
+
- *(silent for ``)*
|
|
141
|
+
|
|
142
|
+
### Lists
|
|
143
|
+
|
|
144
|
+
List items are announced with their position and type:
|
|
145
|
+
|
|
146
|
+
- Unordered: `"<text>, Bullet point"` (top-level) or `"<text>, Nested bullet point"` (deeper)
|
|
147
|
+
- Ordered: `"<text>, List item 1"`, `"<text>, List item 2"`, etc.
|
|
148
|
+
|
|
149
|
+
Override via `accessibilityLabels.list.*`. The `{n}` placeholder is substituted with the 1-based item number.
|
|
150
|
+
|
|
151
|
+
### Blockquotes
|
|
152
|
+
|
|
153
|
+
Content inside a blockquote gets the blockquote label appended (after any list or link suffix). Nested blockquotes use the `nestedQuote` label instead.
|
|
154
|
+
|
|
155
|
+
**Example announcement:**
|
|
156
|
+
- `"This is a quoted line., Blockquote"`
|
|
157
|
+
- `"Nested quote text., Nested blockquote"`
|
|
158
|
+
|
|
159
|
+
### Tables
|
|
160
|
+
|
|
161
|
+
Tables expose one focusable element per row (header included), labeled with the `table.row` template. The default reads `"Row 1: Column A, Column B"` for the header, `"Row 2: Cell A1, Cell B1"` for the first body row, and so on. The header row additionally carries the heading trait/role.
|
|
162
|
+
|
|
163
|
+
### Math
|
|
164
|
+
|
|
165
|
+
Each math block exposes a single focusable element labeled `"Math: <latex>"`. The library does **not** convert LaTeX to natural language — the screen reader reads the raw LaTeX source. If you need spoken math, plug in a LaTeX→speech library on the consumer side and translate the labels accordingly.
|
|
166
|
+
|
|
167
|
+
## Known Limitations
|
|
168
|
+
|
|
169
|
+
- **Inline formatting** (bold / italic / underline / strikethrough / inline code / spoiler) is not split into separate accessibility elements — the entire paragraph is read as one element, exactly as the surrounding text. Screen readers don't apply visual emphasis to bold/italic by default.
|
|
170
|
+
- **macOS** screen-reader support is still pending — `MarkdownAccessibilityElementBuilder.m` ships a no-op stub for macOS. Tracked as a TODO; iOS implementation can serve as a reference.
|
|
171
|
+
- **Android** has no rotor concept — `accessibilityLabels.rotor.*` is silently ignored on that platform.
|
|
172
|
+
- **iOS** blockquote backgrounds may break at link boundaries instead of spanning the full line. Visual only; doesn't affect accessibility.
|