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.
Files changed (140) hide show
  1. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerDelegate.java +15 -0
  2. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerInterface.java +3 -0
  3. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +15 -0
  4. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +3 -0
  5. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +122 -0
  6. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +100 -0
  7. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +20 -0
  8. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +124 -0
  9. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +145 -0
  10. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +19 -0
  11. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +22 -0
  12. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +85 -0
  13. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +22 -0
  14. package/android/src/main/java/com/swmansion/enriched/markdown/events/LinkLayoutEvent.kt +36 -0
  15. package/android/src/main/java/com/swmansion/enriched/markdown/events/SelectionChangeEvent.kt +42 -0
  16. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +37 -7
  17. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +5 -1
  18. package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBackgroundSpan.kt +13 -99
  19. package/android/src/main/java/com/swmansion/enriched/markdown/spans/InlineChipPainter.kt +140 -0
  20. package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkChipSpans.kt +177 -0
  21. package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkSpan.kt +1 -6
  22. package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkStyle.kt +36 -1
  23. package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkVariantEntry.kt +7 -3
  24. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +28 -0
  25. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/conversion/HTMLGenerator.kt +2 -1
  26. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/conversion/MarkdownExtractor.kt +6 -0
  27. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkLayoutCollector.kt +100 -0
  28. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt +4 -2
  29. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionChangeReporter.kt +230 -0
  30. package/android/src/main/java/com/swmansion/enriched/markdown/views/CodeBlockContainerView.kt +11 -1
  31. package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +22 -1
  32. package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +8 -1
  33. package/docs/API_REFERENCE.md +111 -0
  34. package/docs/MENTIONS.md +8 -1
  35. package/docs/STYLES.md +27 -0
  36. package/docs/TEXT.md +2 -0
  37. package/ios/EnrichedMarkdown.mm +221 -1
  38. package/ios/EnrichedMarkdownText.mm +142 -1
  39. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.cpp +122 -0
  40. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.h +100 -0
  41. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp +20 -0
  42. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +124 -0
  43. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +50 -2
  44. package/ios/internals/MeasurementCache.h +5 -0
  45. package/ios/renderer/LinkRenderer.m +47 -15
  46. package/ios/styles/StyleConfig.h +16 -0
  47. package/ios/styles/StyleConfig.mm +60 -0
  48. package/ios/utils/CodeBackground.m +3 -65
  49. package/ios/utils/ENRMChipDrawing.h +24 -0
  50. package/ios/utils/ENRMChipDrawing.m +83 -0
  51. package/ios/utils/ENRMLinkChip.h +70 -0
  52. package/ios/utils/ENRMLinkChip.m +194 -0
  53. package/ios/utils/ENRMLinkLayout.h +50 -0
  54. package/ios/utils/ENRMLinkLayout.m +45 -0
  55. package/ios/utils/ENRMSelectionChange.h +96 -0
  56. package/ios/utils/ENRMSelectionChange.m +204 -0
  57. package/ios/utils/EditMenuUtils+macOS.m +4 -1
  58. package/ios/utils/EditMenuUtils.m +4 -1
  59. package/ios/utils/HTMLGenerator.m +2 -1
  60. package/ios/utils/MarkdownAccessibilityElementBuilder.m +3 -1
  61. package/ios/utils/MarkdownExtractor.m +2 -1
  62. package/ios/utils/RTFExportUtils.m +2 -0
  63. package/ios/utils/StylePropsUtils.h +37 -1
  64. package/ios/utils/TextViewLayoutManager.mm +3 -0
  65. package/ios/views/ENRMCodeBlockContainerView.h +3 -0
  66. package/ios/views/ENRMCodeBlockContainerView.m +3 -0
  67. package/ios/views/ENRMMathContainerView.h +3 -0
  68. package/ios/views/ENRMMathContainerView.m +3 -0
  69. package/ios/views/TableContainerView.h +6 -0
  70. package/ios/views/TableContainerView.m +24 -0
  71. package/lib/module/EnrichedMarkdownNativeComponent.ts +101 -1
  72. package/lib/module/EnrichedMarkdownTextNativeComponent.ts +101 -1
  73. package/lib/module/index.js.map +1 -1
  74. package/lib/module/jest/index.js +13 -0
  75. package/lib/module/jest/index.js.map +1 -1
  76. package/lib/module/native/EnrichedMarkdownText.js +138 -9
  77. package/lib/module/native/EnrichedMarkdownText.js.map +1 -1
  78. package/lib/module/normalizeMarkdownStyle.js +13 -2
  79. package/lib/module/normalizeMarkdownStyle.js.map +1 -1
  80. package/lib/module/normalizeMarkdownStyle.web.js +13 -2
  81. package/lib/module/normalizeMarkdownStyle.web.js.map +1 -1
  82. package/lib/module/web/EnrichedMarkdownText.js +6 -3
  83. package/lib/module/web/EnrichedMarkdownText.js.map +1 -1
  84. package/lib/module/web/renderers/InlineRenderers.js +96 -3
  85. package/lib/module/web/renderers/InlineRenderers.js.map +1 -1
  86. package/lib/module/web/styles.js +18 -0
  87. package/lib/module/web/styles.js.map +1 -1
  88. package/lib/module/web/utils.js +13 -0
  89. package/lib/module/web/utils.js.map +1 -1
  90. package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +93 -2
  91. package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
  92. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +93 -2
  93. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
  94. package/lib/typescript/src/index.d.ts +2 -2
  95. package/lib/typescript/src/index.d.ts.map +1 -1
  96. package/lib/typescript/src/index.web.d.ts +1 -1
  97. package/lib/typescript/src/index.web.d.ts.map +1 -1
  98. package/lib/typescript/src/jest/index.d.ts +1 -1
  99. package/lib/typescript/src/jest/index.d.ts.map +1 -1
  100. package/lib/typescript/src/native/EnrichedMarkdownText.d.ts +5 -5
  101. package/lib/typescript/src/native/EnrichedMarkdownText.d.ts.map +1 -1
  102. package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -1
  103. package/lib/typescript/src/normalizeMarkdownStyle.web.d.ts.map +1 -1
  104. package/lib/typescript/src/types/MarkdownStyle.d.ts +28 -5
  105. package/lib/typescript/src/types/MarkdownStyle.d.ts.map +1 -1
  106. package/lib/typescript/src/types/MarkdownStyleInternal.d.ts +10 -0
  107. package/lib/typescript/src/types/MarkdownStyleInternal.d.ts.map +1 -1
  108. package/lib/typescript/src/types/MarkdownTextProps.d.ts +76 -2
  109. package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
  110. package/lib/typescript/src/types/MarkdownTextProps.web.d.ts +41 -1
  111. package/lib/typescript/src/types/MarkdownTextProps.web.d.ts.map +1 -1
  112. package/lib/typescript/src/types/events.d.ts +36 -0
  113. package/lib/typescript/src/types/events.d.ts.map +1 -1
  114. package/lib/typescript/src/web/EnrichedMarkdownText.d.ts +1 -1
  115. package/lib/typescript/src/web/EnrichedMarkdownText.d.ts.map +1 -1
  116. package/lib/typescript/src/web/renderers/InlineRenderers.d.ts.map +1 -1
  117. package/lib/typescript/src/web/styles.d.ts.map +1 -1
  118. package/lib/typescript/src/web/types.d.ts +4 -0
  119. package/lib/typescript/src/web/types.d.ts.map +1 -1
  120. package/lib/typescript/src/web/utils.d.ts +4 -0
  121. package/lib/typescript/src/web/utils.d.ts.map +1 -1
  122. package/package.json +1 -1
  123. package/src/EnrichedMarkdownNativeComponent.ts +101 -1
  124. package/src/EnrichedMarkdownTextNativeComponent.ts +101 -1
  125. package/src/index.tsx +5 -0
  126. package/src/index.web.tsx +5 -1
  127. package/src/jest/index.tsx +21 -1
  128. package/src/native/EnrichedMarkdownText.tsx +181 -8
  129. package/src/normalizeMarkdownStyle.ts +13 -0
  130. package/src/normalizeMarkdownStyle.web.ts +11 -0
  131. package/src/types/MarkdownStyle.ts +29 -5
  132. package/src/types/MarkdownStyleInternal.ts +10 -0
  133. package/src/types/MarkdownTextProps.ts +85 -1
  134. package/src/types/MarkdownTextProps.web.ts +43 -1
  135. package/src/types/events.ts +38 -0
  136. package/src/web/EnrichedMarkdownText.tsx +10 -3
  137. package/src/web/renderers/InlineRenderers.tsx +111 -3
  138. package/src/web/styles.ts +25 -0
  139. package/src/web/types.ts +4 -0
  140. package/src/web/utils.ts +8 -0
@@ -40,6 +40,44 @@ export interface CopyEvent {
40
40
  whole: boolean;
41
41
  }
42
42
 
43
+ /**
44
+ * A rect in dp, relative to the component's native view: add the origin from
45
+ * the component ref's `measureInWindow` to get window coordinates.
46
+ */
47
+ export interface SelectionRect {
48
+ x: number;
49
+ y: number;
50
+ width: number;
51
+ height: number;
52
+ }
53
+
54
+ /**
55
+ * Payload of `onSelectionChange`: the current text selection, or a table /
56
+ * math / code block long-pressed while `contextMenuHidden` is on. Content
57
+ * fields carry what `onCopy` would receive for the same selection.
58
+ */
59
+ export interface SelectionChangeEvent {
60
+ /** `false` once the selection is gone; every other field is then empty / zero. */
61
+ active: boolean;
62
+ /** `'selection'` for text; the block kind for a long-pressed block. */
63
+ source: CopyEvent['source'];
64
+ /** Rendered text of the selection, or the block's raw content. */
65
+ text: string;
66
+ /** Same as `CopyEvent.markdown` for this selection. */
67
+ markdown: string;
68
+ /** Same as `CopyEvent.whole`. Always `true` for blocks. */
69
+ whole: boolean;
70
+ /**
71
+ * Bounding box of the selection (full content width when it spans several
72
+ * lines); the block's frame for blocks.
73
+ */
74
+ rect: SelectionRect;
75
+ /** Selected part of the first line (block: same as `rect`). */
76
+ startRect: SelectionRect;
77
+ /** Selected part of the last line (block: same as `rect`). */
78
+ endRect: SelectionRect;
79
+ }
80
+
43
81
  /**
44
82
  * Native-level context menu item config sent to the native component.
45
83
  * Does not include the `onPress` callback — callbacks are managed on the JS side.
@@ -9,7 +9,7 @@ import {
9
9
  import { parseMarkdown } from './parseMarkdown';
10
10
  import { RenderNode } from './renderers';
11
11
  import type { ASTNode, RendererCallbacks, RenderCapabilities } from './types';
12
- import { indexTaskItems, markInlineImages } from './utils';
12
+ import { indexLinks, indexTaskItems, markInlineImages } from './utils';
13
13
  import { loadKaTeX } from './katex';
14
14
  import type { KaTeXInstance } from './katex';
15
15
  import { ENRM_TEXT_CLASS, ENRM_SELECTION_BG_VAR } from './globalStyles';
@@ -27,6 +27,7 @@ export const EnrichedMarkdownText = ({
27
27
  selectable = true,
28
28
  dir,
29
29
  selectionColor,
30
+ renderLinkDecoration,
30
31
  ...rest
31
32
  }: EnrichedMarkdownTextProps) => {
32
33
  const normalizedStyle = useMemo(
@@ -66,6 +67,7 @@ export const EnrichedMarkdownText = ({
66
67
  .then(([result, katexInstance]) => {
67
68
  if (!cancelled) {
68
69
  indexTaskItems(result);
70
+ indexLinks(result);
69
71
  markInlineImages(result);
70
72
 
71
73
  setParseError(false);
@@ -99,8 +101,13 @@ export const EnrichedMarkdownText = ({
99
101
  ]);
100
102
 
101
103
  const callbacks = useMemo<RendererCallbacks>(
102
- () => ({ onLinkPress, onLinkLongPress, onTaskListItemPress }),
103
- [onLinkPress, onLinkLongPress, onTaskListItemPress]
104
+ () => ({
105
+ onLinkPress,
106
+ onLinkLongPress,
107
+ onTaskListItemPress,
108
+ renderLinkDecoration,
109
+ }),
110
+ [onLinkPress, onLinkLongPress, onTaskListItemPress, renderLinkDecoration]
104
111
  );
105
112
 
106
113
  const capabilities = useMemo<RenderCapabilities>(
@@ -1,8 +1,16 @@
1
- import type { MouseEvent } from 'react';
1
+ import {
2
+ useLayoutEffect,
3
+ useRef,
4
+ useState,
5
+ type MouseEvent,
6
+ type ReactNode,
7
+ } from 'react';
8
+ import type { LinkRect } from '../../types/MarkdownTextProps.web';
2
9
  import type { RendererProps, RendererMap } from '../types';
3
10
  import { extractNodeText } from '../utils';
4
11
  import { KaTeXRenderer } from './KaTeXRenderer';
5
12
  import { linkStyleForUrl } from '../styles';
13
+ import type { CSSProperties } from 'react';
6
14
 
7
15
  function TextRenderer({ node }: RendererProps) {
8
16
  return <>{node.content ?? ''}</>;
@@ -78,16 +86,116 @@ function LinkRenderer({
78
86
  }
79
87
  };
80
88
 
89
+ const linkStyle = linkStyleForUrl(style, url);
90
+ const children = renderChildren(node);
91
+
92
+ if (callbacks.renderLinkDecoration) {
93
+ return (
94
+ <DecoratedLink
95
+ url={url}
96
+ index={node.attributes?.linkIndex ?? 0}
97
+ text={extractNodeText(node)}
98
+ linkStyle={linkStyle}
99
+ renderLinkDecoration={callbacks.renderLinkDecoration}
100
+ onClick={handleClick}
101
+ onContextMenu={handleContextMenu}
102
+ >
103
+ {children}
104
+ </DecoratedLink>
105
+ );
106
+ }
107
+
81
108
  return (
82
109
  <a
83
110
  href={url}
84
- style={linkStyleForUrl(style, url)}
111
+ style={linkStyle}
85
112
  target="_blank"
86
113
  rel="noopener noreferrer"
87
114
  onClick={handleClick}
88
115
  onContextMenu={handleContextMenu}
89
116
  >
90
- {renderChildren(node)}
117
+ {children}
118
+ </a>
119
+ );
120
+ }
121
+
122
+ const EMPTY_RECT: LinkRect = { x: 0, y: 0, width: 0, height: 0 };
123
+
124
+ const decorationLayerStyle: CSSProperties = {
125
+ position: 'absolute',
126
+ inset: 0,
127
+ pointerEvents: 'none',
128
+ };
129
+
130
+ const decorationContentStyle: CSSProperties = { pointerEvents: 'auto' };
131
+
132
+ /**
133
+ * A link with a `renderLinkDecoration` overlay. Unlike native, the overlay
134
+ * anchors to the link's whole inline box (not the last line fragment of a
135
+ * wrapping link), and `rect` is that box relative to itself.
136
+ */
137
+ function DecoratedLink({
138
+ url,
139
+ index,
140
+ text,
141
+ linkStyle,
142
+ renderLinkDecoration,
143
+ onClick,
144
+ onContextMenu,
145
+ children,
146
+ }: {
147
+ url: string;
148
+ index: number;
149
+ text: string;
150
+ linkStyle: ReturnType<typeof linkStyleForUrl>;
151
+ renderLinkDecoration: NonNullable<
152
+ RendererProps['callbacks']['renderLinkDecoration']
153
+ >;
154
+ onClick: (event: MouseEvent) => void;
155
+ onContextMenu: (event: MouseEvent) => void;
156
+ children: ReactNode;
157
+ }) {
158
+ const anchorRef = useRef<HTMLAnchorElement>(null);
159
+ const [rect, setRect] = useState<LinkRect>(EMPTY_RECT);
160
+
161
+ // The overlay itself is sized by CSS (inset: 0); `rect` is informational, so
162
+ // re-measuring when the content or chip style changes is enough.
163
+ useLayoutEffect(() => {
164
+ const anchor = anchorRef.current;
165
+ if (!anchor) return;
166
+ const { width, height } = anchor.getBoundingClientRect();
167
+ setRect((prev) =>
168
+ prev.width === width && prev.height === height
169
+ ? prev
170
+ : { x: 0, y: 0, width, height }
171
+ );
172
+ }, [text, linkStyle]);
173
+
174
+ const decoration = renderLinkDecoration({
175
+ index,
176
+ url,
177
+ text,
178
+ rect,
179
+ rects: [rect],
180
+ });
181
+
182
+ return (
183
+ <a
184
+ ref={anchorRef}
185
+ href={url}
186
+ style={{ ...linkStyle, position: 'relative' }} // eslint-disable-line react-native/no-inline-styles
187
+ target="_blank"
188
+ rel="noopener noreferrer"
189
+ onClick={onClick}
190
+ onContextMenu={onContextMenu}
191
+ >
192
+ {children}
193
+ {decoration != null && (
194
+ <span aria-hidden style={decorationLayerStyle}>
195
+ {/* Mirrors native pointerEvents="box-none": only the decoration itself is hit-testable. */}
196
+ <span style={decorationContentStyle}>{decoration}</span>
197
+ </span>
198
+ )}
91
199
  </a>
92
200
  );
93
201
  }
package/src/web/styles.ts CHANGED
@@ -323,6 +323,12 @@ export function linkStyleForUrl(
323
323
  variantIndex !== -1 ? style.linkVariants[variantIndex]! : base;
324
324
 
325
325
  const backgroundColor = resolved.backgroundColor;
326
+ const hasBorder =
327
+ resolved.borderWidth > 0 &&
328
+ !!resolved.borderColor &&
329
+ resolved.borderColor !== 'transparent';
330
+ const hasPadding =
331
+ resolved.paddingHorizontal > 0 || resolved.paddingVertical > 0;
326
332
  return {
327
333
  color: resolved.color,
328
334
  fontFamily: normalizeFontFamily(base.fontFamily),
@@ -330,6 +336,25 @@ export function linkStyleForUrl(
330
336
  ...(backgroundColor && backgroundColor !== 'transparent'
331
337
  ? { backgroundColor }
332
338
  : undefined),
339
+ // Chip: inline padding reserves horizontal space like native; vertical
340
+ // padding only paints (inline boxes don't grow the line box). `clone`
341
+ // rounds and pads each fragment of a wrapping link.
342
+ ...(hasPadding
343
+ ? {
344
+ padding: `${resolved.paddingVertical}px ${resolved.paddingHorizontal}px`,
345
+ }
346
+ : undefined),
347
+ ...(resolved.borderRadius > 0
348
+ ? { borderRadius: resolved.borderRadius }
349
+ : undefined),
350
+ ...(hasBorder
351
+ ? {
352
+ border: `${resolved.borderWidth}px solid ${resolved.borderColor}`,
353
+ }
354
+ : undefined),
355
+ ...(hasPadding || hasBorder || resolved.borderRadius > 0
356
+ ? { boxDecorationBreak: 'clone', WebkitBoxDecorationBreak: 'clone' }
357
+ : undefined),
333
358
  };
334
359
  }
335
360
 
package/src/web/types.ts CHANGED
@@ -6,6 +6,7 @@ import type {
6
6
  LinkLongPressEvent,
7
7
  TaskListItemPressEvent,
8
8
  } from '../types/events';
9
+ import type { LinkDecorationInfo } from '../types/MarkdownTextProps.web';
9
10
  import type { KaTeXInstance } from './katex';
10
11
 
11
12
  export type NodeType =
@@ -52,6 +53,8 @@ export interface NodeAttributes {
52
53
  taskIndex?: number;
53
54
  /** Stamped by markInlineImages() — not present in the raw WASM output. */
54
55
  isInline?: boolean;
56
+ /** Stamped by indexLinks() — document-order link index, not in the raw WASM output. */
57
+ linkIndex?: number;
55
58
  colCount?: string;
56
59
  headRowCount?: string;
57
60
  bodyRowCount?: string;
@@ -72,6 +75,7 @@ export interface RendererCallbacks {
72
75
  onLinkPress?: (event: LinkPressEvent) => void;
73
76
  onLinkLongPress?: (event: LinkLongPressEvent) => void;
74
77
  onTaskListItemPress?: (event: TaskListItemPressEvent) => void;
78
+ renderLinkDecoration?: (info: LinkDecorationInfo) => ReactNode | null;
75
79
  }
76
80
 
77
81
  export interface RenderCapabilities {
package/src/web/utils.ts CHANGED
@@ -30,6 +30,14 @@ export function indexTaskItems(node: ASTNode, counter = { value: 0 }): void {
30
30
  node.children?.forEach((child) => indexTaskItems(child, counter));
31
31
  }
32
32
 
33
+ /** Stamps Link nodes with their document-order index (the `renderLinkDecoration` key). */
34
+ export function indexLinks(node: ASTNode, counter = { value: 0 }): void {
35
+ if (node.type === 'Link') {
36
+ node.attributes = { ...node.attributes, linkIndex: counter.value++ };
37
+ }
38
+ node.children?.forEach((child) => indexLinks(child, counter));
39
+ }
40
+
33
41
  /**
34
42
  * Stamps Image nodes with `isInline` when they appear inside a paragraph
35
43
  * that also contains other content (text, links, etc.).