react-native-enriched-markdown-scaffold 0.6.0-scaffold.8 → 0.7.4-scaffold.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.
Files changed (213) hide show
  1. package/ReactNativeEnrichedMarkdown.podspec +62 -11
  2. package/android/build.gradle +5 -0
  3. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerDelegate.java +3 -0
  4. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerInterface.java +1 -0
  5. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextInputManagerDelegate.java +12 -0
  6. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextInputManagerInterface.java +4 -0
  7. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +3 -0
  8. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +1 -0
  9. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +25 -0
  10. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +793 -0
  11. package/android/src/main/baseline-prof.txt +75 -2
  12. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +62 -0
  13. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +10 -2
  14. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +10 -0
  15. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +9 -2
  16. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +10 -0
  17. package/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt +6 -4
  18. package/android/src/main/java/com/swmansion/enriched/markdown/accessibility/AccessibilityLabels.kt +15 -0
  19. package/android/src/main/java/com/swmansion/enriched/markdown/accessibility/MarkdownAccessibilityHelper.kt +44 -7
  20. package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputManager.kt +72 -0
  21. package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputView.kt +110 -12
  22. package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/BlockSyntaxSentinel.kt +69 -0
  23. package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/FormattingStore.kt +19 -0
  24. package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/InputParser.kt +84 -6
  25. package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/InputRemend.kt +1 -0
  26. package/android/src/main/java/com/swmansion/enriched/markdown/input/layout/InputLayoutManager.kt +30 -2
  27. package/android/src/main/java/com/swmansion/enriched/markdown/input/layout/InputMeasurementStore.kt +13 -2
  28. package/android/src/main/java/com/swmansion/enriched/markdown/input/model/MathChipStyle.kt +3 -0
  29. package/android/src/main/java/com/swmansion/enriched/markdown/input/toolbar/InputContextMenu.kt +54 -13
  30. package/android/src/main/java/com/swmansion/enriched/markdown/parser/MarkdownASTNode.kt +17 -0
  31. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/HeadingRenderer.kt +1 -0
  32. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +4 -4
  33. package/android/src/main/java/com/swmansion/enriched/markdown/spans/HeadingSpan.kt +8 -1
  34. package/android/src/main/java/com/swmansion/enriched/markdown/spans/LineHeightSpan.kt +27 -1
  35. package/android/src/main/java/com/swmansion/enriched/markdown/styles/InlineMathStyle.kt +5 -1
  36. package/android/src/main/java/com/swmansion/enriched/markdown/styles/MathFontWeight.kt +25 -0
  37. package/android/src/main/java/com/swmansion/enriched/markdown/styles/MathStyle.kt +12 -1
  38. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +37 -7
  39. package/android/src/main/java/com/swmansion/enriched/markdown/utils/input/MarkdownStyleParser.kt +10 -0
  40. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/extensions/SpannableExtensions.kt +12 -4
  41. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkEvents.kt +22 -0
  42. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt +36 -7
  43. package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +74 -4
  44. package/android/src/main/jni/CMakeLists.txt +15 -1
  45. package/android/src/math/java/com/swmansion/enriched/markdown/renderer/MathInlineRenderer.kt +2 -0
  46. package/android/src/math/java/com/swmansion/enriched/markdown/spans/MathChipSpan.kt +1 -1
  47. package/android/src/math/java/com/swmansion/enriched/markdown/spans/MathInlinePlaceholderSpan.kt +7 -3
  48. package/android/src/math/java/com/swmansion/enriched/markdown/spans/MathInlineSpan.kt +8 -5
  49. package/android/src/math/java/com/swmansion/enriched/markdown/spans/MathTypeface.kt +30 -0
  50. package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +27 -3
  51. package/app.plugin.js +2 -1
  52. package/cpp/parser/MD4CParser.cpp +1 -1
  53. package/cpp/wasm/build.sh +9 -9
  54. package/docs/ACCESSIBILITY.md +172 -0
  55. package/docs/API_REFERENCE.md +957 -0
  56. package/docs/COPY_OPTIONS.md +127 -0
  57. package/docs/ELEMENTS_STRUCTURE.md +112 -0
  58. package/docs/IMAGE_CACHING.md +25 -0
  59. package/docs/INPUT.md +281 -0
  60. package/docs/LATEX_MATH.md +198 -0
  61. package/docs/MACOS.md +19 -0
  62. package/docs/MARKDOWN_STREAMING.md +36 -0
  63. package/docs/MENTIONS.md +97 -0
  64. package/docs/RTL.md +82 -0
  65. package/docs/STYLES.md +522 -0
  66. package/docs/TEXT.md +110 -0
  67. package/docs/WEB.md +49 -0
  68. package/ios/EnrichedMarkdown.mm +126 -37
  69. package/ios/EnrichedMarkdownText.mm +84 -16
  70. package/ios/attachments/ENRMMathInlineAttachment+macOS.m +5 -1
  71. package/ios/attachments/ENRMMathInlineAttachment.h +3 -0
  72. package/ios/attachments/ENRMMathInlineAttachment.m +2 -1
  73. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp +25 -0
  74. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +793 -0
  75. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +15 -0
  76. package/ios/input/ENRMBlockSyntaxSentinel.h +25 -0
  77. package/ios/input/ENRMBlockSyntaxSentinel.mm +80 -0
  78. package/ios/input/ENRMFormattingStore.h +5 -0
  79. package/ios/input/ENRMFormattingStore.mm +34 -0
  80. package/ios/input/ENRMInputFormatter.h +4 -0
  81. package/ios/input/ENRMInputFormatter.mm +2 -0
  82. package/ios/input/ENRMInputParser.h +5 -0
  83. package/ios/input/ENRMInputParser.mm +79 -6
  84. package/ios/input/ENRMInputTextView.h +2 -0
  85. package/ios/input/ENRMInputTextView.mm +8 -17
  86. package/ios/input/ENRMMarkdownSerializer.mm +1 -1
  87. package/ios/input/EnrichedMarkdownTextInput+ContextMenu.mm +71 -53
  88. package/ios/input/EnrichedMarkdownTextInput+Internal.h +26 -0
  89. package/ios/input/EnrichedMarkdownTextInput.h +1 -0
  90. package/ios/input/EnrichedMarkdownTextInput.mm +249 -31
  91. package/ios/input/InputStylePropsUtils.h +13 -0
  92. package/ios/input/styles/ENRMBoldStyleHandler.mm +3 -2
  93. package/ios/input/styles/ENRMItalicStyleHandler.mm +3 -2
  94. package/ios/internals/MeasurementCache.h +1 -0
  95. package/ios/math/ENRMRaTeXBridge.swift +52 -3
  96. package/ios/parser/ENRMMarkdownParser.h +8 -0
  97. package/ios/parser/ENRMMarkdownParser.mm +11 -0
  98. package/ios/renderer/AttributedRenderer.m +51 -6
  99. package/ios/renderer/ENRMMathInlineRenderer.m +8 -1
  100. package/ios/renderer/ListItemRenderer.m +8 -5
  101. package/ios/styles/ENRMFontSlot.h +1 -1
  102. package/ios/styles/StyleConfig.h +6 -0
  103. package/ios/styles/StyleConfig.mm +36 -0
  104. package/ios/utils/ENRMAccessibilityLabels.h +36 -0
  105. package/ios/utils/ENRMAccessibilityLabels.m +5 -0
  106. package/ios/utils/ENRMMathFontWeight.h +18 -0
  107. package/ios/utils/EditMenuUtils+macOS.m +4 -5
  108. package/ios/utils/EditMenuUtils.h +70 -0
  109. package/ios/utils/EditMenuUtils.m +14 -11
  110. package/ios/utils/LastElementUtils.h +39 -2
  111. package/ios/utils/MarkdownAccessibilityElementBuilder.h +18 -16
  112. package/ios/utils/MarkdownAccessibilityElementBuilder.m +64 -32
  113. package/ios/utils/ParagraphStyleUtils.m +21 -2
  114. package/ios/utils/StylePropsUtils.h +21 -0
  115. package/ios/views/ENRMMathContainerView.h +8 -0
  116. package/ios/views/ENRMMathContainerView.m +10 -6
  117. package/ios/views/ENRMTableIOSGridView.h +40 -0
  118. package/ios/views/ENRMTableIOSGridView.m +216 -0
  119. package/ios/views/EnrichedMarkdownInternalText.h +2 -0
  120. package/ios/views/EnrichedMarkdownInternalText.m +2 -0
  121. package/ios/views/TableContainerView.h +8 -0
  122. package/ios/views/TableContainerView.m +72 -128
  123. package/lib/module/EnrichedMarkdownNativeComponent.ts +40 -0
  124. package/lib/module/EnrichedMarkdownTextInput.js +50 -1
  125. package/lib/module/EnrichedMarkdownTextInput.js.map +1 -1
  126. package/lib/module/EnrichedMarkdownTextInputNativeComponent.ts +47 -0
  127. package/lib/module/EnrichedMarkdownTextNativeComponent.ts +40 -0
  128. package/lib/module/accessibilityLabelDefaults.js +64 -0
  129. package/lib/module/accessibilityLabelDefaults.js.map +1 -0
  130. package/lib/module/index.js +1 -0
  131. package/lib/module/index.js.map +1 -1
  132. package/lib/module/native/EnrichedMarkdownText.js +72 -4
  133. package/lib/module/native/EnrichedMarkdownText.js.map +1 -1
  134. package/lib/module/normalizeMarkdownStyle.js +5 -2
  135. package/lib/module/normalizeMarkdownStyle.js.map +1 -1
  136. package/lib/module/normalizeMarkdownStyle.web.js +5 -2
  137. package/lib/module/normalizeMarkdownStyle.web.js.map +1 -1
  138. package/lib/module/normalizeMarkdownTextInputStyle.js +7 -3
  139. package/lib/module/normalizeMarkdownTextInputStyle.js.map +1 -1
  140. package/lib/module/normalizeMenuItem.js +38 -0
  141. package/lib/module/normalizeMenuItem.js.map +1 -0
  142. package/lib/module/types/AccessibilityLabels.js +2 -0
  143. package/lib/module/types/AccessibilityLabels.js.map +1 -0
  144. package/lib/module/web/globalStyles.js +8 -1
  145. package/lib/module/web/globalStyles.js.map +1 -1
  146. package/lib/module/web/styles.js +3 -1
  147. package/lib/module/web/styles.js.map +1 -1
  148. package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +37 -0
  149. package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
  150. package/lib/typescript/src/EnrichedMarkdownTextInput.d.ts +57 -1
  151. package/lib/typescript/src/EnrichedMarkdownTextInput.d.ts.map +1 -1
  152. package/lib/typescript/src/EnrichedMarkdownTextInputNativeComponent.d.ts +42 -0
  153. package/lib/typescript/src/EnrichedMarkdownTextInputNativeComponent.d.ts.map +1 -1
  154. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +37 -0
  155. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
  156. package/lib/typescript/src/accessibilityLabelDefaults.d.ts +16 -0
  157. package/lib/typescript/src/accessibilityLabelDefaults.d.ts.map +1 -0
  158. package/lib/typescript/src/index.d.ts +4 -2
  159. package/lib/typescript/src/index.d.ts.map +1 -1
  160. package/lib/typescript/src/native/EnrichedMarkdownText.d.ts +3 -3
  161. package/lib/typescript/src/native/EnrichedMarkdownText.d.ts.map +1 -1
  162. package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -1
  163. package/lib/typescript/src/normalizeMarkdownStyle.web.d.ts.map +1 -1
  164. package/lib/typescript/src/normalizeMarkdownTextInputStyle.d.ts +1 -0
  165. package/lib/typescript/src/normalizeMarkdownTextInputStyle.d.ts.map +1 -1
  166. package/lib/typescript/src/normalizeMenuItem.d.ts +7 -0
  167. package/lib/typescript/src/normalizeMenuItem.d.ts.map +1 -0
  168. package/lib/typescript/src/types/AccessibilityLabels.d.ts +114 -0
  169. package/lib/typescript/src/types/AccessibilityLabels.d.ts.map +1 -0
  170. package/lib/typescript/src/types/MarkdownStyle.d.ts +32 -0
  171. package/lib/typescript/src/types/MarkdownStyle.d.ts.map +1 -1
  172. package/lib/typescript/src/types/MarkdownStyleInternal.d.ts +3 -0
  173. package/lib/typescript/src/types/MarkdownStyleInternal.d.ts.map +1 -1
  174. package/lib/typescript/src/types/MarkdownTextProps.d.ts +78 -9
  175. package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
  176. package/lib/typescript/src/web/styles.d.ts.map +1 -1
  177. package/package.json +4 -2
  178. package/plugin/build/withAndroidMath.js +19 -0
  179. package/plugin/build/withIosMath.js +28 -0
  180. package/plugin/build/withReactNativeEnrichedMarkdown.js +11 -0
  181. package/plugin/tsconfig.build.json +16 -0
  182. package/src/EnrichedMarkdownNativeComponent.ts +40 -0
  183. package/src/EnrichedMarkdownTextInput.tsx +121 -1
  184. package/src/EnrichedMarkdownTextInputNativeComponent.ts +47 -0
  185. package/src/EnrichedMarkdownTextNativeComponent.ts +40 -0
  186. package/src/accessibilityLabelDefaults.ts +83 -0
  187. package/src/index.tsx +11 -0
  188. package/src/native/EnrichedMarkdownText.tsx +120 -6
  189. package/src/normalizeMarkdownStyle.ts +6 -1
  190. package/src/normalizeMarkdownStyle.web.ts +6 -1
  191. package/src/normalizeMarkdownTextInputStyle.ts +5 -0
  192. package/src/normalizeMenuItem.ts +52 -0
  193. package/src/types/AccessibilityLabels.ts +118 -0
  194. package/src/types/MarkdownStyle.ts +32 -0
  195. package/src/types/MarkdownStyleInternal.ts +3 -0
  196. package/src/types/MarkdownTextProps.ts +74 -9
  197. package/src/web/globalStyles.ts +7 -0
  198. package/src/web/styles.ts +5 -1
  199. package/lib/module/plugin/withAndroidMath.js +0 -20
  200. package/lib/module/plugin/withAndroidMath.js.map +0 -1
  201. package/lib/module/plugin/withIosMath.js +0 -23
  202. package/lib/module/plugin/withIosMath.js.map +0 -1
  203. package/lib/module/plugin/withReactNativeEnrichedMarkdown.js +0 -16
  204. package/lib/module/plugin/withReactNativeEnrichedMarkdown.js.map +0 -1
  205. package/lib/typescript/src/plugin/withAndroidMath.d.ts +0 -5
  206. package/lib/typescript/src/plugin/withAndroidMath.d.ts.map +0 -1
  207. package/lib/typescript/src/plugin/withIosMath.d.ts +0 -5
  208. package/lib/typescript/src/plugin/withIosMath.d.ts.map +0 -1
  209. package/lib/typescript/src/plugin/withReactNativeEnrichedMarkdown.d.ts +0 -6
  210. package/lib/typescript/src/plugin/withReactNativeEnrichedMarkdown.d.ts.map +0 -1
  211. /package/{src/plugin → plugin/src}/withAndroidMath.ts +0 -0
  212. /package/{src/plugin → plugin/src}/withIosMath.ts +0 -0
  213. /package/{src/plugin → plugin/src}/withReactNativeEnrichedMarkdown.ts +0 -0
@@ -38,6 +38,7 @@ import type {
38
38
  ColorValue,
39
39
  } from 'react-native';
40
40
  import { normalizeMarkdownTextInputStyle } from './normalizeMarkdownTextInputStyle';
41
+ import { normalizeMenuItem } from './normalizeMenuItem';
41
42
  import { toNativeRegexConfig } from './utils/regexParser';
42
43
  import type { RefObject } from 'react';
43
44
 
@@ -60,6 +61,7 @@ export interface MathStyle {
60
61
  paddingVertical?: number;
61
62
  selectedBackgroundColor?: string;
62
63
  selectedBorderColor?: string;
64
+ fontWeight?: 'bold' | 'normal';
63
65
  }
64
66
 
65
67
  export interface MathBlockStyle extends MathStyle {
@@ -149,10 +151,48 @@ export interface EnrichedMarkdownTextInputInstance {
149
151
  options?: InsertMathOptions
150
152
  ) => void;
151
153
  removeMath: (start: number) => void;
154
+ copyToClipboard: () => void;
152
155
  getMarkdown: () => Promise<string>;
153
156
  getCaretRect: () => Promise<CaretRect>;
154
157
  }
155
158
 
159
+ /**
160
+ * Per-item shape: `{ enabled }` toggles visibility, `label` overrides the
161
+ * English default. Wire `label` to your i18n library to localize the menu.
162
+ */
163
+ type MenuItem = { enabled?: boolean; label?: string };
164
+
165
+ export interface InputSelectionMenuConfig {
166
+ /**
167
+ * The built-in "Format" submenu (Bold, Italic, Underline, etc.) in the
168
+ * text selection context menu. `label` overrides the submenu title.
169
+ * @default { enabled: true, label: "Format" }
170
+ */
171
+ format?: MenuItem;
172
+ /**
173
+ * The built-in "Copy as Markdown" action in the text selection context
174
+ * menu. `label` overrides the action title.
175
+ * @default { enabled: true, label: "Copy as Markdown" }
176
+ */
177
+ copyAsMarkdown?: MenuItem;
178
+ }
179
+
180
+ /** Controls the individual items inside the Format submenu. */
181
+ export interface FormatMenuConfig {
182
+ /** @default { enabled: true, label: "Bold" } */
183
+ bold?: MenuItem;
184
+ /** @default { enabled: true, label: "Italic" } */
185
+ italic?: MenuItem;
186
+ /** @default { enabled: true, label: "Underline" } */
187
+ underline?: MenuItem;
188
+ /** @default { enabled: true, label: "Strikethrough" } */
189
+ strikethrough?: MenuItem;
190
+ /** @default { enabled: true, label: "Spoiler" } */
191
+ spoiler?: MenuItem;
192
+ /** @default { enabled: true, label: "Link" } */
193
+ link?: MenuItem;
194
+ }
195
+
156
196
  export interface EnrichedMarkdownTextInputProps extends Omit<
157
197
  ViewProps,
158
198
  'style' | 'children'
@@ -164,6 +204,7 @@ export interface EnrichedMarkdownTextInputProps extends Omit<
164
204
  editable?: boolean;
165
205
  autoFocus?: boolean;
166
206
  scrollEnabled?: boolean;
207
+ blockSyntax?: boolean;
167
208
  autoCapitalize?: string;
168
209
  multiline?: boolean;
169
210
  cursorColor?: ColorValue;
@@ -184,6 +225,22 @@ export interface EnrichedMarkdownTextInputProps extends Omit<
184
225
  onFocus?: () => void;
185
226
  onBlur?: () => void;
186
227
  contextMenuItems?: ContextMenuItem[];
228
+ /**
229
+ * Controls built-in items in the text selection context menu.
230
+ * Omitting the prop or any field reproduces today's exact menu.
231
+ * Custom app-provided actions are controlled separately via `contextMenuItems`.
232
+ * @default { format: true, copyAsMarkdown: true }
233
+ * @platform ios, android, macos
234
+ */
235
+ selectionMenuConfig?: InputSelectionMenuConfig;
236
+ /**
237
+ * Controls which items appear inside the Format submenu.
238
+ * Only effective when `selectionMenuConfig.format` is `true` (the default).
239
+ * Omitting the prop or any field shows all items.
240
+ * @default { bold: true, italic: true, underline: true, strikethrough: true, spoiler: true, link: true }
241
+ * @platform ios, android, macos
242
+ */
243
+ formatMenuConfig?: FormatMenuConfig;
187
244
  linkRegex?: RegExp | null;
188
245
  /**
189
246
  * Paragraph writing direction.
@@ -228,6 +285,7 @@ export const EnrichedMarkdownTextInput = ({
228
285
  editable = true,
229
286
  autoFocus = false,
230
287
  scrollEnabled = true,
288
+ blockSyntax = true,
231
289
  autoCapitalize = 'sentences',
232
290
  multiline = true,
233
291
  cursorColor,
@@ -246,11 +304,15 @@ export const EnrichedMarkdownTextInput = ({
246
304
  onFocus,
247
305
  onBlur,
248
306
  contextMenuItems,
307
+ selectionMenuConfig,
308
+ formatMenuConfig,
249
309
  linkRegex: _linkRegex,
250
310
  writingDirection = 'first-strong',
251
311
  ...rest
252
312
  }: EnrichedMarkdownTextInputProps) => {
253
313
  const nativeRef = useRef<NativeRef | null>(null);
314
+ // Freeze `defaultValue` at mount (RN TextInput semantics): post-mount changes are ignored.
315
+ const initialDefaultValue = useRef(defaultValue).current;
254
316
 
255
317
  const nextRequestId = useRef(1);
256
318
  const pendingRequests = useRef(new Map<number, PendingRequest<string>>());
@@ -294,6 +356,60 @@ export const EnrichedMarkdownTextInput = ({
294
356
 
295
357
  const normalizedStyle = normalizeMarkdownTextInputStyle(markdownStyle);
296
358
 
359
+ const normalizedSelectionMenuConfig = useMemo(() => {
360
+ const format = normalizeMenuItem(
361
+ selectionMenuConfig?.format,
362
+ true,
363
+ 'Format'
364
+ );
365
+ const copyAsMarkdown = normalizeMenuItem(
366
+ selectionMenuConfig?.copyAsMarkdown,
367
+ true,
368
+ 'Copy as Markdown'
369
+ );
370
+ return {
371
+ format: format.enabled,
372
+ formatLabel: format.label,
373
+ copyAsMarkdown: copyAsMarkdown.enabled,
374
+ copyAsMarkdownLabel: copyAsMarkdown.label,
375
+ };
376
+ }, [selectionMenuConfig]);
377
+
378
+ const normalizedFormatMenuConfig = useMemo(() => {
379
+ const bold = normalizeMenuItem(formatMenuConfig?.bold, true, 'Bold');
380
+ const italic = normalizeMenuItem(formatMenuConfig?.italic, true, 'Italic');
381
+ const underline = normalizeMenuItem(
382
+ formatMenuConfig?.underline,
383
+ true,
384
+ 'Underline'
385
+ );
386
+ const strikethrough = normalizeMenuItem(
387
+ formatMenuConfig?.strikethrough,
388
+ true,
389
+ 'Strikethrough'
390
+ );
391
+ const spoiler = normalizeMenuItem(
392
+ formatMenuConfig?.spoiler,
393
+ true,
394
+ 'Spoiler'
395
+ );
396
+ const link = normalizeMenuItem(formatMenuConfig?.link, true, 'Link');
397
+ return {
398
+ bold: bold.enabled,
399
+ boldLabel: bold.label,
400
+ italic: italic.enabled,
401
+ italicLabel: italic.label,
402
+ underline: underline.enabled,
403
+ underlineLabel: underline.label,
404
+ strikethrough: strikethrough.enabled,
405
+ strikethroughLabel: strikethrough.label,
406
+ spoiler: spoiler.enabled,
407
+ spoilerLabel: spoiler.label,
408
+ link: link.enabled,
409
+ linkLabel: link.label,
410
+ };
411
+ }, [formatMenuConfig]);
412
+
297
413
  const linkRegex = useMemo(
298
414
  () => toNativeRegexConfig(_linkRegex),
299
415
  [_linkRegex]
@@ -476,6 +592,7 @@ export const EnrichedMarkdownTextInput = ({
476
592
  options?.display ?? false
477
593
  ),
478
594
  removeMath: (start) => Commands.removeMath(commandRef, start),
595
+ copyToClipboard: () => Commands.copyToClipboard(commandRef),
479
596
  getMarkdown: () =>
480
597
  new Promise<string>((resolve, reject) => {
481
598
  const requestId = nextRequestId.current++;
@@ -499,12 +616,13 @@ export const EnrichedMarkdownTextInput = ({
499
616
  ref={nativeRef}
500
617
  style={style}
501
618
  markdownStyle={normalizedStyle}
502
- defaultValue={defaultValue}
619
+ defaultValue={initialDefaultValue}
503
620
  placeholder={placeholder}
504
621
  placeholderTextColor={placeholderTextColor}
505
622
  editable={editable}
506
623
  autoFocus={autoFocus}
507
624
  scrollEnabled={scrollEnabled}
625
+ blockSyntax={blockSyntax}
508
626
  autoCapitalize={autoCapitalize}
509
627
  multiline={multiline}
510
628
  cursorColor={cursorColor}
@@ -530,6 +648,8 @@ export const EnrichedMarkdownTextInput = ({
530
648
  handleCaretRectChange as NativeProps['onCaretRectChange']
531
649
  }
532
650
  contextMenuItems={nativeContextMenuItems}
651
+ selectionMenuConfig={normalizedSelectionMenuConfig}
652
+ formatMenuConfig={normalizedFormatMenuConfig}
533
653
  mentionIndicators={mentionIndicators}
534
654
  onContextMenuItemPress={
535
655
  handleContextMenuItemPress as NativeProps['onContextMenuItemPress']
@@ -19,6 +19,7 @@ interface MathStyleInternal {
19
19
  paddingVertical: CodegenTypes.Float;
20
20
  selectedBackgroundColor: ColorValue;
21
21
  selectedBorderColor: ColorValue;
22
+ fontWeight: string;
22
23
  }
23
24
 
24
25
  interface MathBlockStyleInternal extends MathStyleInternal {
@@ -145,6 +146,28 @@ export interface ContextMenuItemConfig {
145
146
  icon?: string;
146
147
  }
147
148
 
149
+ export interface InputSelectionMenuConfigInternal {
150
+ format: boolean;
151
+ formatLabel: string;
152
+ copyAsMarkdown: boolean;
153
+ copyAsMarkdownLabel: string;
154
+ }
155
+
156
+ export interface FormatMenuConfigInternal {
157
+ bold: boolean;
158
+ boldLabel: string;
159
+ italic: boolean;
160
+ italicLabel: string;
161
+ underline: boolean;
162
+ underlineLabel: string;
163
+ strikethrough: boolean;
164
+ strikethroughLabel: string;
165
+ spoiler: boolean;
166
+ spoilerLabel: string;
167
+ link: boolean;
168
+ linkLabel: string;
169
+ }
170
+
148
171
  export interface OnContextMenuItemPressEvent {
149
172
  itemText: string;
150
173
  selectedText: string;
@@ -188,6 +211,17 @@ export interface NativeProps extends ViewProps {
188
211
  * @default true
189
212
  */
190
213
  scrollEnabled?: boolean;
214
+ /**
215
+ * Whether block-level markdown syntax is interpreted.
216
+ *
217
+ * When `false`, list markers, headings, blockquotes, tables, code blocks and
218
+ * thematic breaks stay in the text verbatim instead of being consumed as
219
+ * structure, so the markdown round-trips byte-for-byte. Inline syntax
220
+ * (`**bold**`, links, `$math$`) still applies. `\r\n` is normalized to `\n`
221
+ * and `U+000B` is stripped from the input.
222
+ * @default true
223
+ */
224
+ blockSyntax?: boolean;
191
225
  /**
192
226
  * Auto-capitalization behavior.
193
227
  */
@@ -230,6 +264,17 @@ export interface NativeProps extends ViewProps {
230
264
  */
231
265
  contextMenuItems?: ReadonlyArray<Readonly<ContextMenuItemConfig>>;
232
266
 
267
+ /**
268
+ * Controls built-in items in the text selection context menu.
269
+ * `format` toggles the Format submenu; `copyAsMarkdown` toggles the Copy as Markdown action.
270
+ */
271
+ selectionMenuConfig: Readonly<InputSelectionMenuConfigInternal>;
272
+
273
+ /**
274
+ * Controls which items appear inside the Format submenu.
275
+ */
276
+ formatMenuConfig: Readonly<FormatMenuConfigInternal>;
277
+
233
278
  /**
234
279
  * Regex configuration for automatic link detection.
235
280
  * Omit or pass undefined to use platform defaults.
@@ -322,6 +367,7 @@ interface NativeCommands {
322
367
  viewRef: React.ElementRef<ComponentType>,
323
368
  start: CodegenTypes.Int32
324
369
  ) => void;
370
+ copyToClipboard: (viewRef: React.ElementRef<ComponentType>) => void;
325
371
  requestMarkdown: (
326
372
  viewRef: React.ElementRef<ComponentType>,
327
373
  requestId: CodegenTypes.Int32
@@ -351,6 +397,7 @@ export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
351
397
  'insertMath',
352
398
  'updateMath',
353
399
  'removeMath',
400
+ 'copyToClipboard',
354
401
  'requestMarkdown',
355
402
  'requestCaretRect',
356
403
  ],
@@ -141,10 +141,13 @@ interface MathStyleInternal {
141
141
  marginTop: CodegenTypes.Float;
142
142
  marginBottom: CodegenTypes.Float;
143
143
  textAlign: string;
144
+ fontWeight: string;
144
145
  }
145
146
 
146
147
  interface InlineMathStyleInternal {
147
148
  color: ColorValue;
149
+ paddingVertical: CodegenTypes.Float;
150
+ fontWeight: string;
148
151
  }
149
152
 
150
153
  interface SpoilerParticlesStyleInternal {
@@ -230,6 +233,13 @@ export interface ContextMenuItemConfig {
230
233
  export interface SelectionMenuConfig {
231
234
  copyAsMarkdown: boolean;
232
235
  copyImageUrl: boolean;
236
+ copyLabel: string;
237
+ copyAsMarkdownLabel: string;
238
+ copyImageUrlLabel: string;
239
+ copyImageUrlsLabel: string;
240
+ // Precomputed plural templates indexed by image count (0..100). Empty when no
241
+ // pluralLabels are set; native then uses copyImageUrlLabel/copyImageUrlsLabel.
242
+ copyImageUrlPluralTemplates: ReadonlyArray<string>;
233
243
  }
234
244
 
235
245
  export interface OnContextMenuItemPressEvent {
@@ -239,6 +249,35 @@ export interface OnContextMenuItemPressEvent {
239
249
  selectionEnd: CodegenTypes.Int32;
240
250
  }
241
251
 
252
+ export interface AccessibilityLabelsListProps {
253
+ bulletPoint: string;
254
+ nestedBulletPoint: string;
255
+ orderedItem: string;
256
+ nestedOrderedItem: string;
257
+ }
258
+ export interface AccessibilityLabelsBlockquoteProps {
259
+ quote: string;
260
+ nestedQuote: string;
261
+ }
262
+ export interface AccessibilityLabelsTableProps {
263
+ row: string;
264
+ }
265
+ export interface AccessibilityLabelsMathProps {
266
+ equation: string;
267
+ }
268
+ export interface AccessibilityLabelsRotorProps {
269
+ headings: string;
270
+ links: string;
271
+ images: string;
272
+ }
273
+ export interface AccessibilityLabelsProps {
274
+ list: Readonly<AccessibilityLabelsListProps>;
275
+ blockquote: Readonly<AccessibilityLabelsBlockquoteProps>;
276
+ table: Readonly<AccessibilityLabelsTableProps>;
277
+ math: Readonly<AccessibilityLabelsMathProps>;
278
+ rotor: Readonly<AccessibilityLabelsRotorProps>;
279
+ }
280
+
242
281
  /**
243
282
  * MD4C parser flags configuration.
244
283
  * Controls how the markdown parser interprets certain syntax.
@@ -393,6 +432,7 @@ export interface NativeProps extends ViewProps {
393
432
  * Built-in items to show in the text selection context menu.
394
433
  */
395
434
  selectionMenuConfig: Readonly<SelectionMenuConfig>;
435
+ accessibilityLabels: Readonly<AccessibilityLabelsProps>;
396
436
  /**
397
437
  * Fired when a custom context menu item is pressed.
398
438
  * Receives the item label, the currently selected text, and the selection range.
@@ -0,0 +1,83 @@
1
+ import type {
2
+ AccessibilityLabels,
3
+ ResolvedAccessibilityLabels,
4
+ } from './types/AccessibilityLabels';
5
+
6
+ /**
7
+ * Default strings spoken by VoiceOver / TalkBack when the consumer does not
8
+ * provide an override. See `AccessibilityLabels` for the placeholder syntax
9
+ * and the rationale for the no-plural cardinal form.
10
+ */
11
+ export const DEFAULT_ACCESSIBILITY_LABELS: ResolvedAccessibilityLabels = {
12
+ list: {
13
+ bulletPoint: 'Bullet point',
14
+ nestedBulletPoint: 'Nested bullet point',
15
+ orderedItem: 'List item {n}',
16
+ nestedOrderedItem: 'Nested list item {n}',
17
+ },
18
+ blockquote: {
19
+ quote: 'Blockquote',
20
+ nestedQuote: 'Nested blockquote',
21
+ },
22
+ table: {
23
+ row: 'Row {n}: {content}',
24
+ },
25
+ math: {
26
+ equation: 'Math: {latex}',
27
+ },
28
+ rotor: {
29
+ headings: 'Headings',
30
+ links: 'Links',
31
+ images: 'Images',
32
+ },
33
+ };
34
+
35
+ /**
36
+ * Resolves a partial `AccessibilityLabels` against `DEFAULT_ACCESSIBILITY_LABELS`,
37
+ * returning a fully-populated struct that native code can consume without any
38
+ * null checks. Override resolution is shallow per sub-group: if a consumer
39
+ * passes `{ list: { bulletPoint: 'Punkt' } }`, only that one field changes and
40
+ * the other list entries fall back to the defaults.
41
+ */
42
+ export function resolveAccessibilityLabels(
43
+ labels: AccessibilityLabels | undefined
44
+ ): ResolvedAccessibilityLabels {
45
+ return {
46
+ list: {
47
+ bulletPoint:
48
+ labels?.list?.bulletPoint ??
49
+ DEFAULT_ACCESSIBILITY_LABELS.list.bulletPoint,
50
+ nestedBulletPoint:
51
+ labels?.list?.nestedBulletPoint ??
52
+ DEFAULT_ACCESSIBILITY_LABELS.list.nestedBulletPoint,
53
+ orderedItem:
54
+ labels?.list?.orderedItem ??
55
+ DEFAULT_ACCESSIBILITY_LABELS.list.orderedItem,
56
+ nestedOrderedItem:
57
+ labels?.list?.nestedOrderedItem ??
58
+ DEFAULT_ACCESSIBILITY_LABELS.list.nestedOrderedItem,
59
+ },
60
+ blockquote: {
61
+ quote:
62
+ labels?.blockquote?.quote ??
63
+ DEFAULT_ACCESSIBILITY_LABELS.blockquote.quote,
64
+ nestedQuote:
65
+ labels?.blockquote?.nestedQuote ??
66
+ DEFAULT_ACCESSIBILITY_LABELS.blockquote.nestedQuote,
67
+ },
68
+ table: {
69
+ row: labels?.table?.row ?? DEFAULT_ACCESSIBILITY_LABELS.table.row,
70
+ },
71
+ math: {
72
+ equation:
73
+ labels?.math?.equation ?? DEFAULT_ACCESSIBILITY_LABELS.math.equation,
74
+ },
75
+ rotor: {
76
+ headings:
77
+ labels?.rotor?.headings ?? DEFAULT_ACCESSIBILITY_LABELS.rotor.headings,
78
+ links: labels?.rotor?.links ?? DEFAULT_ACCESSIBILITY_LABELS.rotor.links,
79
+ images:
80
+ labels?.rotor?.images ?? DEFAULT_ACCESSIBILITY_LABELS.rotor.images,
81
+ },
82
+ };
83
+ }
package/src/index.tsx CHANGED
@@ -6,12 +6,21 @@ export type {
6
6
  Md4cFlags,
7
7
  ContextMenuItem as TextContextMenuItem,
8
8
  SelectionMenuConfig as TextSelectionMenuConfig,
9
+ SelectionMenuPluralLabels as TextSelectionMenuPluralLabels,
9
10
  } from './native/EnrichedMarkdownText';
10
11
  export type {
11
12
  LinkPressEvent,
12
13
  LinkLongPressEvent,
13
14
  TaskListItemPressEvent,
14
15
  } from './types/events';
16
+ export type {
17
+ AccessibilityLabels,
18
+ ResolvedAccessibilityLabels,
19
+ } from './types/AccessibilityLabels';
20
+ export {
21
+ DEFAULT_ACCESSIBILITY_LABELS,
22
+ resolveAccessibilityLabels,
23
+ } from './accessibilityLabelDefaults';
15
24
 
16
25
  export { EnrichedMarkdownTextInput } from './EnrichedMarkdownTextInput';
17
26
  export type {
@@ -20,6 +29,8 @@ export type {
20
29
  MarkdownTextInputStyle,
21
30
  StyleState,
22
31
  ContextMenuItem,
32
+ InputSelectionMenuConfig,
33
+ FormatMenuConfig,
23
34
  OnLinkDetected,
24
35
  OnStartMentionEvent,
25
36
  OnChangeMentionEvent,
@@ -3,6 +3,11 @@ import EnrichedMarkdownTextNativeComponent from '../EnrichedMarkdownTextNativeCo
3
3
  import type { MarkdownStyleInternal } from '../EnrichedMarkdownTextNativeComponent';
4
4
  import EnrichedMarkdownNativeComponent from '../EnrichedMarkdownNativeComponent';
5
5
  import { normalizeMarkdownStyle } from '../normalizeMarkdownStyle';
6
+ import { resolveAccessibilityLabels } from '../accessibilityLabelDefaults';
7
+ import {
8
+ normalizeMenuItem,
9
+ normalizeLegacyBooleanMenuItem,
10
+ } from '../normalizeMenuItem';
6
11
  import type { NativeSyntheticEvent } from 'react-native';
7
12
  import type { MarkdownStyle, Md4cFlags } from '../types/MarkdownStyle';
8
13
  import type {
@@ -10,6 +15,7 @@ import type {
10
15
  StreamingConfig,
11
16
  ContextMenuItem,
12
17
  SelectionMenuConfig,
18
+ SelectionMenuPluralLabels,
13
19
  } from '../types/MarkdownTextProps';
14
20
  import type {
15
21
  LinkPressEvent,
@@ -24,9 +30,71 @@ export type {
24
30
  StreamingConfig,
25
31
  ContextMenuItem,
26
32
  SelectionMenuConfig,
33
+ SelectionMenuPluralLabels,
27
34
  };
28
35
  export type { LinkPressEvent, LinkLongPressEvent, TaskListItemPressEvent };
29
36
 
37
+ // Default English labels for the built-in selection menu actions. Defaults are
38
+ // resolved here (JS-side) so the native code always receives a concrete string.
39
+ const DEFAULT_COPY_LABEL = 'Copy';
40
+ const DEFAULT_COPY_AS_MARKDOWN_LABEL = 'Copy as Markdown';
41
+ const DEFAULT_COPY_IMAGE_URL_LABEL = 'Copy Image URL';
42
+ const DEFAULT_COPY_IMAGE_URLS_LABEL = 'Copy {count} Image URLs';
43
+
44
+ /**
45
+ * Resolves `pluralLabels` into a per-count template table (index = image count,
46
+ * 0..100) the native side can index without any locale logic. `Intl.PluralRules`
47
+ * selects the CLDR category for each count; missing categories fall back to the
48
+ * required `other`. Index 1 uses the singular `label` (the single-image case).
49
+ * Native uses `other` (copyImageUrlsLabel) for counts > 100, and falls back to
50
+ * the singular/`{count}` templates entirely when this returns an empty array
51
+ * (no plural labels set, or `Intl.PluralRules` unavailable).
52
+ */
53
+ const buildPluralTemplates = (
54
+ pluralLabels: SelectionMenuPluralLabels | undefined,
55
+ singularLabel: string
56
+ ): string[] => {
57
+ if (!pluralLabels) return [];
58
+
59
+ const IntlRef = Intl as unknown as {
60
+ PluralRules?: new (locales?: string | string[]) => {
61
+ select(n: number): string;
62
+ };
63
+ };
64
+ if (typeof IntlRef.PluralRules !== 'function') return [];
65
+
66
+ let pluralRules: { select(n: number): string };
67
+ try {
68
+ // TODO: expose `copyImageUrl.pluralLocale?: string` so i18n apps can force
69
+ // a locale independent of the device. Today this resolves to the JS
70
+ // runtime's default locale, so on an English device only `one` and `other`
71
+ // ever fire even when richer plural labels are configured.
72
+ pluralRules = new IntlRef.PluralRules();
73
+ } catch {
74
+ return [];
75
+ }
76
+
77
+ const { other } = pluralLabels;
78
+ const byCategory: Record<string, string> = {
79
+ zero: pluralLabels.zero ?? other,
80
+ one: pluralLabels.one ?? other,
81
+ two: pluralLabels.two ?? other,
82
+ few: pluralLabels.few ?? other,
83
+ many: pluralLabels.many ?? other,
84
+ other,
85
+ };
86
+
87
+ const templates: string[] = [];
88
+ for (let n = 0; n <= 100; n++) {
89
+ // The literal single-image case uses the (non-plural) singular label; every
90
+ // other count uses its CLDR category form.
91
+ templates.push(
92
+ n === 1 ? singularLabel : (byCategory[pluralRules.select(n)] ?? other)
93
+ );
94
+ }
95
+ return templates;
96
+ };
97
+
30
98
  const defaultMd4cFlags: Md4cFlags = {
31
99
  underline: false,
32
100
  superscript: false,
@@ -54,6 +122,7 @@ export const EnrichedMarkdownText = ({
54
122
  spoilerOverlay = 'particles',
55
123
  contextMenuItems,
56
124
  selectionMenuConfig,
125
+ accessibilityLabels,
57
126
  selectionColor,
58
127
  selectionHandleColor,
59
128
  textBreakStrategy,
@@ -142,12 +211,56 @@ export const EnrichedMarkdownText = ({
142
211
 
143
212
  const tableMode = streamingConfig?.tableMode ?? 'progressive';
144
213
  const normalizedStreamingConfig = useMemo(() => ({ tableMode }), [tableMode]);
145
- const normalizedSelectionMenuConfig = useMemo(
146
- () => ({
147
- copyAsMarkdown: selectionMenuConfig?.copyAsMarkdown ?? true,
148
- copyImageUrl: selectionMenuConfig?.copyImageUrl ?? true,
149
- }),
150
- [selectionMenuConfig]
214
+ const normalizedSelectionMenuConfig = useMemo(() => {
215
+ // The boolean acceptance is confined to this wrapper boundary via a single
216
+ // `as unknown` cast; the public type only exposes the object shape.
217
+ const config = selectionMenuConfig as
218
+ | {
219
+ copy?: unknown;
220
+ copyAsMarkdown?: unknown;
221
+ copyImageUrl?: unknown;
222
+ }
223
+ | undefined;
224
+
225
+ const copy = normalizeMenuItem(config?.copy, true, DEFAULT_COPY_LABEL);
226
+ const copyAsMarkdown = normalizeLegacyBooleanMenuItem(
227
+ config?.copyAsMarkdown,
228
+ 'selectionMenuConfig',
229
+ 'copyAsMarkdown',
230
+ true,
231
+ DEFAULT_COPY_AS_MARKDOWN_LABEL
232
+ );
233
+ const copyImageUrl = normalizeLegacyBooleanMenuItem(
234
+ config?.copyImageUrl,
235
+ 'selectionMenuConfig',
236
+ 'copyImageUrl',
237
+ true,
238
+ DEFAULT_COPY_IMAGE_URL_LABEL
239
+ );
240
+
241
+ const pluralLabels = (
242
+ config?.copyImageUrl as
243
+ | { pluralLabels?: SelectionMenuPluralLabels }
244
+ | undefined
245
+ )?.pluralLabels;
246
+
247
+ return {
248
+ copyAsMarkdown: copyAsMarkdown.enabled,
249
+ copyImageUrl: copyImageUrl.enabled,
250
+ copyLabel: copy.label,
251
+ copyAsMarkdownLabel: copyAsMarkdown.label,
252
+ copyImageUrlLabel: copyImageUrl.label,
253
+ copyImageUrlsLabel: pluralLabels?.other ?? DEFAULT_COPY_IMAGE_URLS_LABEL,
254
+ copyImageUrlPluralTemplates: buildPluralTemplates(
255
+ pluralLabels,
256
+ copyImageUrl.label
257
+ ),
258
+ };
259
+ }, [selectionMenuConfig]);
260
+
261
+ const resolvedAccessibilityLabels = useMemo(
262
+ () => resolveAccessibilityLabels(accessibilityLabels),
263
+ [accessibilityLabels]
151
264
  );
152
265
 
153
266
  const sharedProps = {
@@ -168,6 +281,7 @@ export const EnrichedMarkdownText = ({
168
281
  style: containerStyle,
169
282
  contextMenuItems: nativeContextMenuItems,
170
283
  selectionMenuConfig: normalizedSelectionMenuConfig,
284
+ accessibilityLabels: resolvedAccessibilityLabels,
171
285
  onContextMenuItemPress: handleContextMenuItemPress,
172
286
  selectionColor,
173
287
  selectionHandleColor,
@@ -193,8 +193,13 @@ const DEFAULT_NORMALIZED_STYLE = Object.freeze({
193
193
  marginTop: 0,
194
194
  marginBottom: 16,
195
195
  textAlign: 'center' as BlockTextAlign,
196
+ fontWeight: 'normal',
197
+ },
198
+ inlineMath: {
199
+ color: defaultTextColor,
200
+ paddingVertical: 4,
201
+ fontWeight: 'normal',
196
202
  },
197
- inlineMath: { color: defaultTextColor },
198
203
  taskList: {
199
204
  checkedColor: Platform.select({
200
205
  ios: normalizeColor('#007AFF')!,
@@ -177,8 +177,13 @@ const DEFAULT_NORMALIZED_STYLE: MarkdownStyleInternal = Object.freeze({
177
177
  marginTop: 0,
178
178
  marginBottom: 16,
179
179
  textAlign: 'center' as BlockTextAlign,
180
+ fontWeight: 'normal',
181
+ },
182
+ inlineMath: {
183
+ color: defaultTextColor,
184
+ paddingVertical: 4,
185
+ fontWeight: 'normal',
180
186
  },
181
- inlineMath: { color: defaultTextColor },
182
187
  taskList: {
183
188
  checkedColor: '#007AFF',
184
189
  borderColor: '#9E9E9E',