react-native-enriched-markdown-scaffold 1.0.2-scaffold.1 → 1.0.2-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 (163) hide show
  1. package/android/build.gradle +2 -0
  2. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerDelegate.java +18 -0
  3. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerInterface.java +4 -0
  4. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +18 -0
  5. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +4 -0
  6. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +148 -0
  7. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +120 -0
  8. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +30 -0
  9. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +210 -0
  10. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +176 -2
  11. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +45 -0
  12. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +35 -0
  13. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +112 -0
  14. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +34 -0
  15. package/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt +46 -8
  16. package/android/src/main/java/com/swmansion/enriched/markdown/events/CopyRequestEvent.kt +28 -0
  17. package/android/src/main/java/com/swmansion/enriched/markdown/events/LinkLayoutEvent.kt +36 -0
  18. package/android/src/main/java/com/swmansion/enriched/markdown/events/SelectionChangeEvent.kt +43 -0
  19. package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputView.kt +25 -3
  20. package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/InputConnectionWrapper.kt +7 -0
  21. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +37 -7
  22. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +5 -1
  23. package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBackgroundSpan.kt +13 -99
  24. package/android/src/main/java/com/swmansion/enriched/markdown/spans/InlineChipPainter.kt +140 -0
  25. package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkChipSpans.kt +177 -0
  26. package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkSpan.kt +1 -6
  27. package/android/src/main/java/com/swmansion/enriched/markdown/styles/InlineMathStyle.kt +16 -1
  28. package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkStyle.kt +36 -1
  29. package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkVariantEntry.kt +7 -3
  30. package/android/src/main/java/com/swmansion/enriched/markdown/styles/MathStyle.kt +3 -0
  31. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +51 -0
  32. package/android/src/main/java/com/swmansion/enriched/markdown/utils/input/PasteTextUtils.kt +34 -2
  33. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/conversion/HTMLGenerator.kt +2 -1
  34. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/conversion/MarkdownExtractor.kt +6 -0
  35. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/HeadlessActionMode.kt +88 -0
  36. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkLayoutCollector.kt +100 -0
  37. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt +53 -14
  38. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionChangeReporter.kt +232 -0
  39. package/android/src/main/java/com/swmansion/enriched/markdown/views/BlockMenuOptions.kt +64 -0
  40. package/android/src/main/java/com/swmansion/enriched/markdown/views/CodeBlockContainerView.kt +29 -3
  41. package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +30 -7
  42. package/android/src/math/java/com/swmansion/enriched/markdown/renderer/MathInlineRenderer.kt +1 -0
  43. package/android/src/math/java/com/swmansion/enriched/markdown/spans/MathInlineSpan.kt +13 -6
  44. package/android/src/math/java/com/swmansion/enriched/markdown/spans/MathMeasureHelper.kt +2 -1
  45. package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +25 -9
  46. package/docs/API_REFERENCE.md +183 -4
  47. package/docs/COPY_OPTIONS.md +40 -0
  48. package/docs/INPUT.md +3 -1
  49. package/docs/MENTIONS.md +8 -1
  50. package/docs/STYLES.md +27 -0
  51. package/docs/TEXT.md +2 -0
  52. package/docs/WEB.md +1 -0
  53. package/ios/EnrichedMarkdown.mm +321 -9
  54. package/ios/EnrichedMarkdownText.mm +203 -6
  55. package/ios/attachments/ENRMMathInlineAttachment+macOS.m +2 -1
  56. package/ios/attachments/ENRMMathInlineAttachment.h +3 -0
  57. package/ios/attachments/ENRMMathInlineAttachment.m +2 -1
  58. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.cpp +148 -0
  59. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.h +120 -0
  60. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp +30 -0
  61. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +210 -0
  62. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +50 -2
  63. package/ios/internals/MeasurementCache.h +8 -2
  64. package/ios/math/ENRMRaTeXBridge.swift +23 -5
  65. package/ios/renderer/ENRMMathInlineRenderer.m +1 -0
  66. package/ios/renderer/LinkRenderer.m +47 -15
  67. package/ios/styles/StyleConfig.h +20 -0
  68. package/ios/styles/StyleConfig.mm +84 -0
  69. package/ios/utils/CodeBackground.m +3 -65
  70. package/ios/utils/ENRMBlockMenu.h +66 -0
  71. package/ios/utils/ENRMChipDrawing.h +24 -0
  72. package/ios/utils/ENRMChipDrawing.m +83 -0
  73. package/ios/utils/ENRMLinkChip.h +70 -0
  74. package/ios/utils/ENRMLinkChip.m +213 -0
  75. package/ios/utils/ENRMLinkLayout.h +50 -0
  76. package/ios/utils/ENRMLinkLayout.m +45 -0
  77. package/ios/utils/ENRMSelectionChange.h +120 -0
  78. package/ios/utils/ENRMSelectionChange.m +282 -0
  79. package/ios/utils/EditMenuUtils+macOS.m +4 -1
  80. package/ios/utils/EditMenuUtils.h +35 -6
  81. package/ios/utils/EditMenuUtils.m +171 -15
  82. package/ios/utils/HTMLGenerator.m +2 -1
  83. package/ios/utils/MarkdownAccessibilityElementBuilder.m +3 -1
  84. package/ios/utils/MarkdownExtractor.m +2 -1
  85. package/ios/utils/RTFExportUtils.m +2 -0
  86. package/ios/utils/StylePropsUtils.h +47 -1
  87. package/ios/utils/TextViewLayoutManager.mm +3 -0
  88. package/ios/views/ENRMCodeBlockContainerView.h +16 -2
  89. package/ios/views/ENRMCodeBlockContainerView.m +22 -19
  90. package/ios/views/ENRMMathContainerView.h +11 -0
  91. package/ios/views/ENRMMathContainerView.m +17 -20
  92. package/ios/views/TableContainerView.h +14 -0
  93. package/ios/views/TableContainerView.m +34 -18
  94. package/lib/module/EnrichedMarkdownNativeComponent.ts +138 -1
  95. package/lib/module/EnrichedMarkdownTextNativeComponent.ts +138 -1
  96. package/lib/module/index.js.map +1 -1
  97. package/lib/module/jest/index.js +14 -0
  98. package/lib/module/jest/index.js.map +1 -1
  99. package/lib/module/native/EnrichedMarkdownText.js +169 -11
  100. package/lib/module/native/EnrichedMarkdownText.js.map +1 -1
  101. package/lib/module/normalizeMarkdownStyle.js +17 -4
  102. package/lib/module/normalizeMarkdownStyle.js.map +1 -1
  103. package/lib/module/normalizeMarkdownStyle.web.js +17 -4
  104. package/lib/module/normalizeMarkdownStyle.web.js.map +1 -1
  105. package/lib/module/web/EnrichedMarkdownText.js +6 -3
  106. package/lib/module/web/EnrichedMarkdownText.js.map +1 -1
  107. package/lib/module/web/renderers/InlineRenderers.js +96 -3
  108. package/lib/module/web/renderers/InlineRenderers.js.map +1 -1
  109. package/lib/module/web/styles.js +18 -0
  110. package/lib/module/web/styles.js.map +1 -1
  111. package/lib/module/web/utils.js +13 -0
  112. package/lib/module/web/utils.js.map +1 -1
  113. package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +123 -2
  114. package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
  115. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +123 -2
  116. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
  117. package/lib/typescript/src/index.d.ts +2 -2
  118. package/lib/typescript/src/index.d.ts.map +1 -1
  119. package/lib/typescript/src/index.web.d.ts +1 -1
  120. package/lib/typescript/src/index.web.d.ts.map +1 -1
  121. package/lib/typescript/src/jest/index.d.ts +1 -1
  122. package/lib/typescript/src/jest/index.d.ts.map +1 -1
  123. package/lib/typescript/src/native/EnrichedMarkdownText.d.ts +5 -5
  124. package/lib/typescript/src/native/EnrichedMarkdownText.d.ts.map +1 -1
  125. package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -1
  126. package/lib/typescript/src/normalizeMarkdownStyle.web.d.ts.map +1 -1
  127. package/lib/typescript/src/types/MarkdownStyle.d.ts +50 -5
  128. package/lib/typescript/src/types/MarkdownStyle.d.ts.map +1 -1
  129. package/lib/typescript/src/types/MarkdownStyleInternal.d.ts +12 -0
  130. package/lib/typescript/src/types/MarkdownStyleInternal.d.ts.map +1 -1
  131. package/lib/typescript/src/types/MarkdownTextProps.d.ts +144 -11
  132. package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
  133. package/lib/typescript/src/types/MarkdownTextProps.web.d.ts +41 -1
  134. package/lib/typescript/src/types/MarkdownTextProps.web.d.ts.map +1 -1
  135. package/lib/typescript/src/types/events.d.ts +66 -0
  136. package/lib/typescript/src/types/events.d.ts.map +1 -1
  137. package/lib/typescript/src/web/EnrichedMarkdownText.d.ts +1 -1
  138. package/lib/typescript/src/web/EnrichedMarkdownText.d.ts.map +1 -1
  139. package/lib/typescript/src/web/renderers/InlineRenderers.d.ts.map +1 -1
  140. package/lib/typescript/src/web/styles.d.ts.map +1 -1
  141. package/lib/typescript/src/web/types.d.ts +4 -0
  142. package/lib/typescript/src/web/types.d.ts.map +1 -1
  143. package/lib/typescript/src/web/utils.d.ts +4 -0
  144. package/lib/typescript/src/web/utils.d.ts.map +1 -1
  145. package/package.json +1 -1
  146. package/src/EnrichedMarkdownNativeComponent.ts +138 -1
  147. package/src/EnrichedMarkdownTextNativeComponent.ts +138 -1
  148. package/src/index.tsx +6 -0
  149. package/src/index.web.tsx +5 -1
  150. package/src/jest/index.tsx +22 -1
  151. package/src/native/EnrichedMarkdownText.tsx +220 -9
  152. package/src/normalizeMarkdownStyle.ts +15 -0
  153. package/src/normalizeMarkdownStyle.web.ts +13 -0
  154. package/src/types/MarkdownStyle.ts +51 -5
  155. package/src/types/MarkdownStyleInternal.ts +12 -0
  156. package/src/types/MarkdownTextProps.ts +151 -11
  157. package/src/types/MarkdownTextProps.web.ts +43 -1
  158. package/src/types/events.ts +69 -0
  159. package/src/web/EnrichedMarkdownText.tsx +10 -3
  160. package/src/web/renderers/InlineRenderers.tsx +111 -3
  161. package/src/web/styles.ts +25 -0
  162. package/src/web/types.ts +4 -0
  163. package/src/web/utils.ts +8 -0
@@ -1,43 +1,75 @@
1
- import { useMemo, useCallback, useRef, useEffect } from 'react';
2
- import EnrichedMarkdownTextNativeComponent from '../EnrichedMarkdownTextNativeComponent';
3
- import type { MarkdownStyleInternal } from '../EnrichedMarkdownTextNativeComponent';
4
- import EnrichedMarkdownNativeComponent from '../EnrichedMarkdownNativeComponent';
1
+ import {
2
+ useMemo,
3
+ useCallback,
4
+ useRef,
5
+ useEffect,
6
+ useState,
7
+ useImperativeHandle,
8
+ } from 'react';
9
+ import EnrichedMarkdownTextNativeComponent, {
10
+ Commands as TextCommands,
11
+ } from '../EnrichedMarkdownTextNativeComponent';
12
+ import type {
13
+ LinkLayoutEvent,
14
+ MarkdownStyleInternal,
15
+ SelectionChangeEvent as NativeSelectionChangeEvent,
16
+ } from '../EnrichedMarkdownTextNativeComponent';
17
+ import EnrichedMarkdownNativeComponent, {
18
+ Commands as MarkdownCommands,
19
+ } from '../EnrichedMarkdownNativeComponent';
5
20
  import { normalizeMarkdownStyle } from '../normalizeMarkdownStyle';
6
21
  import { resolveAccessibilityLabels } from '../accessibilityLabelDefaults';
7
22
  import {
8
23
  normalizeMenuItem,
9
24
  normalizeLegacyBooleanMenuItem,
10
25
  } from '../normalizeMenuItem';
11
- import type { NativeSyntheticEvent } from 'react-native';
26
+ import { StyleSheet, View } from 'react-native';
27
+ import type {
28
+ HostInstance,
29
+ LayoutChangeEvent,
30
+ NativeSyntheticEvent,
31
+ } from 'react-native';
12
32
  import type { MarkdownStyle, Md4cFlags } from '../types/MarkdownStyle';
13
33
  import type {
14
34
  EnrichedMarkdownTextProps,
35
+ EnrichedMarkdownTextInstance,
15
36
  StreamingConfig,
16
37
  ContextMenuItem,
17
38
  SelectionMenuConfig,
18
39
  SelectionMenuPluralLabels,
40
+ LinkDecorationInfo,
41
+ LinkRect,
19
42
  } from '../types/MarkdownTextProps';
20
43
  import type {
21
44
  LinkPressEvent,
22
45
  LinkLongPressEvent,
23
46
  TaskListItemPressEvent,
24
47
  CopyPressEvent,
48
+ CopyEvent,
25
49
  OnContextMenuItemPressEvent,
50
+ SelectionChangeEvent,
51
+ SelectionRect,
26
52
  } from '../types/events';
27
53
 
28
54
  export type { MarkdownStyle, Md4cFlags };
29
55
  export type {
30
56
  EnrichedMarkdownTextProps,
57
+ EnrichedMarkdownTextInstance,
31
58
  StreamingConfig,
32
59
  ContextMenuItem,
33
60
  SelectionMenuConfig,
34
61
  SelectionMenuPluralLabels,
62
+ LinkDecorationInfo,
63
+ LinkRect,
35
64
  };
36
65
  export type {
37
66
  LinkPressEvent,
38
67
  LinkLongPressEvent,
39
68
  TaskListItemPressEvent,
40
69
  CopyPressEvent,
70
+ CopyEvent,
71
+ SelectionChangeEvent,
72
+ SelectionRect,
41
73
  };
42
74
 
43
75
  // Default English labels for the built-in selection menu actions. Defaults are
@@ -101,6 +133,34 @@ const buildPluralTemplates = (
101
133
  return templates;
102
134
  };
103
135
 
136
+ type LinkLayoutFragment = LinkLayoutEvent['fragments'][number];
137
+
138
+ /** Groups the flat per-line fragments native reports into one entry per link. */
139
+ const groupLinkFragments = (
140
+ fragments: ReadonlyArray<LinkLayoutFragment>
141
+ ): LinkDecorationInfo[] => {
142
+ const byIndex = new Map<number, LinkDecorationInfo>();
143
+ for (const { linkIndex, url, text, x, y, width, height } of fragments) {
144
+ const rect: LinkRect = { x, y, width, height };
145
+ const info = byIndex.get(linkIndex);
146
+ if (info) {
147
+ info.rects.push(rect);
148
+ info.rect = rect;
149
+ } else {
150
+ byIndex.set(linkIndex, {
151
+ index: linkIndex,
152
+ url,
153
+ text,
154
+ rect,
155
+ rects: [rect],
156
+ });
157
+ }
158
+ }
159
+ return [...byIndex.values()];
160
+ };
161
+
162
+ const ZERO_OFFSET = { x: 0, y: 0 };
163
+
104
164
  const defaultMd4cFlags: Md4cFlags = {
105
165
  underline: false,
106
166
  superscript: false,
@@ -111,6 +171,7 @@ const defaultMd4cFlags: Md4cFlags = {
111
171
  };
112
172
 
113
173
  export const EnrichedMarkdownText = ({
174
+ ref,
114
175
  markdown,
115
176
  markdownStyle = {},
116
177
  containerStyle,
@@ -119,7 +180,10 @@ export const EnrichedMarkdownText = ({
119
180
  onTaskListItemPress,
120
181
  enableTaskListItemToggle = true,
121
182
  onCopyPress,
183
+ onCopy,
184
+ onSelectionChange,
122
185
  enableLinkPreview,
186
+ linkLongPressSelects = false,
123
187
  selectable = true,
124
188
  md4cFlags = defaultMd4cFlags,
125
189
  allowFontScaling = true,
@@ -139,6 +203,8 @@ export const EnrichedMarkdownText = ({
139
203
  textBreakStrategy,
140
204
  lineBreakStrategyIOS,
141
205
  writingDirection = 'first-strong',
206
+ renderLinkDecoration,
207
+ onLayout,
142
208
  ...rest
143
209
  }: EnrichedMarkdownTextProps) => {
144
210
  const normalizedStyleRef = useRef<MarkdownStyleInternal | null>(null);
@@ -239,6 +305,89 @@ export const EnrichedMarkdownText = ({
239
305
  [onCopyPress]
240
306
  );
241
307
 
308
+ const handleCopyRequest = useCallback(
309
+ // Codegen types `source` as a plain string; native only sends the four
310
+ // values of CopyEvent['source'].
311
+ (
312
+ e: NativeSyntheticEvent<Omit<CopyEvent, 'source'> & { source: string }>
313
+ ) => {
314
+ const { source, markdown: copied, text, whole } = e.nativeEvent;
315
+ onCopy?.({
316
+ source: source as CopyEvent['source'],
317
+ markdown: copied,
318
+ text,
319
+ whole,
320
+ });
321
+ },
322
+ [onCopy]
323
+ );
324
+ const interceptCopy = onCopy != null;
325
+
326
+ const decorated = renderLinkDecoration != null;
327
+ const [linkLayout, setLinkLayout] = useState<LinkDecorationInfo[]>([]);
328
+ // Where the native view sits inside the wrapper (the wrapper's padding).
329
+ const [nativeOffset, setNativeOffset] = useState(ZERO_OFFSET);
330
+
331
+ const handleLinkLayout = useCallback(
332
+ (e: NativeSyntheticEvent<LinkLayoutEvent>) => {
333
+ setLinkLayout(groupLinkFragments(e.nativeEvent.fragments));
334
+ },
335
+ []
336
+ );
337
+
338
+ const handleNativeLayout = useCallback((e: LayoutChangeEvent) => {
339
+ const { x, y } = e.nativeEvent.layout;
340
+ setNativeOffset((prev) => (prev.x === x && prev.y === y ? prev : { x, y }));
341
+ }, []);
342
+
343
+ // Rects stay relative to the native view — the view the ref's
344
+ // `measureInWindow` measures — even when decorations wrap it.
345
+ const handleSelectionChange = useCallback(
346
+ // Codegen types `source` as a plain string; native only sends the four
347
+ // values of SelectionChangeEvent['source'].
348
+ (e: NativeSyntheticEvent<NativeSelectionChangeEvent>) => {
349
+ const { source, ...payload } = e.nativeEvent;
350
+ onSelectionChange?.({
351
+ ...payload,
352
+ source: source as SelectionChangeEvent['source'],
353
+ });
354
+ },
355
+ [onSelectionChange]
356
+ );
357
+
358
+ const nativeRef = useRef<HostInstance | null>(null);
359
+ const isGithubFlavor = flavor === 'github';
360
+ useImperativeHandle(
361
+ ref,
362
+ () => ({
363
+ clearSelection: () => {
364
+ const node = nativeRef.current;
365
+ if (node == null) return;
366
+ // Codegen's ViewRef resolves to `never` with RN 0.84's function-based
367
+ // HostComponent type — the cast is safe at runtime.
368
+ if (isGithubFlavor) {
369
+ MarkdownCommands.clearSelection(
370
+ node as Parameters<(typeof MarkdownCommands)['clearSelection']>[0]
371
+ );
372
+ } else {
373
+ TextCommands.clearSelection(
374
+ node as Parameters<(typeof TextCommands)['clearSelection']>[0]
375
+ );
376
+ }
377
+ },
378
+ measure: (callback) => nativeRef.current?.measure(callback),
379
+ measureInWindow: (callback) =>
380
+ nativeRef.current?.measureInWindow(callback),
381
+ measureLayout: (relativeToNativeNode, onSuccess, onFail) =>
382
+ nativeRef.current?.measureLayout(
383
+ relativeToNativeNode,
384
+ onSuccess,
385
+ onFail
386
+ ),
387
+ }),
388
+ [isGithubFlavor]
389
+ );
390
+
242
391
  const tableMode = streamingConfig?.tableMode ?? 'progressive';
243
392
  const codeBlockMode = streamingConfig?.codeBlockMode ?? 'progressive';
244
393
  const normalizedStreamingConfig = useMemo(
@@ -253,10 +402,19 @@ export const EnrichedMarkdownText = ({
253
402
  copy?: unknown;
254
403
  copyAsMarkdown?: unknown;
255
404
  copyImageUrl?: unknown;
405
+ systemItems?: unknown;
406
+ codeBlockCopyButton?: unknown;
256
407
  }
257
408
  | undefined;
258
409
 
259
410
  const copy = normalizeMenuItem(config?.copy, true, DEFAULT_COPY_LABEL);
411
+ const copyFormat = (config?.copy as { format?: unknown } | undefined)
412
+ ?.format;
413
+ const codeBlockCopyButton = normalizeMenuItem(
414
+ config?.codeBlockCopyButton,
415
+ true,
416
+ ''
417
+ );
260
418
  const copyAsMarkdown = normalizeLegacyBooleanMenuItem(
261
419
  config?.copyAsMarkdown,
262
420
  'selectionMenuConfig',
@@ -289,8 +447,13 @@ export const EnrichedMarkdownText = ({
289
447
  pluralLabels,
290
448
  copyImageUrl.label
291
449
  ),
450
+ copyEnabled: copy.enabled,
451
+ copyFormatMarkdown: copyFormat === 'markdown',
452
+ systemItemsNone: config?.systemItems === 'none',
453
+ codeBlockCopyButton: codeBlockCopyButton.enabled,
454
+ interceptCopy,
292
455
  };
293
- }, [selectionMenuConfig]);
456
+ }, [selectionMenuConfig, interceptCopy]);
294
457
 
295
458
  const resolvedAccessibilityLabels = useMemo(
296
459
  () => resolveAccessibilityLabels(accessibilityLabels),
@@ -305,7 +468,11 @@ export const EnrichedMarkdownText = ({
305
468
  onTaskListItemPress: handleTaskListItemPress,
306
469
  enableTaskListItemToggle,
307
470
  onCopyPress: handleCopyPress,
471
+ onCopyRequest: handleCopyRequest,
472
+ emitSelectionChange: onSelectionChange != null,
473
+ onSelectionChange: handleSelectionChange,
308
474
  enableLinkPreview: onLinkLongPress == null && (enableLinkPreview ?? true),
475
+ linkLongPressSelects,
309
476
  selectable,
310
477
  md4cFlags: normalizedMd4cFlags,
311
478
  allowFontScaling,
@@ -329,11 +496,55 @@ export const EnrichedMarkdownText = ({
329
496
  ...rest,
330
497
  };
331
498
 
332
- if (flavor === 'github') {
333
- return <EnrichedMarkdownNativeComponent {...sharedProps} />;
499
+ const NativeComponent = isGithubFlavor
500
+ ? EnrichedMarkdownNativeComponent
501
+ : EnrichedMarkdownTextNativeComponent;
502
+
503
+ // Without decorations the native view is returned as-is (no wrapper, no
504
+ // layout events) so existing layouts are untouched.
505
+ if (!decorated) {
506
+ return (
507
+ <NativeComponent {...sharedProps} ref={nativeRef} onLayout={onLayout} />
508
+ );
334
509
  }
335
510
 
336
- return <EnrichedMarkdownTextNativeComponent {...sharedProps} />;
511
+ return (
512
+ <View style={containerStyle} onLayout={onLayout}>
513
+ <NativeComponent
514
+ {...sharedProps}
515
+ ref={nativeRef}
516
+ style={undefined}
517
+ emitLinkLayout
518
+ onLinkLayout={handleLinkLayout}
519
+ onLayout={handleNativeLayout}
520
+ />
521
+ {linkLayout.map((info) => {
522
+ const decoration = renderLinkDecoration(info);
523
+ if (decoration == null) return null;
524
+ return (
525
+ <View
526
+ key={info.index}
527
+ pointerEvents="box-none"
528
+ style={[
529
+ styles.decoration,
530
+ {
531
+ left: nativeOffset.x + info.rect.x,
532
+ top: nativeOffset.y + info.rect.y,
533
+ width: info.rect.width,
534
+ height: info.rect.height,
535
+ },
536
+ ]}
537
+ >
538
+ {decoration}
539
+ </View>
540
+ );
541
+ })}
542
+ </View>
543
+ );
337
544
  };
338
545
 
546
+ const styles = StyleSheet.create({
547
+ decoration: { position: 'absolute' },
548
+ });
549
+
339
550
  export default EnrichedMarkdownText;
@@ -183,6 +183,11 @@ const DEFAULT_NORMALIZED_STYLE = Object.freeze({
183
183
  color: normalizeColor('#2563EB')!,
184
184
  underline: true,
185
185
  backgroundColor: normalizeColor('transparent')!,
186
+ borderRadius: 0,
187
+ paddingHorizontal: 0,
188
+ paddingVertical: 0,
189
+ borderColor: normalizeColor('transparent')!,
190
+ borderWidth: 0,
186
191
  },
187
192
  linkVariants: [],
188
193
  strong: { fontFamily: '', fontWeight: 'bold', color: undefined },
@@ -248,11 +253,13 @@ const DEFAULT_NORMALIZED_STYLE = Object.freeze({
248
253
  marginBottom: 16,
249
254
  textAlign: 'center' as BlockTextAlign,
250
255
  fontWeight: 'normal',
256
+ glyphOverflow: 0,
251
257
  },
252
258
  inlineMath: {
253
259
  color: defaultTextColor,
254
260
  paddingVertical: 4,
255
261
  fontWeight: 'normal',
262
+ glyphOverflow: 0,
256
263
  },
257
264
  taskList: {
258
265
  checkedColor: Platform.select({
@@ -338,9 +345,17 @@ export const normalizeMarkdownStyle = (
338
345
  color: ((override.color ? normalizeColor(override.color) : null) ??
339
346
  linkBase.color) as string,
340
347
  underline: override.underline ?? linkBase.underline,
348
+ // Chip ("box") properties never inherit from the base link style.
341
349
  backgroundColor: (override.backgroundColor
342
350
  ? (normalizeColor(override.backgroundColor) ?? transparent)
343
351
  : transparent) as string,
352
+ borderRadius: override.borderRadius ?? 0,
353
+ paddingHorizontal: override.paddingHorizontal ?? 0,
354
+ paddingVertical: override.paddingVertical ?? 0,
355
+ borderColor: (override.borderColor
356
+ ? (normalizeColor(override.borderColor) ?? transparent)
357
+ : transparent) as string,
358
+ borderWidth: override.borderWidth ?? 0,
344
359
  };
345
360
  }
346
361
  );
@@ -147,6 +147,11 @@ const DEFAULT_NORMALIZED_STYLE: MarkdownStyleInternal = Object.freeze({
147
147
  color: '#2563EB',
148
148
  underline: true,
149
149
  backgroundColor: 'transparent',
150
+ borderRadius: 0,
151
+ paddingHorizontal: 0,
152
+ paddingVertical: 0,
153
+ borderColor: 'transparent',
154
+ borderWidth: 0,
150
155
  },
151
156
  linkVariants: [],
152
157
  strong: { fontFamily: '', fontWeight: 'bold', color: undefined },
@@ -210,11 +215,13 @@ const DEFAULT_NORMALIZED_STYLE: MarkdownStyleInternal = Object.freeze({
210
215
  marginBottom: 16,
211
216
  textAlign: 'center' as BlockTextAlign,
212
217
  fontWeight: 'normal',
218
+ glyphOverflow: 0,
213
219
  },
214
220
  inlineMath: {
215
221
  color: defaultTextColor,
216
222
  paddingVertical: 4,
217
223
  fontWeight: 'normal',
224
+ glyphOverflow: 0,
218
225
  },
219
226
  taskList: {
220
227
  checkedColor: '#007AFF',
@@ -289,7 +296,13 @@ export const normalizeMarkdownStyle = (
289
296
  pattern,
290
297
  color: override.color ?? linkBase.color,
291
298
  underline: override.underline ?? linkBase.underline,
299
+ // Chip ("box") properties never inherit from the base link style.
292
300
  backgroundColor: override.backgroundColor ?? 'transparent',
301
+ borderRadius: override.borderRadius ?? 0,
302
+ paddingHorizontal: override.paddingHorizontal ?? 0,
303
+ paddingVertical: override.paddingVertical ?? 0,
304
+ borderColor: override.borderColor ?? 'transparent',
305
+ borderWidth: override.borderWidth ?? 0,
293
306
  };
294
307
  }
295
308
  );
@@ -84,17 +84,39 @@ interface CodeBlockStyle extends BaseBlockStyle {
84
84
  syntaxColors?: CodeBlockSyntaxColors;
85
85
  }
86
86
 
87
- export interface LinkStyle {
87
+ /**
88
+ * Chip ("box") properties shared by `link` and `linkVariants`. Unlike `color`
89
+ * and `underline`, these never inherit from the base `link` style: a variant
90
+ * that omits them renders without a chip (`0` / `transparent`).
91
+ */
92
+ interface LinkChipStyle {
93
+ backgroundColor?: string;
94
+ /** Corner radius of the chip, in dp. */
95
+ borderRadius?: number;
96
+ /**
97
+ * Horizontal padding, in dp. Reserves real space in the line on both outer
98
+ * edges of the link (affects measurement and wrapping).
99
+ */
100
+ paddingHorizontal?: number;
101
+ /**
102
+ * Vertical padding, in dp. Only affects the drawn chip (it bleeds into the
103
+ * surrounding line spacing); the line box is unchanged.
104
+ */
105
+ paddingVertical?: number;
106
+ borderColor?: string;
107
+ /** Border width, in dp. Drawn inset, within the chip bounds. */
108
+ borderWidth?: number;
109
+ }
110
+
111
+ export interface LinkStyle extends LinkChipStyle {
88
112
  fontFamily?: string;
89
113
  color?: string;
90
114
  underline?: boolean;
91
- backgroundColor?: string;
92
115
  }
93
116
 
94
- export interface LinkVariantStyle {
117
+ export interface LinkVariantStyle extends LinkChipStyle {
95
118
  color?: string;
96
119
  underline?: boolean;
97
- backgroundColor?: string;
98
120
  }
99
121
 
100
122
  interface StrongStyle {
@@ -234,6 +256,17 @@ interface MathStyle {
234
256
  * @default 'normal'
235
257
  */
236
258
  fontWeight?: 'bold' | 'normal';
259
+ /**
260
+ * Headroom kept above and below the rendered formula, in points (dp on
261
+ * Android), inside the box the formula reports. The math engine sizes a
262
+ * formula by its TeX metric box, and digit and capital outlines — plus the
263
+ * synthetic-bold stroke — rise a little past it; without this headroom that
264
+ * ink is clipped at the top of the box. About `0.06 × fontSize` covers the
265
+ * KaTeX faces; add `0.02 × fontSize` when `fontWeight` is `'bold'`.
266
+ * Android rounds it up to whole pixels.
267
+ * @default 0
268
+ */
269
+ glyphOverflow?: number;
237
270
  }
238
271
 
239
272
  interface InlineMathStyle {
@@ -258,6 +291,17 @@ interface InlineMathStyle {
258
291
  * @default 'normal'
259
292
  */
260
293
  fontWeight?: 'bold' | 'normal';
294
+ /**
295
+ * Headroom kept above and below the rendered formula, in points (dp on
296
+ * Android), inside the box the formula reports. The math engine sizes a
297
+ * formula by its TeX metric box, and digit and capital outlines — plus the
298
+ * synthetic-bold stroke — rise a little past it; without this headroom that
299
+ * ink is clipped at the top of the box. About `0.06 × fontSize` covers the
300
+ * KaTeX faces; add `0.02 × fontSize` when `fontWeight` is `'bold'`.
301
+ * Android rounds it up to whole pixels.
302
+ * @default 0
303
+ */
304
+ glyphOverflow?: number;
261
305
  }
262
306
 
263
307
  interface SpoilerParticlesStyle {
@@ -350,7 +394,9 @@ export interface MarkdownStyle {
350
394
  * specific patterns take precedence.
351
395
  *
352
396
  * `color` and `underline` inherit from the base `link` style when omitted.
353
- * `backgroundColor` defaults to `transparent`.
397
+ * Chip properties (`backgroundColor`, `borderRadius`, `paddingHorizontal`,
398
+ * `paddingVertical`, `borderColor`, `borderWidth`) do not inherit: when
399
+ * omitted they default to `0` / `transparent`.
354
400
  * `fontFamily` always follows the base `link` style and cannot be overridden per-variant.
355
401
  *
356
402
  * @example
@@ -88,6 +88,11 @@ interface LinkStyleInternal {
88
88
  color: string;
89
89
  underline: boolean;
90
90
  backgroundColor: string;
91
+ borderRadius: number;
92
+ paddingHorizontal: number;
93
+ paddingVertical: number;
94
+ borderColor: string;
95
+ borderWidth: number;
91
96
  }
92
97
 
93
98
  export interface LinkVariantEntryInternal {
@@ -95,6 +100,11 @@ export interface LinkVariantEntryInternal {
95
100
  color: string;
96
101
  underline: boolean;
97
102
  backgroundColor: string;
103
+ borderRadius: number;
104
+ paddingHorizontal: number;
105
+ paddingVertical: number;
106
+ borderColor: string;
107
+ borderWidth: number;
98
108
  }
99
109
 
100
110
  interface StrongStyleInternal {
@@ -180,12 +190,14 @@ interface MathStyleInternal {
180
190
  marginBottom: number;
181
191
  textAlign: BlockTextAlign;
182
192
  fontWeight: string;
193
+ glyphOverflow: number;
183
194
  }
184
195
 
185
196
  interface InlineMathStyleInternal {
186
197
  color: string;
187
198
  paddingVertical: number;
188
199
  fontWeight: string;
200
+ glyphOverflow: number;
189
201
  }
190
202
 
191
203
  interface SpoilerParticlesStyleInternal {