@ai-react-markdown/core 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,407 @@
1
+ # @ai-react-markdown/core
2
+
3
+ A batteries-included React component for rendering AI-generated markdown with first-class support for LaTeX math, GFM, CJK text, and streaming content.
4
+
5
+ ## Features
6
+
7
+ - **GFM** -- tables, strikethrough, task lists, autolinks via `remark-gfm`
8
+ - **LaTeX math** -- inline and display math rendered with KaTeX; smart preprocessing handles currency `$` signs, bracket delimiters (`\[...\]`, `\(...\)`), pipe escaping, and mhchem commands
9
+ - **Emoji** -- shortcode support (`:smile:`) via `remark-emoji`
10
+ - **CJK-friendly** -- proper line breaking and spacing for Chinese, Japanese, and Korean text
11
+ - **Extra syntax** -- highlight (`==text==`), definition lists, superscript/subscript
12
+ - **Display optimizations** -- SmartyPants typography, pangu CJK spacing, HTML comment removal
13
+ - **Streaming-aware** -- built-in `streaming` flag propagated via context for custom components
14
+ - **Customizable** -- swap typography, color scheme, individual markdown element renderers, and inject extra style wrappers
15
+ - **Metadata context** -- pass arbitrary data to deeply nested custom components without prop drilling, isolated from render state to avoid unnecessary re-renders
16
+ - **TypeScript** -- full generic support for extended configs and metadata types
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ # npm
22
+ npm install @ai-react-markdown/core
23
+
24
+ # pnpm
25
+ pnpm add @ai-react-markdown/core
26
+
27
+ # yarn
28
+ yarn add @ai-react-markdown/core
29
+ ```
30
+
31
+ ### Peer Dependencies
32
+
33
+ ```json
34
+ {
35
+ "react": ">=19.0.0",
36
+ "react-dom": ">=19.0.0"
37
+ }
38
+ ```
39
+
40
+ ### CSS Dependencies
41
+
42
+ For LaTeX math rendering, include the KaTeX stylesheet:
43
+
44
+ ```tsx
45
+ import 'katex/dist/katex.min.css';
46
+ ```
47
+
48
+ For the built-in default typography, include the typography CSS:
49
+
50
+ ```tsx
51
+ import '@ai-react-markdown/core/typography/default.css';
52
+ // or import all typography variants at once:
53
+ import '@ai-react-markdown/core/typography/all.css';
54
+ ```
55
+
56
+ ## Quick Start
57
+
58
+ ```tsx
59
+ import AIMarkdown from '@ai-react-markdown/core';
60
+ import 'katex/dist/katex.min.css';
61
+ import '@ai-react-markdown/core/typography/default.css';
62
+
63
+ function App() {
64
+ return <AIMarkdown content="Hello **world**! Math: $E = mc^2$" />;
65
+ }
66
+ ```
67
+
68
+ ### Streaming Example
69
+
70
+ ```tsx
71
+ function StreamingChat({ content, isStreaming }: { content: string; isStreaming: boolean }) {
72
+ return <AIMarkdown content={content} streaming={isStreaming} colorScheme="dark" />;
73
+ }
74
+ ```
75
+
76
+ ## Props API Reference
77
+
78
+ ### `AIMarkdownProps<TConfig, TRenderData>`
79
+
80
+ | Prop | Type | Default | Description |
81
+ | ---------------------- | -------------------------------- | ------------------------------- | ---------------------------------------------------------------------- |
82
+ | `content` | `string` | **(required)** | Raw markdown content to render. |
83
+ | `streaming` | `boolean` | `false` | Whether content is actively being streamed (e.g. from an LLM). |
84
+ | `fontSize` | `number \| string` | `'0.875rem'` | Base font size. Numbers are treated as pixels. |
85
+ | `variant` | `AIMarkdownVariant` | `'default'` | Typography variant name. |
86
+ | `colorScheme` | `AIMarkdownColorScheme` | `'light'` | Color scheme name (`'light'`, `'dark'`, or custom). |
87
+ | `config` | `PartialDeep<TConfig>` | `undefined` | Partial render config, deep-merged with defaults. |
88
+ | `defaultConfig` | `TConfig` | `defaultAIMarkdownRenderConfig` | Base config to merge against. Sub-packages can pass extended defaults. |
89
+ | `metadata` | `TRenderData` | `undefined` | Arbitrary data passed to custom components via a dedicated context. |
90
+ | `contentPreprocessors` | `AIMDContentPreprocessor[]` | `[]` | Additional preprocessors run after the built-in LaTeX preprocessor. |
91
+ | `customComponents` | `AIMarkdownCustomComponents` | `undefined` | `react-markdown` component overrides for specific HTML elements. |
92
+ | `Typography` | `AIMarkdownTypographyComponent` | `DefaultTypography` | Typography wrapper component. |
93
+ | `ExtraStyles` | `AIMarkdownExtraStylesComponent` | `undefined` | Optional extra style wrapper rendered between typography and content. |
94
+
95
+ ## Configuration
96
+
97
+ Rendering behavior is controlled by `AIMarkdownRenderConfig`, which has two configuration arrays:
98
+
99
+ ### Extra Syntax Extensions
100
+
101
+ Enable via `config.extraSyntaxSupported`. All are enabled by default.
102
+
103
+ | Value | Description |
104
+ | --------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
105
+ | `AIMarkdownRenderExtraSyntax.HIGHLIGHT` | `==Highlight==` syntax support |
106
+ | `AIMarkdownRenderExtraSyntax.DEFINITION_LIST` | Definition list syntax ([PHP Markdown Extra](https://michelf.ca/projects/php-markdown/extra/#def-list)) |
107
+ | `AIMarkdownRenderExtraSyntax.SUBSCRIPT` | Superscript (`^text^`) and subscript (`~text~`) |
108
+
109
+ ### Display Optimization Abilities
110
+
111
+ Enable via `config.displayOptimizeAbilities`. All are enabled by default.
112
+
113
+ | Value | Description |
114
+ | -------------------------------------------------------- | -------------------------------------------------------- |
115
+ | `AIMarkdownRenderDisplayOptimizeAbility.REMOVE_COMMENTS` | Strip HTML comments |
116
+ | `AIMarkdownRenderDisplayOptimizeAbility.SMARTYPANTS` | Typographic enhancements (curly quotes, em-dashes, etc.) |
117
+ | `AIMarkdownRenderDisplayOptimizeAbility.PANGU` | Auto-insert spaces between CJK and half-width characters |
118
+
119
+ ### Example: Selective Configuration
120
+
121
+ ```tsx
122
+ import AIMarkdown, {
123
+ AIMarkdownRenderExtraSyntax,
124
+ AIMarkdownRenderDisplayOptimizeAbility,
125
+ } from '@ai-react-markdown/core';
126
+
127
+ <AIMarkdown
128
+ content={markdown}
129
+ config={{
130
+ extraSyntaxSupported: [AIMarkdownRenderExtraSyntax.HIGHLIGHT],
131
+ displayOptimizeAbilities: [AIMarkdownRenderDisplayOptimizeAbility.SMARTYPANTS],
132
+ }}
133
+ />;
134
+ ```
135
+
136
+ When you provide a partial `config`, it is deep-merged with the defaults. Array values (like `extraSyntaxSupported`) are **replaced entirely**, not merged by index -- so the example above enables only the highlight extension, disabling definition lists and subscript.
137
+
138
+ ## Hooks
139
+
140
+ ### `useAIMarkdownRenderState<TConfig>()`
141
+
142
+ Access the current render state from within any component rendered inside `<AIMarkdown>`. Throws if called outside the provider boundary.
143
+
144
+ ```tsx
145
+ import { useAIMarkdownRenderState } from '@ai-react-markdown/core';
146
+
147
+ function CustomCodeBlock({ children }: PropsWithChildren) {
148
+ const { streaming, config, fontSize, variant, colorScheme } = useAIMarkdownRenderState();
149
+
150
+ if (streaming) {
151
+ return <pre className="streaming">{children}</pre>;
152
+ }
153
+ return <pre>{children}</pre>;
154
+ }
155
+ ```
156
+
157
+ **Returns** `AIMarkdownRenderState<TConfig>`:
158
+
159
+ | Field | Type | Description |
160
+ | ------------- | ----------------------- | --------------------------------------------------- |
161
+ | `streaming` | `boolean` | Whether content is being streamed. |
162
+ | `fontSize` | `string` | Resolved CSS font-size value. |
163
+ | `variant` | `AIMarkdownVariant` | Active typography variant. |
164
+ | `colorScheme` | `AIMarkdownColorScheme` | Active color scheme. |
165
+ | `config` | `TConfig` | Active render configuration (merged with defaults). |
166
+
167
+ ### `useAIMarkdownMetadata<TMetadata>()`
168
+
169
+ Access arbitrary metadata from within the `<AIMarkdown>` tree. Metadata lives in a **separate** React context from render state, so metadata changes do not trigger re-renders in components that only consume render state.
170
+
171
+ ```tsx
172
+ import { useAIMarkdownMetadata } from '@ai-react-markdown/core';
173
+
174
+ interface MyMetadata {
175
+ onCopyCode: (code: string) => void;
176
+ messageId: string;
177
+ }
178
+
179
+ function CustomCodeBlock({ children }: PropsWithChildren) {
180
+ const metadata = useAIMarkdownMetadata<MyMetadata>();
181
+ return (
182
+ <pre>
183
+ <button onClick={() => metadata?.onCopyCode(String(children))}>Copy</button>
184
+ {children}
185
+ </pre>
186
+ );
187
+ }
188
+ ```
189
+
190
+ **Returns** `TMetadata | undefined` -- `undefined` when no metadata was provided.
191
+
192
+ ### `useStableValue<T>(value: T)`
193
+
194
+ Returns a referentially stable version of `value`. On each render the new value is deep-compared (via `lodash/isEqual`) against the previous one. If they are structurally equal, the previous reference is returned, preventing unnecessary re-renders in downstream `useMemo`/`useEffect` consumers.
195
+
196
+ ```tsx
197
+ import { useStableValue } from '@ai-react-markdown/core';
198
+
199
+ const stableConfig = useStableValue(config);
200
+ // stableConfig keeps the same reference as long as config is deep-equal.
201
+ ```
202
+
203
+ ## Typography and Styling
204
+
205
+ The `<AIMarkdown>` component wraps its content in a typography component that controls font size, variant, and color scheme.
206
+
207
+ ### Built-in Default Typography
208
+
209
+ The built-in `DefaultTypography` renders a `<div>` with CSS class names for the active variant and color scheme:
210
+
211
+ ```html
212
+ <div class="aim-typography-root default light" style="width: 100%; font-size: 0.875rem">
213
+ <!-- markdown content -->
214
+ </div>
215
+ ```
216
+
217
+ Import the corresponding CSS to activate styles:
218
+
219
+ ```tsx
220
+ import '@ai-react-markdown/core/typography/default.css';
221
+ ```
222
+
223
+ ### Custom Typography Component
224
+
225
+ Replace the typography wrapper by passing a custom component:
226
+
227
+ ```tsx
228
+ import type { AIMarkdownTypographyProps } from '@ai-react-markdown/core';
229
+
230
+ function MyTypography({ children, fontSize, variant, colorScheme }: AIMarkdownTypographyProps) {
231
+ return (
232
+ <div className={`my-markdown ${colorScheme}`} style={{ fontSize }}>
233
+ {children}
234
+ </div>
235
+ );
236
+ }
237
+
238
+ <AIMarkdown content={markdown} Typography={MyTypography} />;
239
+ ```
240
+
241
+ ### Extra Styles Wrapper
242
+
243
+ The `ExtraStyles` prop accepts a component rendered between the typography wrapper and the markdown content. Useful for injecting additional CSS scope or theme providers:
244
+
245
+ ```tsx
246
+ import type { AIMarkdownExtraStylesProps } from '@ai-react-markdown/core';
247
+
248
+ function MyExtraStyles({ children }: AIMarkdownExtraStylesProps) {
249
+ return <div className="my-extra-scope">{children}</div>;
250
+ }
251
+
252
+ <AIMarkdown content={markdown} ExtraStyles={MyExtraStyles} />;
253
+ ```
254
+
255
+ ## Custom Components
256
+
257
+ Override the default renderers for specific HTML elements using the `customComponents` prop. This maps directly to `react-markdown`'s `Components` type:
258
+
259
+ ```tsx
260
+ import type { AIMarkdownCustomComponents } from '@ai-react-markdown/core';
261
+
262
+ const components: AIMarkdownCustomComponents = {
263
+ a: ({ href, children }) => (
264
+ <a href={href} target="_blank" rel="noopener noreferrer">
265
+ {children}
266
+ </a>
267
+ ),
268
+ img: ({ src, alt }) => <img src={src} alt={alt} loading="lazy" />,
269
+ };
270
+
271
+ <AIMarkdown content={markdown} customComponents={components} />;
272
+ ```
273
+
274
+ ## Streaming Support
275
+
276
+ Pass `streaming={true}` when content is actively being generated (e.g. token-by-token from an LLM). The flag is propagated to all descendant components via `useAIMarkdownRenderState()`, allowing custom renderers to adapt their behavior (e.g. show a cursor, disable copy buttons, or skip animations).
277
+
278
+ ```tsx
279
+ function ChatMessage({ content, isStreaming }: { content: string; isStreaming: boolean }) {
280
+ return <AIMarkdown content={content} streaming={isStreaming} />;
281
+ }
282
+ ```
283
+
284
+ ## Metadata
285
+
286
+ The `metadata` prop lets you pass arbitrary data to deeply nested custom components without prop drilling. Metadata is stored in a **separate React context** from the render state, so updating metadata does not cause re-renders in components that only read render state (like the core `MarkdownContent`).
287
+
288
+ ```tsx
289
+ interface ChatMetadata {
290
+ messageId: string;
291
+ onCopyCode: (code: string) => void;
292
+ onRegenerate: () => void;
293
+ }
294
+
295
+ <AIMarkdown<AIMarkdownRenderConfig, ChatMetadata>
296
+ content={markdown}
297
+ metadata={{
298
+ messageId: msg.id,
299
+ onCopyCode: handleCopy,
300
+ onRegenerate: handleRegenerate,
301
+ }}
302
+ />;
303
+ ```
304
+
305
+ ## Content Preprocessors
306
+
307
+ The rendering pipeline runs a LaTeX preprocessor by default. You can append additional preprocessors that transform the raw markdown string before it enters the remark/rehype pipeline:
308
+
309
+ ```tsx
310
+ import type { AIMDContentPreprocessor } from '@ai-react-markdown/core';
311
+
312
+ const stripFrontmatter: AIMDContentPreprocessor = (content) => content.replace(/^---[\s\S]*?---\n/, '');
313
+
314
+ <AIMarkdown content={markdown} contentPreprocessors={[stripFrontmatter]} />;
315
+ ```
316
+
317
+ Preprocessors run in sequence: built-in LaTeX preprocessor first, then your custom ones in array order.
318
+
319
+ ## TypeScript Generics
320
+
321
+ The component supports two generic type parameters for type-safe config and metadata:
322
+
323
+ ```tsx
324
+ import AIMarkdown, { type AIMarkdownRenderConfig, type AIMarkdownMetadata } from '@ai-react-markdown/core';
325
+
326
+ // Extended config (e.g. adding code block options)
327
+ interface MyConfig extends AIMarkdownRenderConfig {
328
+ codeBlock: { defaultExpanded: boolean };
329
+ }
330
+
331
+ // Extended metadata
332
+ interface MyMetadata extends AIMarkdownMetadata {
333
+ messageId: string;
334
+ }
335
+
336
+ <AIMarkdown<MyConfig, MyMetadata>
337
+ content={markdown}
338
+ defaultConfig={myDefaultConfig}
339
+ config={{ codeBlock: { defaultExpanded: false } }}
340
+ metadata={{ messageId: '123' }}
341
+ />;
342
+ ```
343
+
344
+ Sub-packages like `@ai-react-markdown/mantine` use this pattern to extend the base config with additional options (e.g. `forceSameFontSize`, `codeBlock.autoDetectUnknownLanguage`) while inheriting all core functionality.
345
+
346
+ Similarly, hooks accept generic parameters for type-safe access:
347
+
348
+ ```tsx
349
+ const { config } = useAIMarkdownRenderState<MyConfig>();
350
+ const metadata = useAIMarkdownMetadata<MyMetadata>();
351
+ ```
352
+
353
+ ## Architecture Overview
354
+
355
+ ```text
356
+ <AIMarkdown>
357
+ <AIMarkdownMetadataProvider> // Separate context for metadata
358
+ <AIMarkdownRenderStateProvider> // Context for render state (streaming, config, etc.)
359
+ <Typography> // Configurable typography wrapper
360
+ <ExtraStyles?> // Optional extra style wrapper
361
+ <AIMarkdownContent /> // react-markdown with remark/rehype plugin chain
362
+ </ExtraStyles?>
363
+ </Typography>
364
+ </AIMarkdownRenderStateProvider>
365
+ </AIMarkdownMetadataProvider>
366
+ </AIMarkdown>
367
+ ```
368
+
369
+ The metadata and render state providers are deliberately separated so that metadata changes (e.g. callback updates) do not trigger re-renders in `AIMarkdownContent`, which only consumes render state.
370
+
371
+ ## Exported API
372
+
373
+ ### Default Export
374
+
375
+ - `AIMarkdown` -- the main component (memoized)
376
+
377
+ ### Types
378
+
379
+ - `AIMarkdownProps`
380
+ - `AIMarkdownCustomComponents`
381
+ - `AIMarkdownRenderConfig`
382
+ - `AIMarkdownRenderState`
383
+ - `AIMarkdownMetadata`
384
+ - `AIMarkdownTypographyProps`
385
+ - `AIMarkdownTypographyComponent`
386
+ - `AIMarkdownExtraStylesProps`
387
+ - `AIMarkdownExtraStylesComponent`
388
+ - `AIMarkdownVariant`
389
+ - `AIMarkdownColorScheme`
390
+ - `AIMDContentPreprocessor`
391
+ - `PartialDeep`
392
+
393
+ ### Enums and Constants
394
+
395
+ - `AIMarkdownRenderExtraSyntax`
396
+ - `AIMarkdownRenderDisplayOptimizeAbility`
397
+ - `defaultAIMarkdownRenderConfig`
398
+
399
+ ### Hooks (re-exported)
400
+
401
+ - `useAIMarkdownRenderState()`
402
+ - `useAIMarkdownMetadata()`
403
+ - `useStableValue()`
404
+
405
+ ## License
406
+
407
+ MIT