react-native-enriched-markdown-scaffold 1.0.2-scaffold.3 → 1.0.2-scaffold.5
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/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerDelegate.java +15 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerInterface.java +3 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +15 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +3 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +122 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +100 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +20 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +124 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +145 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +19 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +22 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +85 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +22 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/events/LinkLayoutEvent.kt +36 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/events/SelectionChangeEvent.kt +42 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +37 -7
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +5 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBackgroundSpan.kt +13 -99
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/InlineChipPainter.kt +140 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkChipSpans.kt +177 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkSpan.kt +1 -6
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkStyle.kt +36 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkVariantEntry.kt +7 -3
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +28 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/conversion/HTMLGenerator.kt +2 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/conversion/MarkdownExtractor.kt +6 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkLayoutCollector.kt +100 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt +4 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionChangeReporter.kt +230 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/views/CodeBlockContainerView.kt +11 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +22 -1
- package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +8 -1
- package/docs/API_REFERENCE.md +111 -0
- package/docs/MENTIONS.md +8 -1
- package/docs/STYLES.md +27 -0
- package/docs/TEXT.md +2 -0
- package/ios/EnrichedMarkdown.mm +221 -1
- package/ios/EnrichedMarkdownText.mm +142 -1
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.cpp +122 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.h +100 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp +20 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +124 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +50 -2
- package/ios/internals/MeasurementCache.h +5 -0
- package/ios/renderer/LinkRenderer.m +47 -15
- package/ios/styles/StyleConfig.h +16 -0
- package/ios/styles/StyleConfig.mm +60 -0
- package/ios/utils/CodeBackground.m +3 -65
- package/ios/utils/ENRMChipDrawing.h +24 -0
- package/ios/utils/ENRMChipDrawing.m +83 -0
- package/ios/utils/ENRMLinkChip.h +70 -0
- package/ios/utils/ENRMLinkChip.m +194 -0
- package/ios/utils/ENRMLinkLayout.h +50 -0
- package/ios/utils/ENRMLinkLayout.m +45 -0
- package/ios/utils/ENRMSelectionChange.h +96 -0
- package/ios/utils/ENRMSelectionChange.m +204 -0
- package/ios/utils/EditMenuUtils+macOS.m +4 -1
- package/ios/utils/EditMenuUtils.m +4 -1
- package/ios/utils/HTMLGenerator.m +2 -1
- package/ios/utils/MarkdownAccessibilityElementBuilder.m +3 -1
- package/ios/utils/MarkdownExtractor.m +2 -1
- package/ios/utils/RTFExportUtils.m +2 -0
- package/ios/utils/StylePropsUtils.h +37 -1
- package/ios/utils/TextViewLayoutManager.mm +3 -0
- package/ios/views/ENRMCodeBlockContainerView.h +3 -0
- package/ios/views/ENRMCodeBlockContainerView.m +3 -0
- package/ios/views/ENRMMathContainerView.h +3 -0
- package/ios/views/ENRMMathContainerView.m +3 -0
- package/ios/views/TableContainerView.h +6 -0
- package/ios/views/TableContainerView.m +24 -0
- package/lib/module/EnrichedMarkdownNativeComponent.ts +101 -1
- package/lib/module/EnrichedMarkdownTextNativeComponent.ts +101 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/jest/index.js +13 -0
- package/lib/module/jest/index.js.map +1 -1
- package/lib/module/native/EnrichedMarkdownText.js +138 -9
- package/lib/module/native/EnrichedMarkdownText.js.map +1 -1
- package/lib/module/normalizeMarkdownStyle.js +13 -2
- package/lib/module/normalizeMarkdownStyle.js.map +1 -1
- package/lib/module/normalizeMarkdownStyle.web.js +13 -2
- package/lib/module/normalizeMarkdownStyle.web.js.map +1 -1
- package/lib/module/web/EnrichedMarkdownText.js +6 -3
- package/lib/module/web/EnrichedMarkdownText.js.map +1 -1
- package/lib/module/web/renderers/InlineRenderers.js +96 -3
- package/lib/module/web/renderers/InlineRenderers.js.map +1 -1
- package/lib/module/web/styles.js +18 -0
- package/lib/module/web/styles.js.map +1 -1
- package/lib/module/web/utils.js +13 -0
- package/lib/module/web/utils.js.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +93 -2
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +93 -2
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/index.web.d.ts +1 -1
- package/lib/typescript/src/index.web.d.ts.map +1 -1
- package/lib/typescript/src/jest/index.d.ts +1 -1
- package/lib/typescript/src/jest/index.d.ts.map +1 -1
- package/lib/typescript/src/native/EnrichedMarkdownText.d.ts +5 -5
- package/lib/typescript/src/native/EnrichedMarkdownText.d.ts.map +1 -1
- package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -1
- package/lib/typescript/src/normalizeMarkdownStyle.web.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownStyle.d.ts +28 -5
- package/lib/typescript/src/types/MarkdownStyle.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownStyleInternal.d.ts +10 -0
- package/lib/typescript/src/types/MarkdownStyleInternal.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownTextProps.d.ts +76 -2
- package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownTextProps.web.d.ts +41 -1
- package/lib/typescript/src/types/MarkdownTextProps.web.d.ts.map +1 -1
- package/lib/typescript/src/types/events.d.ts +36 -0
- package/lib/typescript/src/types/events.d.ts.map +1 -1
- package/lib/typescript/src/web/EnrichedMarkdownText.d.ts +1 -1
- package/lib/typescript/src/web/EnrichedMarkdownText.d.ts.map +1 -1
- package/lib/typescript/src/web/renderers/InlineRenderers.d.ts.map +1 -1
- package/lib/typescript/src/web/styles.d.ts.map +1 -1
- package/lib/typescript/src/web/types.d.ts +4 -0
- package/lib/typescript/src/web/types.d.ts.map +1 -1
- package/lib/typescript/src/web/utils.d.ts +4 -0
- package/lib/typescript/src/web/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/EnrichedMarkdownNativeComponent.ts +101 -1
- package/src/EnrichedMarkdownTextNativeComponent.ts +101 -1
- package/src/index.tsx +5 -0
- package/src/index.web.tsx +5 -1
- package/src/jest/index.tsx +21 -1
- package/src/native/EnrichedMarkdownText.tsx +181 -8
- package/src/normalizeMarkdownStyle.ts +13 -0
- package/src/normalizeMarkdownStyle.web.ts +11 -0
- package/src/types/MarkdownStyle.ts +29 -5
- package/src/types/MarkdownStyleInternal.ts +10 -0
- package/src/types/MarkdownTextProps.ts +85 -1
- package/src/types/MarkdownTextProps.web.ts +43 -1
- package/src/types/events.ts +38 -0
- package/src/web/EnrichedMarkdownText.tsx +10 -3
- package/src/web/renderers/InlineRenderers.tsx +111 -3
- package/src/web/styles.ts +25 -0
- package/src/web/types.ts +4 -0
- package/src/web/utils.ts +8 -0
package/src/index.web.tsx
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export { EnrichedMarkdownText, default } from './web/EnrichedMarkdownText';
|
|
2
|
-
export type {
|
|
2
|
+
export type {
|
|
3
|
+
EnrichedMarkdownTextProps,
|
|
4
|
+
LinkDecorationInfo,
|
|
5
|
+
LinkRect,
|
|
6
|
+
} from './types/MarkdownTextProps.web';
|
|
3
7
|
export type { MarkdownStyle, Md4cFlags } from './types/MarkdownStyle';
|
|
4
8
|
export type {
|
|
5
9
|
LinkPressEvent,
|
package/src/jest/index.tsx
CHANGED
|
@@ -26,7 +26,10 @@ import type {
|
|
|
26
26
|
EnrichedMarkdownTextInputProps,
|
|
27
27
|
InsertMathOptions,
|
|
28
28
|
} from '../EnrichedMarkdownTextInput';
|
|
29
|
-
import type {
|
|
29
|
+
import type {
|
|
30
|
+
EnrichedMarkdownTextInstance,
|
|
31
|
+
EnrichedMarkdownTextProps,
|
|
32
|
+
} from '../native/EnrichedMarkdownText';
|
|
30
33
|
|
|
31
34
|
export {
|
|
32
35
|
DEFAULT_ACCESSIBILITY_LABELS,
|
|
@@ -165,6 +168,7 @@ export const EnrichedMarkdownTextInput = ({
|
|
|
165
168
|
};
|
|
166
169
|
|
|
167
170
|
export const EnrichedMarkdownText = ({
|
|
171
|
+
ref,
|
|
168
172
|
markdown,
|
|
169
173
|
containerStyle,
|
|
170
174
|
testID,
|
|
@@ -178,6 +182,7 @@ export const EnrichedMarkdownText = ({
|
|
|
178
182
|
onLinkPress: _onLinkPress,
|
|
179
183
|
onLinkLongPress: _onLinkLongPress,
|
|
180
184
|
onTaskListItemPress: _onTaskListItemPress,
|
|
185
|
+
onSelectionChange: _onSelectionChange,
|
|
181
186
|
enableLinkPreview: _enableLinkPreview,
|
|
182
187
|
selectable: _selectable,
|
|
183
188
|
md4cFlags: _md4cFlags,
|
|
@@ -198,6 +203,21 @@ export const EnrichedMarkdownText = ({
|
|
|
198
203
|
lineBreakStrategyIOS: _lineBreakStrategyIOS,
|
|
199
204
|
writingDirection: _writingDirection,
|
|
200
205
|
}: EnrichedMarkdownTextProps) => {
|
|
206
|
+
const instanceRef = useRef<EnrichedMarkdownTextInstance | null>(null);
|
|
207
|
+
if (instanceRef.current === null) {
|
|
208
|
+
instanceRef.current = {
|
|
209
|
+
// No native selection under Jest; the mock records the call.
|
|
210
|
+
clearSelection: spy(() => {}),
|
|
211
|
+
measure: spy((callback) => callback(0, 0, 0, 0, 0, 0)),
|
|
212
|
+
measureInWindow: spy((callback) => callback(0, 0, 0, 0)),
|
|
213
|
+
measureLayout: spy((_relativeToNativeNode, onSuccess) =>
|
|
214
|
+
onSuccess(0, 0, 0, 0)
|
|
215
|
+
),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
useImperativeHandle(ref, () => instanceRef.current!);
|
|
220
|
+
|
|
201
221
|
return (
|
|
202
222
|
<Text
|
|
203
223
|
style={containerStyle}
|
|
@@ -1,21 +1,44 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
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,
|
|
@@ -24,15 +47,20 @@ import type {
|
|
|
24
47
|
CopyPressEvent,
|
|
25
48
|
CopyEvent,
|
|
26
49
|
OnContextMenuItemPressEvent,
|
|
50
|
+
SelectionChangeEvent,
|
|
51
|
+
SelectionRect,
|
|
27
52
|
} from '../types/events';
|
|
28
53
|
|
|
29
54
|
export type { MarkdownStyle, Md4cFlags };
|
|
30
55
|
export type {
|
|
31
56
|
EnrichedMarkdownTextProps,
|
|
57
|
+
EnrichedMarkdownTextInstance,
|
|
32
58
|
StreamingConfig,
|
|
33
59
|
ContextMenuItem,
|
|
34
60
|
SelectionMenuConfig,
|
|
35
61
|
SelectionMenuPluralLabels,
|
|
62
|
+
LinkDecorationInfo,
|
|
63
|
+
LinkRect,
|
|
36
64
|
};
|
|
37
65
|
export type {
|
|
38
66
|
LinkPressEvent,
|
|
@@ -40,6 +68,8 @@ export type {
|
|
|
40
68
|
TaskListItemPressEvent,
|
|
41
69
|
CopyPressEvent,
|
|
42
70
|
CopyEvent,
|
|
71
|
+
SelectionChangeEvent,
|
|
72
|
+
SelectionRect,
|
|
43
73
|
};
|
|
44
74
|
|
|
45
75
|
// Default English labels for the built-in selection menu actions. Defaults are
|
|
@@ -103,6 +133,34 @@ const buildPluralTemplates = (
|
|
|
103
133
|
return templates;
|
|
104
134
|
};
|
|
105
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
|
+
|
|
106
164
|
const defaultMd4cFlags: Md4cFlags = {
|
|
107
165
|
underline: false,
|
|
108
166
|
superscript: false,
|
|
@@ -113,6 +171,7 @@ const defaultMd4cFlags: Md4cFlags = {
|
|
|
113
171
|
};
|
|
114
172
|
|
|
115
173
|
export const EnrichedMarkdownText = ({
|
|
174
|
+
ref,
|
|
116
175
|
markdown,
|
|
117
176
|
markdownStyle = {},
|
|
118
177
|
containerStyle,
|
|
@@ -122,6 +181,7 @@ export const EnrichedMarkdownText = ({
|
|
|
122
181
|
enableTaskListItemToggle = true,
|
|
123
182
|
onCopyPress,
|
|
124
183
|
onCopy,
|
|
184
|
+
onSelectionChange,
|
|
125
185
|
enableLinkPreview,
|
|
126
186
|
selectable = true,
|
|
127
187
|
md4cFlags = defaultMd4cFlags,
|
|
@@ -142,6 +202,8 @@ export const EnrichedMarkdownText = ({
|
|
|
142
202
|
textBreakStrategy,
|
|
143
203
|
lineBreakStrategyIOS,
|
|
144
204
|
writingDirection = 'first-strong',
|
|
205
|
+
renderLinkDecoration,
|
|
206
|
+
onLayout,
|
|
145
207
|
...rest
|
|
146
208
|
}: EnrichedMarkdownTextProps) => {
|
|
147
209
|
const normalizedStyleRef = useRef<MarkdownStyleInternal | null>(null);
|
|
@@ -260,6 +322,71 @@ export const EnrichedMarkdownText = ({
|
|
|
260
322
|
);
|
|
261
323
|
const interceptCopy = onCopy != null;
|
|
262
324
|
|
|
325
|
+
const decorated = renderLinkDecoration != null;
|
|
326
|
+
const [linkLayout, setLinkLayout] = useState<LinkDecorationInfo[]>([]);
|
|
327
|
+
// Where the native view sits inside the wrapper (the wrapper's padding).
|
|
328
|
+
const [nativeOffset, setNativeOffset] = useState(ZERO_OFFSET);
|
|
329
|
+
|
|
330
|
+
const handleLinkLayout = useCallback(
|
|
331
|
+
(e: NativeSyntheticEvent<LinkLayoutEvent>) => {
|
|
332
|
+
setLinkLayout(groupLinkFragments(e.nativeEvent.fragments));
|
|
333
|
+
},
|
|
334
|
+
[]
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
const handleNativeLayout = useCallback((e: LayoutChangeEvent) => {
|
|
338
|
+
const { x, y } = e.nativeEvent.layout;
|
|
339
|
+
setNativeOffset((prev) => (prev.x === x && prev.y === y ? prev : { x, y }));
|
|
340
|
+
}, []);
|
|
341
|
+
|
|
342
|
+
// Rects stay relative to the native view — the view the ref's
|
|
343
|
+
// `measureInWindow` measures — even when decorations wrap it.
|
|
344
|
+
const handleSelectionChange = useCallback(
|
|
345
|
+
// Codegen types `source` as a plain string; native only sends the four
|
|
346
|
+
// values of SelectionChangeEvent['source'].
|
|
347
|
+
(e: NativeSyntheticEvent<NativeSelectionChangeEvent>) => {
|
|
348
|
+
const { source, ...payload } = e.nativeEvent;
|
|
349
|
+
onSelectionChange?.({
|
|
350
|
+
...payload,
|
|
351
|
+
source: source as SelectionChangeEvent['source'],
|
|
352
|
+
});
|
|
353
|
+
},
|
|
354
|
+
[onSelectionChange]
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
const nativeRef = useRef<HostInstance | null>(null);
|
|
358
|
+
const isGithubFlavor = flavor === 'github';
|
|
359
|
+
useImperativeHandle(
|
|
360
|
+
ref,
|
|
361
|
+
() => ({
|
|
362
|
+
clearSelection: () => {
|
|
363
|
+
const node = nativeRef.current;
|
|
364
|
+
if (node == null) return;
|
|
365
|
+
// Codegen's ViewRef resolves to `never` with RN 0.84's function-based
|
|
366
|
+
// HostComponent type — the cast is safe at runtime.
|
|
367
|
+
if (isGithubFlavor) {
|
|
368
|
+
MarkdownCommands.clearSelection(
|
|
369
|
+
node as Parameters<(typeof MarkdownCommands)['clearSelection']>[0]
|
|
370
|
+
);
|
|
371
|
+
} else {
|
|
372
|
+
TextCommands.clearSelection(
|
|
373
|
+
node as Parameters<(typeof TextCommands)['clearSelection']>[0]
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
},
|
|
377
|
+
measure: (callback) => nativeRef.current?.measure(callback),
|
|
378
|
+
measureInWindow: (callback) =>
|
|
379
|
+
nativeRef.current?.measureInWindow(callback),
|
|
380
|
+
measureLayout: (relativeToNativeNode, onSuccess, onFail) =>
|
|
381
|
+
nativeRef.current?.measureLayout(
|
|
382
|
+
relativeToNativeNode,
|
|
383
|
+
onSuccess,
|
|
384
|
+
onFail
|
|
385
|
+
),
|
|
386
|
+
}),
|
|
387
|
+
[isGithubFlavor]
|
|
388
|
+
);
|
|
389
|
+
|
|
263
390
|
const tableMode = streamingConfig?.tableMode ?? 'progressive';
|
|
264
391
|
const codeBlockMode = streamingConfig?.codeBlockMode ?? 'progressive';
|
|
265
392
|
const normalizedStreamingConfig = useMemo(
|
|
@@ -341,6 +468,8 @@ export const EnrichedMarkdownText = ({
|
|
|
341
468
|
enableTaskListItemToggle,
|
|
342
469
|
onCopyPress: handleCopyPress,
|
|
343
470
|
onCopyRequest: handleCopyRequest,
|
|
471
|
+
emitSelectionChange: onSelectionChange != null,
|
|
472
|
+
onSelectionChange: handleSelectionChange,
|
|
344
473
|
enableLinkPreview: onLinkLongPress == null && (enableLinkPreview ?? true),
|
|
345
474
|
selectable,
|
|
346
475
|
md4cFlags: normalizedMd4cFlags,
|
|
@@ -365,11 +494,55 @@ export const EnrichedMarkdownText = ({
|
|
|
365
494
|
...rest,
|
|
366
495
|
};
|
|
367
496
|
|
|
368
|
-
|
|
369
|
-
|
|
497
|
+
const NativeComponent = isGithubFlavor
|
|
498
|
+
? EnrichedMarkdownNativeComponent
|
|
499
|
+
: EnrichedMarkdownTextNativeComponent;
|
|
500
|
+
|
|
501
|
+
// Without decorations the native view is returned as-is (no wrapper, no
|
|
502
|
+
// layout events) so existing layouts are untouched.
|
|
503
|
+
if (!decorated) {
|
|
504
|
+
return (
|
|
505
|
+
<NativeComponent {...sharedProps} ref={nativeRef} onLayout={onLayout} />
|
|
506
|
+
);
|
|
370
507
|
}
|
|
371
508
|
|
|
372
|
-
return
|
|
509
|
+
return (
|
|
510
|
+
<View style={containerStyle} onLayout={onLayout}>
|
|
511
|
+
<NativeComponent
|
|
512
|
+
{...sharedProps}
|
|
513
|
+
ref={nativeRef}
|
|
514
|
+
style={undefined}
|
|
515
|
+
emitLinkLayout
|
|
516
|
+
onLinkLayout={handleLinkLayout}
|
|
517
|
+
onLayout={handleNativeLayout}
|
|
518
|
+
/>
|
|
519
|
+
{linkLayout.map((info) => {
|
|
520
|
+
const decoration = renderLinkDecoration(info);
|
|
521
|
+
if (decoration == null) return null;
|
|
522
|
+
return (
|
|
523
|
+
<View
|
|
524
|
+
key={info.index}
|
|
525
|
+
pointerEvents="box-none"
|
|
526
|
+
style={[
|
|
527
|
+
styles.decoration,
|
|
528
|
+
{
|
|
529
|
+
left: nativeOffset.x + info.rect.x,
|
|
530
|
+
top: nativeOffset.y + info.rect.y,
|
|
531
|
+
width: info.rect.width,
|
|
532
|
+
height: info.rect.height,
|
|
533
|
+
},
|
|
534
|
+
]}
|
|
535
|
+
>
|
|
536
|
+
{decoration}
|
|
537
|
+
</View>
|
|
538
|
+
);
|
|
539
|
+
})}
|
|
540
|
+
</View>
|
|
541
|
+
);
|
|
373
542
|
};
|
|
374
543
|
|
|
544
|
+
const styles = StyleSheet.create({
|
|
545
|
+
decoration: { position: 'absolute' },
|
|
546
|
+
});
|
|
547
|
+
|
|
375
548
|
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 },
|
|
@@ -338,9 +343,17 @@ export const normalizeMarkdownStyle = (
|
|
|
338
343
|
color: ((override.color ? normalizeColor(override.color) : null) ??
|
|
339
344
|
linkBase.color) as string,
|
|
340
345
|
underline: override.underline ?? linkBase.underline,
|
|
346
|
+
// Chip ("box") properties never inherit from the base link style.
|
|
341
347
|
backgroundColor: (override.backgroundColor
|
|
342
348
|
? (normalizeColor(override.backgroundColor) ?? transparent)
|
|
343
349
|
: transparent) as string,
|
|
350
|
+
borderRadius: override.borderRadius ?? 0,
|
|
351
|
+
paddingHorizontal: override.paddingHorizontal ?? 0,
|
|
352
|
+
paddingVertical: override.paddingVertical ?? 0,
|
|
353
|
+
borderColor: (override.borderColor
|
|
354
|
+
? (normalizeColor(override.borderColor) ?? transparent)
|
|
355
|
+
: transparent) as string,
|
|
356
|
+
borderWidth: override.borderWidth ?? 0,
|
|
344
357
|
};
|
|
345
358
|
}
|
|
346
359
|
);
|
|
@@ -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 },
|
|
@@ -289,7 +294,13 @@ export const normalizeMarkdownStyle = (
|
|
|
289
294
|
pattern,
|
|
290
295
|
color: override.color ?? linkBase.color,
|
|
291
296
|
underline: override.underline ?? linkBase.underline,
|
|
297
|
+
// Chip ("box") properties never inherit from the base link style.
|
|
292
298
|
backgroundColor: override.backgroundColor ?? 'transparent',
|
|
299
|
+
borderRadius: override.borderRadius ?? 0,
|
|
300
|
+
paddingHorizontal: override.paddingHorizontal ?? 0,
|
|
301
|
+
paddingVertical: override.paddingVertical ?? 0,
|
|
302
|
+
borderColor: override.borderColor ?? 'transparent',
|
|
303
|
+
borderWidth: override.borderWidth ?? 0,
|
|
293
304
|
};
|
|
294
305
|
}
|
|
295
306
|
);
|
|
@@ -84,17 +84,39 @@ interface CodeBlockStyle extends BaseBlockStyle {
|
|
|
84
84
|
syntaxColors?: CodeBlockSyntaxColors;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
|
|
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 {
|
|
@@ -350,7 +372,9 @@ export interface MarkdownStyle {
|
|
|
350
372
|
* specific patterns take precedence.
|
|
351
373
|
*
|
|
352
374
|
* `color` and `underline` inherit from the base `link` style when omitted.
|
|
353
|
-
* `backgroundColor`
|
|
375
|
+
* Chip properties (`backgroundColor`, `borderRadius`, `paddingHorizontal`,
|
|
376
|
+
* `paddingVertical`, `borderColor`, `borderWidth`) do not inherit: when
|
|
377
|
+
* omitted they default to `0` / `transparent`.
|
|
354
378
|
* `fontFamily` always follows the base `link` style and cannot be overridden per-variant.
|
|
355
379
|
*
|
|
356
380
|
* @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 {
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ReactNode, RefObject } from 'react';
|
|
2
|
+
import type {
|
|
3
|
+
ColorValue,
|
|
4
|
+
HostInstance,
|
|
5
|
+
ViewProps,
|
|
6
|
+
ViewStyle,
|
|
7
|
+
TextStyle,
|
|
8
|
+
} from 'react-native';
|
|
2
9
|
import type { MarkdownStyle, Md4cFlags } from './MarkdownStyle';
|
|
3
10
|
import type { AccessibilityLabels } from './AccessibilityLabels';
|
|
4
11
|
import type {
|
|
@@ -7,8 +14,49 @@ import type {
|
|
|
7
14
|
TaskListItemPressEvent,
|
|
8
15
|
CopyPressEvent,
|
|
9
16
|
CopyEvent,
|
|
17
|
+
SelectionChangeEvent,
|
|
10
18
|
} from './events';
|
|
11
19
|
|
|
20
|
+
/** Imperative handle of `EnrichedMarkdownText`, reached through its `ref`. */
|
|
21
|
+
export interface EnrichedMarkdownTextInstance {
|
|
22
|
+
/**
|
|
23
|
+
* Clears the text selection (and any long-pressed block) and fires
|
|
24
|
+
* `onSelectionChange` with `active: false`.
|
|
25
|
+
* @platform ios, android
|
|
26
|
+
*/
|
|
27
|
+
clearSelection: () => void;
|
|
28
|
+
measure: HostInstance['measure'];
|
|
29
|
+
measureInWindow: HostInstance['measureInWindow'];
|
|
30
|
+
measureLayout: HostInstance['measureLayout'];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A rect in dp, relative to the component's content box (inside any
|
|
35
|
+
* `containerStyle` padding).
|
|
36
|
+
*/
|
|
37
|
+
export interface LinkRect {
|
|
38
|
+
x: number;
|
|
39
|
+
y: number;
|
|
40
|
+
width: number;
|
|
41
|
+
height: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Layout of one rendered link, passed to `renderLinkDecoration`. */
|
|
45
|
+
export interface LinkDecorationInfo {
|
|
46
|
+
/** Position of the link in the document (0-based) — stable key for a link. */
|
|
47
|
+
index: number;
|
|
48
|
+
url: string;
|
|
49
|
+
/** The link's visible text. */
|
|
50
|
+
text: string;
|
|
51
|
+
/**
|
|
52
|
+
* Anchor rect. Native: the drawn chip (glyphs + padding) of the link's last
|
|
53
|
+
* line fragment. Web: the link's inline box.
|
|
54
|
+
*/
|
|
55
|
+
rect: LinkRect;
|
|
56
|
+
/** Every line fragment of the link (several when it wraps). Web: `[rect]`. */
|
|
57
|
+
rects: LinkRect[];
|
|
58
|
+
}
|
|
59
|
+
|
|
12
60
|
/**
|
|
13
61
|
* Public context menu item. Each item includes a JS-side `onPress` callback
|
|
14
62
|
* that is called when the user taps the item in the selection context menu.
|
|
@@ -141,6 +189,11 @@ export interface StreamingConfig {
|
|
|
141
189
|
}
|
|
142
190
|
|
|
143
191
|
export interface EnrichedMarkdownTextProps extends Omit<ViewProps, 'style'> {
|
|
192
|
+
/**
|
|
193
|
+
* Imperative handle: `clearSelection()` plus the host `measure*` methods.
|
|
194
|
+
* @platform ios, android
|
|
195
|
+
*/
|
|
196
|
+
ref?: RefObject<EnrichedMarkdownTextInstance | null>;
|
|
144
197
|
/**
|
|
145
198
|
* Markdown content to render.
|
|
146
199
|
* @platform ios, android, web
|
|
@@ -178,6 +231,21 @@ export interface EnrichedMarkdownTextProps extends Omit<ViewProps, 'style'> {
|
|
|
178
231
|
* @platform ios, android, web
|
|
179
232
|
*/
|
|
180
233
|
onLinkLongPress?: (event: LinkLongPressEvent) => void;
|
|
234
|
+
/**
|
|
235
|
+
* Renders a decoration (e.g. a badge or icon) over each link. The returned
|
|
236
|
+
* node is placed in an absolutely positioned box matching `info.rect` (the
|
|
237
|
+
* link's last line fragment, including chip padding) with
|
|
238
|
+
* `pointerEvents="box-none"`; position content relative to it (negative
|
|
239
|
+
* offsets overflow the box). Return `null` to leave a link undecorated.
|
|
240
|
+
*
|
|
241
|
+
* Native caveats: rects arrive from native after layout, so decorations
|
|
242
|
+
* appear one frame after the text; links inside tables (`flavor="github"`)
|
|
243
|
+
* are not reported; content overflowing the component bounds does not
|
|
244
|
+
* receive touches on Android. Toggling this prop between set and unset
|
|
245
|
+
* remounts the native view.
|
|
246
|
+
* @platform ios, android, web
|
|
247
|
+
*/
|
|
248
|
+
renderLinkDecoration?: (info: LinkDecorationInfo) => ReactNode | null;
|
|
181
249
|
/**
|
|
182
250
|
* Callback fired when a task list checkbox is tapped.
|
|
183
251
|
*
|
|
@@ -226,6 +294,22 @@ export interface EnrichedMarkdownTextProps extends Omit<ViewProps, 'style'> {
|
|
|
226
294
|
* @platform ios, android
|
|
227
295
|
*/
|
|
228
296
|
onCopy?: (event: CopyEvent) => void;
|
|
297
|
+
/**
|
|
298
|
+
* Reports the text selection so the app can draw its own menu (pair it with
|
|
299
|
+
* `contextMenuHidden`). Fires at most once per frame while the selection
|
|
300
|
+
* changes, and with `active: false` once it is gone. With
|
|
301
|
+
* `contextMenuHidden`, a long press on a table / math / code block fires it
|
|
302
|
+
* too (`source` is the block kind, `whole: true`), since those blocks have no
|
|
303
|
+
* text selection of their own. Only one part of a component holds a
|
|
304
|
+
* selection at a time; clearing the selection of *other* components is up to
|
|
305
|
+
* the app (`ref.current.clearSelection()`).
|
|
306
|
+
*
|
|
307
|
+
* Rects are relative to the view that `ref.current.measureInWindow`
|
|
308
|
+
* measures — add that origin to position UI on screen. There is no
|
|
309
|
+
* "dragging" flag: debounce before showing a menu.
|
|
310
|
+
* @platform ios, android
|
|
311
|
+
*/
|
|
312
|
+
onSelectionChange?: (event: SelectionChangeEvent) => void;
|
|
229
313
|
/**
|
|
230
314
|
* Controls whether the system link preview is shown on long press (iOS only).
|
|
231
315
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CSSProperties, HTMLAttributes } from 'react';
|
|
1
|
+
import type { CSSProperties, HTMLAttributes, ReactNode } from 'react';
|
|
2
2
|
import type { MarkdownStyle, Md4cFlags } from './MarkdownStyle';
|
|
3
3
|
import type {
|
|
4
4
|
LinkPressEvent,
|
|
@@ -6,6 +6,33 @@ import type {
|
|
|
6
6
|
TaskListItemPressEvent,
|
|
7
7
|
} from './events';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* A rect in dp, relative to the component's content box (inside any
|
|
11
|
+
* `containerStyle` padding).
|
|
12
|
+
*/
|
|
13
|
+
export interface LinkRect {
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Layout of one rendered link, passed to `renderLinkDecoration`. */
|
|
21
|
+
export interface LinkDecorationInfo {
|
|
22
|
+
/** Position of the link in the document (0-based) — stable key for a link. */
|
|
23
|
+
index: number;
|
|
24
|
+
url: string;
|
|
25
|
+
/** The link's visible text. */
|
|
26
|
+
text: string;
|
|
27
|
+
/**
|
|
28
|
+
* Anchor rect. Native: the drawn chip (glyphs + padding) of the link's last
|
|
29
|
+
* line fragment. Web: the link's inline box.
|
|
30
|
+
*/
|
|
31
|
+
rect: LinkRect;
|
|
32
|
+
/** Every line fragment of the link (several when it wraps). Web: `[rect]`. */
|
|
33
|
+
rects: LinkRect[];
|
|
34
|
+
}
|
|
35
|
+
|
|
9
36
|
export interface EnrichedMarkdownTextProps extends Omit<
|
|
10
37
|
HTMLAttributes<HTMLDivElement>,
|
|
11
38
|
'style' | 'dir'
|
|
@@ -47,6 +74,21 @@ export interface EnrichedMarkdownTextProps extends Omit<
|
|
|
47
74
|
* @platform ios, android, web
|
|
48
75
|
*/
|
|
49
76
|
onLinkLongPress?: (event: LinkLongPressEvent) => void;
|
|
77
|
+
/**
|
|
78
|
+
* Renders a decoration (e.g. a badge or icon) over each link. The returned
|
|
79
|
+
* node is placed in an absolutely positioned box matching `info.rect` (the
|
|
80
|
+
* link's last line fragment, including chip padding) with
|
|
81
|
+
* `pointerEvents="box-none"`; position content relative to it (negative
|
|
82
|
+
* offsets overflow the box). Return `null` to leave a link undecorated.
|
|
83
|
+
*
|
|
84
|
+
* Native caveats: rects arrive from native after layout, so decorations
|
|
85
|
+
* appear one frame after the text; links inside tables (`flavor="github"`)
|
|
86
|
+
* are not reported; content overflowing the component bounds does not
|
|
87
|
+
* receive touches on Android. Toggling this prop between set and unset
|
|
88
|
+
* remounts the native view.
|
|
89
|
+
* @platform ios, android, web
|
|
90
|
+
*/
|
|
91
|
+
renderLinkDecoration?: (info: LinkDecorationInfo) => ReactNode | null;
|
|
50
92
|
/**
|
|
51
93
|
* Callback fired when a task list checkbox is tapped.
|
|
52
94
|
*
|