@typefm/react-markdown-viewer 0.1.0

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 (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +217 -0
  3. package/dist/ErrorBoundary.d.ts +25 -0
  4. package/dist/ErrorBoundary.d.ts.map +1 -0
  5. package/dist/ErrorBoundary.js +29 -0
  6. package/dist/ErrorBoundary.js.map +1 -0
  7. package/dist/MarkdownViewer.d.ts +41 -0
  8. package/dist/MarkdownViewer.d.ts.map +1 -0
  9. package/dist/MarkdownViewer.js +69 -0
  10. package/dist/MarkdownViewer.js.map +1 -0
  11. package/dist/index.d.ts +17 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +21 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/lib/cache-manager.d.ts +59 -0
  16. package/dist/lib/cache-manager.d.ts.map +1 -0
  17. package/dist/lib/cache-manager.js +160 -0
  18. package/dist/lib/cache-manager.js.map +1 -0
  19. package/dist/lib/cursor-controller.d.ts +13 -0
  20. package/dist/lib/cursor-controller.d.ts.map +1 -0
  21. package/dist/lib/cursor-controller.js +93 -0
  22. package/dist/lib/cursor-controller.js.map +1 -0
  23. package/dist/lib/defaults/code-block.d.ts +71 -0
  24. package/dist/lib/defaults/code-block.d.ts.map +1 -0
  25. package/dist/lib/defaults/code-block.js +104 -0
  26. package/dist/lib/defaults/code-block.js.map +1 -0
  27. package/dist/lib/defaults/image.d.ts +41 -0
  28. package/dist/lib/defaults/image.d.ts.map +1 -0
  29. package/dist/lib/defaults/image.js +45 -0
  30. package/dist/lib/defaults/image.js.map +1 -0
  31. package/dist/lib/defaults/link.d.ts +45 -0
  32. package/dist/lib/defaults/link.d.ts.map +1 -0
  33. package/dist/lib/defaults/link.js +76 -0
  34. package/dist/lib/defaults/link.js.map +1 -0
  35. package/dist/lib/defaults/math.d.ts +51 -0
  36. package/dist/lib/defaults/math.d.ts.map +1 -0
  37. package/dist/lib/defaults/math.js +119 -0
  38. package/dist/lib/defaults/math.js.map +1 -0
  39. package/dist/lib/defaults/table.d.ts +18 -0
  40. package/dist/lib/defaults/table.d.ts.map +1 -0
  41. package/dist/lib/defaults/table.js +19 -0
  42. package/dist/lib/defaults/table.js.map +1 -0
  43. package/dist/lib/highlighter.d.ts +81 -0
  44. package/dist/lib/highlighter.d.ts.map +1 -0
  45. package/dist/lib/highlighter.js +421 -0
  46. package/dist/lib/highlighter.js.map +1 -0
  47. package/dist/lib/hook-utils.d.ts +32 -0
  48. package/dist/lib/hook-utils.d.ts.map +1 -0
  49. package/dist/lib/hook-utils.js +42 -0
  50. package/dist/lib/hook-utils.js.map +1 -0
  51. package/dist/lib/html.d.ts +2 -0
  52. package/dist/lib/html.d.ts.map +1 -0
  53. package/dist/lib/html.js +12 -0
  54. package/dist/lib/html.js.map +1 -0
  55. package/dist/lib/morph.d.ts +57 -0
  56. package/dist/lib/morph.d.ts.map +1 -0
  57. package/dist/lib/morph.js +204 -0
  58. package/dist/lib/morph.js.map +1 -0
  59. package/dist/lib/parser.d.ts +32 -0
  60. package/dist/lib/parser.d.ts.map +1 -0
  61. package/dist/lib/parser.js +645 -0
  62. package/dist/lib/parser.js.map +1 -0
  63. package/dist/lib/wasm-init.d.ts +33 -0
  64. package/dist/lib/wasm-init.d.ts.map +1 -0
  65. package/dist/lib/wasm-init.js +69 -0
  66. package/dist/lib/wasm-init.js.map +1 -0
  67. package/dist/styles/alerts.css +294 -0
  68. package/dist/styles/dotted.svg +3 -0
  69. package/dist/styles/hljs.css +332 -0
  70. package/dist/styles/index.css +17 -0
  71. package/dist/styles/katex.css +74 -0
  72. package/dist/styles/viewer.css +975 -0
  73. package/dist/types/hooks.d.ts +207 -0
  74. package/dist/types/hooks.d.ts.map +1 -0
  75. package/dist/types/hooks.js +7 -0
  76. package/dist/types/hooks.js.map +1 -0
  77. package/dist/useMarkdownViewer.d.ts +18 -0
  78. package/dist/useMarkdownViewer.d.ts.map +1 -0
  79. package/dist/useMarkdownViewer.js +403 -0
  80. package/dist/useMarkdownViewer.js.map +1 -0
  81. package/dist/utils.d.ts +20 -0
  82. package/dist/utils.d.ts.map +1 -0
  83. package/dist/utils.js +18 -0
  84. package/dist/utils.js.map +1 -0
  85. package/package.json +78 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Janos Veres
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,217 @@
1
+ <p align="center">
2
+ <img src="./assets/logo.png" alt="react-markdown-viewer" width="80" />
3
+ </p>
4
+
5
+ <h1 align="center">@typefm/react-markdown-viewer</h1>
6
+
7
+ <p align="center">
8
+ <strong>High-performance React component for rendering markdown with streaming support</strong>
9
+ <br />
10
+ <em>GitHub-style rendering optimized for LLM chat interfaces</em>
11
+ </p>
12
+
13
+ <p align="center">
14
+ <img src="https://img.shields.io/badge/react-19+-087ea4" alt="React 19+" />
15
+ <img src="https://img.shields.io/badge/typescript-5+-3178c6" alt="TypeScript" />
16
+ <img src="https://img.shields.io/badge/license-MIT-green" alt="MIT License" />
17
+ <img src="https://img.shields.io/badge/styling-GitHub%20Primer-24292f" alt="GitHub Primer" />
18
+ </p>
19
+
20
+ <p align="center">
21
+ <a href="#features">Features</a> •
22
+ <a href="#installation">Installation</a> •
23
+ <a href="#usage">Usage</a> •
24
+ <a href="#security">Security</a> •
25
+ <a href="#demo">Demo</a>
26
+ </p>
27
+
28
+ ---
29
+
30
+ ## Features
31
+
32
+ - **GitHub-Style Rendering** — Visual parity with GitHub's markdown using Primer design tokens
33
+ - **Streaming Support** — Optimized for real-time LLM output with adaptive throttling and DOM morphing
34
+ - **Element-Level Diffing** — Skips unchanged blocks during streaming (~99% skip rate)
35
+ - **Syntax Highlighting** — GitHub Prettylights theme, 40+ languages via highlight.js with dynamic loading
36
+ - **Math Rendering** — KaTeX with lazy loading
37
+ - **Color Previews** — Auto-detects hex, rgb, hsl colors and shows visual swatches
38
+ - **GitHub Flavored Markdown** — Tables, task lists, strikethrough, alerts, footnotes
39
+ - **Smart Caching** — LRU caches for rendered content, syntax highlighting, and KaTeX output
40
+ - **Cursor Animation** — Blinking cursor during streaming with focus-aware styling
41
+ - **Dark Mode** — CSS-based theming (Tailwind `.dark`, `data-theme`, or OS preference)
42
+ - **Error Boundary** — Built-in error handling with customizable fallback UI
43
+ - **Markdown Healing** — `healMarkdown()` closes unclosed delimiters during streaming
44
+
45
+ ## Architecture
46
+
47
+ See **[docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md)** for the full internal architecture:
48
+
49
+ - Rendering pipeline (markdown → WASM parse → sanitize → post-process → DOM)
50
+ - Dual rendering modes (static `dangerouslySetInnerHTML` vs streaming Idiomorph morphing)
51
+ - WASM initialization, streaming throttling, cursor system
52
+ - Caching strategy, hook system, lazy loading
53
+
54
+ ```
55
+ Markdown → comrak-wasm → HTML → Sanitization → Post-Processing → DOM
56
+ │ │
57
+ GFM Extensions • KaTeX math
58
+ • Tables • Syntax highlighting
59
+ • Task lists • Color previews
60
+ • Alerts • Link security
61
+ • Math syntax • Copy buttons
62
+ ```
63
+
64
+ ## Installation
65
+
66
+ ```bash
67
+ pnpm add @typefm/react-markdown-viewer
68
+ ```
69
+
70
+ Requires React 19:
71
+
72
+ ```json
73
+ {
74
+ "peerDependencies": {
75
+ "react": "^19.0.0",
76
+ "react-dom": "^19.0.0"
77
+ }
78
+ }
79
+ ```
80
+
81
+ ## Usage
82
+
83
+ ```tsx
84
+ import { initMarkdownViewer, MarkdownViewer } from '@typefm/react-markdown-viewer';
85
+ import '@typefm/react-markdown-viewer/styles.css';
86
+ import 'katex/dist/katex.min.css';
87
+
88
+ // Initialize WASM once at app startup
89
+ await initMarkdownViewer();
90
+
91
+ function ChatMessage({ content, isStreaming }) {
92
+ return (
93
+ <MarkdownViewer
94
+ text={content}
95
+ isStreaming={isStreaming}
96
+ throttleMs={50}
97
+ />
98
+ );
99
+ }
100
+ ```
101
+
102
+ ### With Error Boundary
103
+
104
+ ```tsx
105
+ import { MarkdownViewerSafe } from '@typefm/react-markdown-viewer';
106
+
107
+ <MarkdownViewerSafe
108
+ text={content}
109
+ isStreaming={isStreaming}
110
+ fallback={<div>Failed to render markdown</div>}
111
+ onError={(error) => console.error(error)}
112
+ />
113
+ ```
114
+
115
+ ## Props
116
+
117
+ | Prop | Type | Default | Description |
118
+ |------|------|---------|-------------|
119
+ | `text` | `string` | `''` | Markdown source text |
120
+ | `isStreaming` | `boolean` | `false` | Enable streaming mode (throttling + cursor) |
121
+ | `throttleMs` | `number` | `50` | Minimum ms between updates during streaming |
122
+ | `className` | `string` | — | Additional CSS class names |
123
+ | `onStreamingEnd` | `() => void` | — | Callback when streaming ends |
124
+ | `hooks` | `RenderHooks` | — | Customize element rendering |
125
+
126
+ ## Render Hooks
127
+
128
+ Customize how specific markdown elements are rendered:
129
+
130
+ ```tsx
131
+ import { MarkdownViewer, escapeHtml, type RenderHooks } from '@typefm/react-markdown-viewer';
132
+
133
+ const hooks: RenderHooks = {
134
+ onCodeBlock: ({ code, language }) => {
135
+ if (language === 'mermaid') {
136
+ return `<div class="mermaid">${escapeHtml(code)}</div>`;
137
+ }
138
+ return null; // use default
139
+ },
140
+ onLink: ({ href, text }) => {
141
+ if (href.includes('youtube.com')) {
142
+ return `<div class="youtube-embed">...</div>`;
143
+ }
144
+ return null; // use default
145
+ },
146
+ onRender: (html) => html.replace(/TODO/g, '<mark>TODO</mark>'),
147
+ };
148
+
149
+ <MarkdownViewer text={content} hooks={hooks} />
150
+ ```
151
+
152
+ **Return `string`** for custom HTML, **`null`** to use the default processor.
153
+
154
+ Available hooks: `onCodeBlock`, `onInlineCode`, `onMath`, `onTable`, `onLink`, `onImage`, `onHeading`, `onBlockquote`, `onAlert`, `onList`, `onHorizontalRule`, `onFootnoteRef`, `onFootnoteDef`, `onRender`.
155
+
156
+ ## Security
157
+
158
+ See **[docs/SECURITY.md](./docs/SECURITY.md)** for complete security documentation.
159
+
160
+ 7-layer defense-in-depth against XSS:
161
+
162
+ 1. **Comrak tagfilter** — escapes `<script>`, `<iframe>`, `<style>`, etc.
163
+ 2. **Tag removal** — removes `<object>`, `<embed>`, `<form>`, `<meta>`, etc.
164
+ 3. **Tag unwrapping** — strips `<noscript>`, `<template>` (keeps content)
165
+ 4. **Event handler filter** — strips all `on*` attributes
166
+ 5. **URL sanitizer** — blocks `javascript:`, `vbscript:`, `data:`, `file:` schemes
167
+ 6. **DOM clobbering filter** — removes dangerous `name`/`id` values
168
+ 7. **SVG sanitizer** — removes SVG with embedded scripts or event handler injection
169
+
170
+ ```tsx
171
+ // Safe to render untrusted content
172
+ <MarkdownViewer text={userInput} />
173
+ ```
174
+
175
+ ## Styling
176
+
177
+ See **[docs/STYLING.md](./docs/STYLING.md)** for complete styling documentation.
178
+
179
+ ```tsx
180
+ import '@typefm/react-markdown-viewer/styles.css';
181
+ ```
182
+
183
+ Dark mode works automatically via `.dark` class, `data-theme="dark"`, or OS preference. Override CSS custom properties on `.markdown-viewer` to customize colors, fonts, and cursor.
184
+
185
+ ## Demo
186
+
187
+ ```bash
188
+ cd packages/react-markdown-viewer
189
+ pnpm install
190
+ pnpm dev
191
+ ```
192
+
193
+ The playground includes:
194
+
195
+ - **Playground** — Full showcase, AI chat simulation, stress test, edge cases with custom markdown input
196
+ - **GitHub Comparison** — Side-by-side rendering comparison with GitHub's Markdown API
197
+
198
+ ## Testing
199
+
200
+ ```bash
201
+ pnpm test
202
+ ```
203
+
204
+ Test coverage includes: parser rendering, XSS prevention, streaming edge cases, DOM morphing, syntax highlighting, KaTeX lazy loading, component API, error boundary, cursor placement, and DoS prevention.
205
+
206
+ ## Dependencies
207
+
208
+ | Package | Purpose | Loading |
209
+ |---------|---------|---------|
210
+ | `@typefm/comrak-wasm` | Markdown parser (Rust/WASM) | Requires init |
211
+ | `highlight.js` | Syntax highlighting | Core bundled, languages lazy loaded |
212
+ | `katex` | Math rendering | Lazy loaded |
213
+ | `idiomorph` | DOM morphing | Bundled |
214
+
215
+ ## License
216
+
217
+ MIT
@@ -0,0 +1,25 @@
1
+ import { Component, type ReactNode, type ErrorInfo } from 'react';
2
+ export interface ErrorBoundaryProps {
3
+ /** Content to render */
4
+ children: ReactNode;
5
+ /** Fallback UI when an error occurs */
6
+ fallback?: ReactNode;
7
+ /** Callback when an error is caught */
8
+ onError?: (error: Error, errorInfo: ErrorInfo) => void;
9
+ }
10
+ interface ErrorBoundaryState {
11
+ hasError: boolean;
12
+ error: Error | null;
13
+ }
14
+ /**
15
+ * Error boundary for MarkdownViewer
16
+ * Catches rendering errors and displays fallback UI
17
+ */
18
+ export declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
19
+ constructor(props: ErrorBoundaryProps);
20
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState;
21
+ componentDidCatch(error: Error, errorInfo: ErrorInfo): void;
22
+ render(): ReactNode;
23
+ }
24
+ export {};
25
+ //# sourceMappingURL=ErrorBoundary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErrorBoundary.d.ts","sourceRoot":"","sources":["../src/ErrorBoundary.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAElE,MAAM,WAAW,kBAAkB;IACjC,wBAAwB;IACxB,QAAQ,EAAE,SAAS,CAAC;IACpB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,uCAAuC;IACvC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,KAAK,IAAI,CAAC;CACxD;AAED,UAAU,kBAAkB;IAC1B,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED;;;GAGG;AACH,qBAAa,aAAc,SAAQ,SAAS,CAAC,kBAAkB,EAAE,kBAAkB,CAAC;gBACtE,KAAK,EAAE,kBAAkB;IAKrC,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,KAAK,GAAG,kBAAkB;IAIjE,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,GAAG,IAAI;IAI3D,MAAM,IAAI,SAAS;CAgBpB"}
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Component } from 'react';
3
+ /**
4
+ * Error boundary for MarkdownViewer
5
+ * Catches rendering errors and displays fallback UI
6
+ */
7
+ export class ErrorBoundary extends Component {
8
+ constructor(props) {
9
+ super(props);
10
+ this.state = { hasError: false, error: null };
11
+ }
12
+ static getDerivedStateFromError(error) {
13
+ return { hasError: true, error };
14
+ }
15
+ componentDidCatch(error, errorInfo) {
16
+ this.props.onError?.(error, errorInfo);
17
+ }
18
+ render() {
19
+ if (this.state.hasError) {
20
+ if (this.props.fallback !== undefined) {
21
+ return this.props.fallback;
22
+ }
23
+ // Default fallback
24
+ return (_jsx("div", { className: "markdown-viewer-error", role: "alert", children: _jsx("p", { children: "Failed to render markdown" }) }));
25
+ }
26
+ return this.props.children;
27
+ }
28
+ }
29
+ //# sourceMappingURL=ErrorBoundary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErrorBoundary.js","sourceRoot":"","sources":["../src/ErrorBoundary.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAkC,MAAM,OAAO,CAAC;AAgBlE;;;GAGG;AACH,MAAM,OAAO,aAAc,SAAQ,SAAiD;IAClF,YAAY,KAAyB;QACnC,KAAK,CAAC,KAAK,CAAC,CAAC;QACb,IAAI,CAAC,KAAK,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAChD,CAAC;IAED,MAAM,CAAC,wBAAwB,CAAC,KAAY;QAC1C,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACnC,CAAC;IAED,iBAAiB,CAAC,KAAY,EAAE,SAAoB;QAClD,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IACzC,CAAC;IAED,MAAM;QACJ,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;YACxB,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBACtC,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;YAC7B,CAAC;YAED,mBAAmB;YACnB,OAAO,CACL,cAAK,SAAS,EAAC,uBAAuB,EAAC,IAAI,EAAC,OAAO,YACjD,oDAAgC,GAC5B,CACP,CAAC;QACJ,CAAC;QAED,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;IAC7B,CAAC;CACF"}
@@ -0,0 +1,41 @@
1
+ import { type ReactNode, type ComponentProps, type Ref } from 'react';
2
+ import type { RenderHooks } from './types/hooks';
3
+ export interface MarkdownViewerRef {
4
+ /** Reset the component state (call when switching content sources) */
5
+ reset: () => void;
6
+ /** Get the container element */
7
+ getContainer: () => HTMLDivElement | null;
8
+ /** Focus the viewer (activates cursor) */
9
+ focus: () => void;
10
+ }
11
+ export interface MarkdownViewerProps extends Omit<ComponentProps<'div'>, 'children' | 'ref'> {
12
+ /** Markdown source text */
13
+ text: string;
14
+ /** Enable streaming mode (throttling + cursor) */
15
+ isStreaming?: boolean;
16
+ /** Minimum ms between updates during streaming (default: 50) */
17
+ throttleMs?: number;
18
+ /** Callback when streaming ends */
19
+ onStreamingEnd?: () => void;
20
+ /** Ref to access imperative methods (React 19+) */
21
+ ref?: Ref<MarkdownViewerRef>;
22
+ /** Hooks for customizing markdown rendering */
23
+ hooks?: RenderHooks;
24
+ }
25
+ /**
26
+ * High-performance React component for rendering markdown with streaming support,
27
+ * optimized for LLM chat interfaces.
28
+ */
29
+ export declare const MarkdownViewer: import("react").NamedExoticComponent<MarkdownViewerProps>;
30
+ export interface MarkdownViewerSafeProps extends Omit<MarkdownViewerProps, 'onError'> {
31
+ /** Fallback UI when an error occurs */
32
+ fallback?: ReactNode;
33
+ /** Callback when a rendering error is caught by the error boundary */
34
+ onError?: (error: Error) => void;
35
+ }
36
+ /**
37
+ * MarkdownViewer wrapped with an error boundary.
38
+ * Catches rendering errors and displays fallback UI instead of crashing.
39
+ */
40
+ export declare const MarkdownViewerSafe: import("react").NamedExoticComponent<MarkdownViewerSafeProps>;
41
+ //# sourceMappingURL=MarkdownViewer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MarkdownViewer.d.ts","sourceRoot":"","sources":["../src/MarkdownViewer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAqC,KAAK,SAAS,EAAE,KAAK,cAAc,EAAE,KAAK,GAAG,EAAE,MAAM,OAAO,CAAC;AAGzG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,MAAM,WAAW,iBAAiB;IAChC,sEAAsE;IACtE,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,gCAAgC;IAChC,YAAY,EAAE,MAAM,cAAc,GAAG,IAAI,CAAC;IAC1C,0CAA0C;IAC1C,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED,MAAM,WAAW,mBAAoB,SAAQ,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,KAAK,CAAC;IAC1F,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,mDAAmD;IACnD,GAAG,CAAC,EAAE,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAC7B,+CAA+C;IAC/C,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,2DAuFzB,CAAC;AAEH,MAAM,WAAW,uBAAwB,SAAQ,IAAI,CAAC,mBAAmB,EAAE,SAAS,CAAC;IACnF,uCAAuC;IACvC,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,sEAAsE;IACtE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB,+DAU7B,CAAC"}
@@ -0,0 +1,69 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useImperativeHandle, memo, useRef } from 'react';
3
+ import { useMarkdownViewer } from './useMarkdownViewer';
4
+ import { ErrorBoundary } from './ErrorBoundary';
5
+ /**
6
+ * High-performance React component for rendering markdown with streaming support,
7
+ * optimized for LLM chat interfaces.
8
+ */
9
+ export const MarkdownViewer = memo(function MarkdownViewer({ text, isStreaming = false, throttleMs = 50, className, onStreamingEnd, ref, onClick, hooks, ...props }) {
10
+ const { containerRef, syncMorphEnabled, getRenderedContent, handleClick, reset, } = useMarkdownViewer({
11
+ text,
12
+ isStreaming,
13
+ throttleMs,
14
+ onStreamingEnd,
15
+ hooks,
16
+ });
17
+ const wrapperRef = useRef(null);
18
+ // Expose imperative methods
19
+ useImperativeHandle(ref, () => ({
20
+ reset,
21
+ getContainer: () => containerRef.current,
22
+ focus: () => wrapperRef.current?.focus(),
23
+ }), [reset]);
24
+ // Build className without array allocation
25
+ let rootClassName = 'markdown-viewer';
26
+ if (className)
27
+ rootClassName += ' ' + className;
28
+ // Merge click handlers
29
+ const handleRootClick = (e) => {
30
+ handleClick(e);
31
+ onClick?.(e);
32
+ };
33
+ // Shared props for both render paths
34
+ const rootProps = {
35
+ ref: wrapperRef,
36
+ className: rootClassName,
37
+ onClick: handleRootClick,
38
+ tabIndex: 0,
39
+ // Data attributes for state-based styling
40
+ 'data-slot': 'markdown-viewer',
41
+ 'data-state': isStreaming ? 'streaming' : 'idle',
42
+ // ARIA attributes for accessibility
43
+ role: 'article',
44
+ 'aria-label': props['aria-label'] ?? 'Markdown content',
45
+ 'aria-busy': isStreaming,
46
+ ...props,
47
+ };
48
+ /*
49
+ * If `syncMorphEnabled` is TRUE (Streaming or Finished Streaming):
50
+ * We use DOM morphing via `morphContent`. This patches the DOM intelligently.
51
+ * It prevents full re-renders and preserves selection.
52
+ * The content is rendered empty initially and morphed in the hook.
53
+ *
54
+ * If `syncMorphEnabled` is FALSE (Initial Static Load):
55
+ * We use dangerouslySetInnerHTML for speed.
56
+ */
57
+ if (syncMorphEnabled) {
58
+ return (_jsx("div", { ...rootProps, children: _jsx("div", { ref: containerRef, className: "markdown", "data-slot": "markdown-content" }) }));
59
+ }
60
+ return (_jsx("div", { ...rootProps, children: _jsx("div", { ref: containerRef, className: "markdown", "data-slot": "markdown-content", dangerouslySetInnerHTML: { __html: getRenderedContent() } }) }));
61
+ });
62
+ /**
63
+ * MarkdownViewer wrapped with an error boundary.
64
+ * Catches rendering errors and displays fallback UI instead of crashing.
65
+ */
66
+ export const MarkdownViewerSafe = memo(function MarkdownViewerSafe({ fallback, onError, ...props }) {
67
+ return (_jsx(ErrorBoundary, { fallback: fallback, onError: onError, children: _jsx(MarkdownViewer, { ...props }) }));
68
+ });
69
+ //# sourceMappingURL=MarkdownViewer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MarkdownViewer.js","sourceRoot":"","sources":["../src/MarkdownViewer.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,EAAiD,MAAM,OAAO,CAAC;AACzG,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AA2BhD;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAC,SAAS,cAAc,CAAC,EACzD,IAAI,EACJ,WAAW,GAAG,KAAK,EACnB,UAAU,GAAG,EAAE,EACf,SAAS,EACT,cAAc,EACd,GAAG,EACH,OAAO,EACP,KAAK,EACL,GAAG,KAAK,EACY;IACpB,MAAM,EACJ,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,WAAW,EACX,KAAK,GACN,GAAG,iBAAiB,CAAC;QACpB,IAAI;QACJ,WAAW;QACX,UAAU;QACV,cAAc;QACd,KAAK;KACN,CAAC,CAAC;IAEH,MAAM,UAAU,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAEhD,4BAA4B;IAC5B,mBAAmB,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;QAC9B,KAAK;QACL,YAAY,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO;QACxC,KAAK,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,KAAK,EAAE;KACzC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAEb,2CAA2C;IAC3C,IAAI,aAAa,GAAG,iBAAiB,CAAC;IACtC,IAAI,SAAS;QAAE,aAAa,IAAI,GAAG,GAAG,SAAS,CAAC;IAEhD,uBAAuB;IACvB,MAAM,eAAe,GAAG,CAAC,CAAmC,EAAE,EAAE;QAC9D,WAAW,CAAC,CAAC,CAAC,CAAC;QACf,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;IACf,CAAC,CAAC;IAEF,qCAAqC;IACrC,MAAM,SAAS,GAAG;QAChB,GAAG,EAAE,UAAU;QACf,SAAS,EAAE,aAAa;QACxB,OAAO,EAAE,eAAe;QACxB,QAAQ,EAAE,CAAC;QACX,0CAA0C;QAC1C,WAAW,EAAE,iBAAiB;QAC9B,YAAY,EAAE,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM;QAChD,oCAAoC;QACpC,IAAI,EAAE,SAAkB;QACxB,YAAY,EAAE,KAAK,CAAC,YAAY,CAAC,IAAI,kBAAkB;QACvD,WAAW,EAAE,WAAW;QACxB,GAAG,KAAK;KACT,CAAC;IAEF;;;;;;;;OAQG;IACH,IAAI,gBAAgB,EAAE,CAAC;QACrB,OAAO,CACL,iBAAS,SAAS,YAChB,cAAK,GAAG,EAAE,YAAY,EAAE,SAAS,EAAC,UAAU,eAAW,kBAAkB,GAAG,GACxE,CACP,CAAC;IACJ,CAAC;IAED,OAAO,CACL,iBAAS,SAAS,YAChB,cACE,GAAG,EAAE,YAAY,EACjB,SAAS,EAAC,UAAU,eACV,kBAAkB,EAC5B,uBAAuB,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,EAAE,GACzD,GACE,CACP,CAAC;AACJ,CAAC,CAAC,CAAC;AASH;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC,SAAS,kBAAkB,CAAC,EACjE,QAAQ,EACR,OAAO,EACP,GAAG,KAAK,EACgB;IACxB,OAAO,CACL,KAAC,aAAa,IAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,YACjD,KAAC,cAAc,OAAK,KAAK,GAAI,GACf,CACjB,CAAC;AACJ,CAAC,CAAC,CAAC"}
@@ -0,0 +1,17 @@
1
+ export { MarkdownViewer, MarkdownViewerSafe } from "./MarkdownViewer";
2
+ export type { MarkdownViewerProps, MarkdownViewerRef, MarkdownViewerSafeProps, } from "./MarkdownViewer";
3
+ export { ErrorBoundary } from "./ErrorBoundary";
4
+ export type { ErrorBoundaryProps } from "./ErrorBoundary";
5
+ export { useMarkdownViewer } from "./useMarkdownViewer";
6
+ export type { UseMarkdownViewerOptions } from "./useMarkdownViewer";
7
+ export { initMarkdownViewer, initMarkdownViewerSync, isWasmReady, } from "./lib/wasm-init";
8
+ export type { RenderHooks, HookResult, CodeBlockData, InlineCodeData, MathData, TableData, LinkData, ImageData, HeadingData, BlockquoteData, AlertData, AlertType, ListData, HorizontalRuleData, FootnoteRefData, FootnoteDefData, } from "./types/hooks";
9
+ export { processCodeBlock, processInlineCode, injectColorPreviews, type CodeBlockOptions, type InlineCodeOptions, } from "./lib/defaults/code-block";
10
+ export { processMathBlock } from "./lib/defaults/math";
11
+ export { processTable } from "./lib/defaults/table";
12
+ export { processLink, isDangerousUrl, isExternalUrl, } from "./lib/defaults/link";
13
+ export { processImage, type ImageOptions } from "./lib/defaults/image";
14
+ export { preloadKaTeX } from "./lib/parser";
15
+ export { escapeHtml } from "./lib/hook-utils";
16
+ export { loadLanguage, loadLanguages, registerLanguage, isLanguageReady, isLanguageSupported, getRegisteredLanguages, getSupportedLanguages, } from "./lib/highlighter";
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtE,YAAY,EACX,mBAAmB,EACnB,iBAAiB,EACjB,uBAAuB,GACvB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAG1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,YAAY,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAGpE,OAAO,EACN,kBAAkB,EAClB,sBAAsB,EACtB,WAAW,GACX,MAAM,iBAAiB,CAAC;AAGzB,YAAY,EACX,WAAW,EACX,UAAU,EACV,aAAa,EACb,cAAc,EACd,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,SAAS,EACT,WAAW,EACX,cAAc,EACd,SAAS,EACT,SAAS,EACT,QAAQ,EACR,kBAAkB,EAClB,eAAe,EACf,eAAe,GACf,MAAM,eAAe,CAAC;AAGvB,OAAO,EACN,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,GACtB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EACN,WAAW,EACX,cAAc,EACd,aAAa,GACb,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGvE,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG5C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9C,OAAO,EACN,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,GACrB,MAAM,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ // Main component
2
+ export { MarkdownViewer, MarkdownViewerSafe } from "./MarkdownViewer";
3
+ // Error boundary (for custom error handling)
4
+ export { ErrorBoundary } from "./ErrorBoundary";
5
+ // Hook for custom implementations
6
+ export { useMarkdownViewer } from "./useMarkdownViewer";
7
+ // WASM initialization
8
+ export { initMarkdownViewer, initMarkdownViewerSync, isWasmReady, } from "./lib/wasm-init";
9
+ // Default processors (for extending/wrapping)
10
+ export { processCodeBlock, processInlineCode, injectColorPreviews, } from "./lib/defaults/code-block";
11
+ export { processMathBlock } from "./lib/defaults/math";
12
+ export { processTable } from "./lib/defaults/table";
13
+ export { processLink, isDangerousUrl, isExternalUrl, } from "./lib/defaults/link";
14
+ export { processImage } from "./lib/defaults/image";
15
+ // Preload utilities for performance optimization
16
+ export { preloadKaTeX } from "./lib/parser";
17
+ // Hook utilities
18
+ export { escapeHtml } from "./lib/hook-utils";
19
+ // Syntax highlighting utilities
20
+ export { loadLanguage, loadLanguages, registerLanguage, isLanguageReady, isLanguageSupported, getRegisteredLanguages, getSupportedLanguages, } from "./lib/highlighter";
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,iBAAiB;AACjB,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAOtE,6CAA6C;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGhD,kCAAkC;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAGxD,sBAAsB;AACtB,OAAO,EACN,kBAAkB,EAClB,sBAAsB,EACtB,WAAW,GACX,MAAM,iBAAiB,CAAC;AAsBzB,8CAA8C;AAC9C,OAAO,EACN,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,GAGnB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EACN,WAAW,EACX,cAAc,EACd,aAAa,GACb,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,YAAY,EAAqB,MAAM,sBAAsB,CAAC;AAEvE,iDAAiD;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,iBAAiB;AACjB,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,gCAAgC;AAChC,OAAO,EACN,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,GACrB,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Unified LRU Cache Manager
3
+ * Coordinates memory usage across all caches in the markdown-viewer
4
+ */
5
+ export interface CacheStats {
6
+ entries: number;
7
+ estimatedBytes: number;
8
+ }
9
+ declare class LRUCache<V> {
10
+ private cache;
11
+ private sizeEstimates;
12
+ private totalBytes;
13
+ private maxEntries;
14
+ private maxBytes;
15
+ constructor(maxEntries: number, maxBytes?: number);
16
+ get(key: string): V | undefined;
17
+ set(key: string, value: V, sizeBytes?: number): void;
18
+ has(key: string): boolean;
19
+ clear(): void;
20
+ evictOldest(count: number): void;
21
+ get stats(): CacheStats;
22
+ private estimateSize;
23
+ }
24
+ /**
25
+ * Global cache manager instance
26
+ * Coordinates memory across render cache, katex cache, and morph cache
27
+ */
28
+ declare class CacheManager {
29
+ private memoryBudget;
30
+ readonly renderCacheSync: LRUCache<string>;
31
+ readonly renderCacheAsync: LRUCache<string>;
32
+ readonly katexCacheDisplay: LRUCache<string>;
33
+ readonly katexCacheInline: LRUCache<string>;
34
+ readonly highlightCache: LRUCache<string>;
35
+ constructor(memoryBudget?: number);
36
+ /**
37
+ * Get combined stats for all caches
38
+ */
39
+ get stats(): {
40
+ renderSync: CacheStats;
41
+ renderAsync: CacheStats;
42
+ katexDisplay: CacheStats;
43
+ katexInline: CacheStats;
44
+ highlight: CacheStats;
45
+ total: CacheStats;
46
+ };
47
+ /**
48
+ * Clear all caches
49
+ */
50
+ clearAll(): void;
51
+ /**
52
+ * Trim caches if under memory pressure
53
+ * Evicts ~25% of entries from each cache when budget exceeded
54
+ */
55
+ trimIfNeeded(): void;
56
+ }
57
+ export declare const cacheManager: CacheManager;
58
+ export type { LRUCache };
59
+ //# sourceMappingURL=cache-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-manager.d.ts","sourceRoot":"","sources":["../../src/lib/cache-manager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,cAAM,QAAQ,CAAC,CAAC;IACd,OAAO,CAAC,KAAK,CAAwB;IACrC,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAS;gBAEb,UAAU,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAiB;IAK3D,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAU/B,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IA6BpD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIzB,KAAK,IAAI,IAAI;IAMb,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAWhC,IAAI,KAAK,IAAI,UAAU,CAKtB;IAED,OAAO,CAAC,YAAY;CAMrB;AAKD;;;GAGG;AACH,cAAM,YAAY;IAChB,OAAO,CAAC,YAAY,CAAS;IAI7B,QAAQ,CAAC,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC3C,QAAQ,CAAC,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAE5C,QAAQ,CAAC,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC7C,QAAQ,CAAC,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAE5C,QAAQ,CAAC,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;gBAE9B,YAAY,SAAwB;IAYhD;;OAEG;IACH,IAAI,KAAK,IAAI;QAAE,UAAU,EAAE,UAAU,CAAC;QAAC,WAAW,EAAE,UAAU,CAAC;QAAC,YAAY,EAAE,UAAU,CAAC;QAAC,WAAW,EAAE,UAAU,CAAC;QAAC,SAAS,EAAE,UAAU,CAAC;QAAC,KAAK,EAAE,UAAU,CAAA;KAAE,CAkB5J;IAED;;OAEG;IACH,QAAQ,IAAI,IAAI;IAQhB;;;OAGG;IACH,YAAY,IAAI,IAAI;CAiBrB;AAGD,eAAO,MAAM,YAAY,cAAqB,CAAC;AAG/C,YAAY,EAAE,QAAQ,EAAE,CAAC"}