react-markdown-typer 1.0.3 → 1.0.4-beta.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.
- package/README.md +238 -729
- package/README.zh.md +238 -699
- package/es/MarkdownTyperCMD/index.d.ts.map +1 -1
- package/es/MarkdownTyperCMD/index.js +75 -23
- package/es/MarkdownTyperCMD/index.js.map +1 -1
- package/es/components/CursorSpan.d.ts +14 -0
- package/es/components/CursorSpan.d.ts.map +1 -0
- package/es/components/CursorSpan.js +19 -0
- package/es/components/CursorSpan.js.map +1 -0
- package/es/constant.d.ts +1 -1
- package/es/constant.d.ts.map +1 -1
- package/es/constant.js +1 -1
- package/es/constant.js.map +1 -1
- package/es/defined.d.ts +30 -26
- package/es/defined.d.ts.map +1 -1
- package/es/hooks/useTypingTask.d.ts +3 -1
- package/es/hooks/useTypingTask.d.ts.map +1 -1
- package/es/hooks/useTypingTask.js +46 -45
- package/es/hooks/useTypingTask.js.map +1 -1
- package/es/index.d.ts +2 -1
- package/es/index.d.ts.map +1 -1
- package/es/index.js +2 -1
- package/es/index.js.map +1 -1
- package/es/plugins/rehypeCursor.d.ts +8 -0
- package/es/plugins/rehypeCursor.d.ts.map +1 -0
- package/es/plugins/rehypeCursor.js +54 -0
- package/es/plugins/rehypeCursor.js.map +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# react-markdown-typer
|
|
2
2
|
|
|
3
|
-
> 🚀 React Markdown
|
|
3
|
+
> 🚀 React Markdown Typing Animation Component
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**If you need styling, support for math formulas, and mermaid chart rendering, we recommend [ds-markdown](https://github.com/onshinpei/ds-markdown)**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**🇨🇳 [中文](./README.zh.md) | 🇺🇸 English**
|
|
8
8
|
|
|
9
|
-
A React component designed for modern AI applications, providing smooth real-time typing
|
|
9
|
+
A React component designed for modern AI applications, providing smooth real-time typing animations and full Markdown rendering capabilities.
|
|
10
10
|
|
|
11
11
|
[](https://www.npmjs.com/package/react-markdown-typer)
|
|
12
12
|
[](https://www.npmjs.com/package/react-markdown-typer)
|
|
@@ -14,856 +14,365 @@ A React component designed for modern AI applications, providing smooth real-tim
|
|
|
14
14
|
[](https://react.dev)
|
|
15
15
|
[](https://www.typescriptlang.org/)
|
|
16
16
|
|
|
17
|
-
[📖
|
|
18
|
-
|
|
19
|
-
[DEMO: 🔧 Try on StackBlitz](https://stackblitz.com/edit/vitejs-vite-zbrdonvx?file=src%2FApp.tsx)
|
|
17
|
+
[📖 Live Demo](https://onshinpei.github.io/react-markdown-typer/) | [🔧 StackBlitz](https://stackblitz.com/edit/vitejs-vite-ndgqzcbp?file=README.md)
|
|
20
18
|
|
|
21
19
|
---
|
|
22
20
|
|
|
23
|
-
##
|
|
24
|
-
|
|
25
|
-
- **Perfect for backend streaming data**
|
|
26
|
-
Many AI/LLM backend APIs (like OpenAI, DeepSeek, etc.) push data chunks containing multiple characters at once. Ordinary typewriter implementations may stutter or skip characters.
|
|
27
|
-
**react-markdown-typer automatically splits each chunk into single characters and renders them smoothly one by one, ensuring a fluid typing animation no matter how many characters are pushed at once.**
|
|
28
|
-
|
|
29
|
-
- **Ultimate developer experience**
|
|
30
|
-
Rich imperative API, supports streaming data, async callbacks, plugin extensions, and flexible animation/content control.
|
|
31
|
-
|
|
32
|
-
- **Lightweight & high performance**
|
|
33
|
-
Small bundle size, high performance, works on both mobile and desktop. Core dependency is [react-markdown](https://github.com/remarkjs/react-markdown) (industry-standard, mature Markdown renderer), no heavy dependencies, ready to use out of the box.
|
|
21
|
+
## Why choose react-markdown-typer?
|
|
34
22
|
|
|
35
|
-
|
|
36
|
-
compatible with [react-markdown](https://github.com/remarkjs/react-markdown) remark/rehype plugins for advanced customization and extension.
|
|
23
|
+
### Optimized for AI Applications
|
|
37
24
|
|
|
38
|
-
|
|
39
|
-
- AI chatbots/assistants
|
|
40
|
-
- Real-time Q&A/knowledge base
|
|
41
|
-
- Educational/math/programming content
|
|
42
|
-
- Product demos, interactive docs
|
|
43
|
-
- Any scenario needing "typewriter" animation and streaming Markdown rendering
|
|
25
|
+
Do regular typewriters stutter with AI streaming data? We don't. **Automatically splits each chunk into characters**, ensuring smooth character-by-character rendering no matter how much the backend pushes at once.
|
|
44
26
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
## 📋 Table of Contents
|
|
48
|
-
|
|
49
|
-
- [react-markdown-typer](#react-markdown-typer)
|
|
50
|
-
- [❓ Why use react-markdown-typer?](#-why-use-react-markdown-typer)
|
|
51
|
-
- [📋 Table of Contents](#-table-of-contents)
|
|
52
|
-
- [✨ Core Features](#-core-features)
|
|
53
|
-
- [🤖 **AI Chat Scenario**](#-ai-chat-scenario)
|
|
54
|
-
- [📊 **Content Display Scenario**](#-content-display-scenario)
|
|
55
|
-
- [🔧 **Developer Experience**](#-developer-experience)
|
|
56
|
-
- [🎬 **Smooth Animation**](#-smooth-animation)
|
|
57
|
-
- [📦 Quick Installation](#-quick-installation)
|
|
58
|
-
- [Use via ESM CDN](#use-via-esm-cdn)
|
|
59
|
-
- [🚀 5-Minute Quick Start](#-5-minute-quick-start)
|
|
60
|
-
- [Basic Usage](#basic-usage)
|
|
61
|
-
- [Disable Typing Animation](#disable-typing-animation)
|
|
62
|
-
- [Custom Markdown Processing](#custom-markdown-processing)
|
|
63
|
-
- [AI Chat Scenario](#ai-chat-scenario)
|
|
64
|
-
- [🎯 Advanced Callback Control](#-advanced-callback-control)
|
|
65
|
-
- [🔄 Restart Animation Demo](#-restart-animation-demo)
|
|
66
|
-
- [▶️ Manual Start Animation Demo](#️-manual-start-animation-demo)
|
|
67
|
-
- [📚 Full API Documentation](#-full-api-documentation)
|
|
68
|
-
- [Default Exported Props for MarkdownTyper and MarkdownCMD](#default-exported-props-for-markdowntyper-and-markdowncmd)
|
|
69
|
-
- [IBeforeTypedChar](#ibeforetypedchar)
|
|
70
|
-
- [ITypedChar](#itypedchar)
|
|
71
|
-
- [Custom Markdown Conversion](#custom-markdown-conversion)
|
|
72
|
-
- [IMarkdownPlugin](#imarkdownplugin)
|
|
73
|
-
- [Exposed Methods](#exposed-methods)
|
|
74
|
-
- [Default Export MarkdownTyper](#default-export-markdowntyper)
|
|
75
|
-
- [MarkdownCMD Exposed Methods](#markdowncmd-exposed-methods)
|
|
76
|
-
- [🔧 Custom Markdown Processing Guide](#-custom-markdown-processing-guide)
|
|
77
|
-
- [Basic Usage](#basic-usage-1)
|
|
78
|
-
- [Advanced Processing](#advanced-processing)
|
|
79
|
-
- [Integration with External Processors](#integration-with-external-processors)
|
|
80
|
-
- [Content Filtering](#content-filtering)
|
|
81
|
-
- [🔌 Plugin System](#-plugin-system)
|
|
82
|
-
- [🎛️ Timer Modes Explained](#️-timer-modes-explained)
|
|
83
|
-
- [`requestAnimationFrame` Mode 🌟 (Recommended)](#requestanimationframe-mode--recommended)
|
|
84
|
-
- [`setTimeout` Mode 📟 (Compatible)](#settimeout-mode--compatible)
|
|
85
|
-
- [📊 Performance Comparison](#-performance-comparison)
|
|
86
|
-
- [💡 Practical Examples](#-practical-examples)
|
|
87
|
-
- [📝 AI Streaming Chat](#-ai-streaming-chat)
|
|
88
|
-
- [🔧 Custom Markdown Processing Demo](#-custom-markdown-processing-demo)
|
|
89
|
-
- [🎯 Advanced Callback Control](#-advanced-callback-control-1)
|
|
90
|
-
|
|
91
|
-
---
|
|
27
|
+
### Lightweight yet Powerful
|
|
92
28
|
|
|
93
|
-
|
|
29
|
+
- Built on industry-standard [react-markdown](https://github.com/remarkjs/react-markdown)
|
|
30
|
+
- Zero additional dependencies, works out of the box
|
|
94
31
|
|
|
95
|
-
###
|
|
32
|
+
### Complete Typing Control
|
|
96
33
|
|
|
97
|
-
|
|
98
|
-
- Supports both `thinking` and `answer` modes
|
|
99
|
-
- Perfectly fits streaming data, zero-latency user response
|
|
34
|
+
Not just playing animations, but also **pause, resume, restart, and clear**. Full imperative API gives you complete control.
|
|
100
35
|
|
|
101
|
-
###
|
|
36
|
+
### Plugin Ecosystem Compatible
|
|
102
37
|
|
|
103
|
-
|
|
104
|
-
- Math formula rendering (KaTeX), supports `$...$` and `\[...\]` syntax
|
|
105
|
-
- Light/dark theme support for different product styles
|
|
106
|
-
- Plugin architecture, supports remark/rehype plugin extensions
|
|
38
|
+
Compatible with the entire remark/rehype plugin ecosystem, easily extend functionality. Supports code highlighting, math formulas, tables, custom cursors, and more.
|
|
107
39
|
|
|
108
|
-
###
|
|
40
|
+
### Production Ready
|
|
109
41
|
|
|
110
|
-
-
|
|
111
|
-
-
|
|
42
|
+
- Native TypeScript support
|
|
43
|
+
- Complete type definitions
|
|
112
44
|
|
|
113
|
-
|
|
45
|
+
**Use Cases**
|
|
114
46
|
|
|
115
|
-
-
|
|
116
|
-
- High-frequency typing supported (with `requestAnimationFrame`, interval can be nearly `0ms`)
|
|
117
|
-
- Frame-synced rendering, perfectly matches browser refresh
|
|
118
|
-
- Smart batch character handling for more natural visuals
|
|
47
|
+
AI Chat Assistants · Real-time Q&A Systems · Online Education Platforms · Product Demos · Interactive Documentation · Knowledge Base Display
|
|
119
48
|
|
|
120
49
|
---
|
|
121
50
|
|
|
122
|
-
##
|
|
51
|
+
## Quick Install
|
|
123
52
|
|
|
124
53
|
```bash
|
|
125
|
-
# npm
|
|
126
54
|
npm install react-markdown-typer
|
|
127
|
-
|
|
128
|
-
# yarn
|
|
129
|
-
yarn add react-markdown-typer
|
|
130
|
-
|
|
131
|
-
# pnpm
|
|
132
|
-
pnpm add react-markdown-typer
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### Use via ESM CDN
|
|
136
|
-
|
|
137
|
-
No installation needed, use directly in the browser:
|
|
138
|
-
|
|
139
|
-
<!-- [DEMO](https://stackblitz.com/edit/vitejs-vite-zbrdonvx?file=src%2FApp.tsx) -->
|
|
140
|
-
|
|
141
|
-
```html
|
|
142
|
-
<!-- Import the component -->
|
|
143
|
-
<script type="module">
|
|
144
|
-
import Markdown from 'https://esm.sh/react-markdown-typer';
|
|
145
|
-
</script>
|
|
146
55
|
```
|
|
147
56
|
|
|
148
|
-
##
|
|
57
|
+
## Quick Start
|
|
149
58
|
|
|
150
59
|
### Basic Usage
|
|
151
60
|
|
|
152
|
-
[DEMO](https://stackblitz.com/edit/vitejs-vite-z94syu8j?file=src%2FApp.tsx)
|
|
153
|
-
|
|
154
61
|
```tsx
|
|
155
62
|
import MarkdownTyper from 'react-markdown-typer';
|
|
156
63
|
|
|
157
64
|
function App() {
|
|
158
65
|
return (
|
|
159
66
|
<MarkdownTyper interval={20}>
|
|
160
|
-
# Hello
|
|
67
|
+
# Hello World
|
|
68
|
+
|
|
69
|
+
This is a **high-performance** typing animation component!
|
|
70
|
+
|
|
71
|
+
- ⚡ Smooth rendering
|
|
72
|
+
- 🎯 Perfect syntax support
|
|
161
73
|
</MarkdownTyper>
|
|
162
74
|
);
|
|
163
75
|
}
|
|
164
76
|
```
|
|
165
77
|
|
|
166
|
-
###
|
|
78
|
+
### AI Streaming Chat
|
|
167
79
|
|
|
168
80
|
```tsx
|
|
169
|
-
import
|
|
81
|
+
import { useRef, useEffect } from 'react';
|
|
82
|
+
import { MarkdownTyperCMD, MarkdownTyperCMDRef } from 'react-markdown-typer';
|
|
170
83
|
|
|
171
|
-
function
|
|
172
|
-
const
|
|
84
|
+
function ChatDemo() {
|
|
85
|
+
const cmdRef = useRef<MarkdownTyperCMDRef>(null);
|
|
86
|
+
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
// Simulate streaming data
|
|
89
|
+
async function simulateStreaming() {
|
|
90
|
+
const chunks = ['# AI Response\n\n', 'This', 'is', 'a', 'streaming', 'response'];
|
|
91
|
+
|
|
92
|
+
for (const chunk of chunks) {
|
|
93
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
94
|
+
cmdRef.current?.push(chunk);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
simulateStreaming();
|
|
99
|
+
}, []);
|
|
173
100
|
|
|
174
101
|
return (
|
|
175
|
-
<
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
# Static Display Mode When `disableTyping` is `true`, all content is shown instantly with no typing animation. This is useful for: - 📄 Static document display - 🔄 Switching display modes -
|
|
180
|
-
⚡ Quick content preview
|
|
181
|
-
</MarkdownTyper>
|
|
182
|
-
</div>
|
|
102
|
+
<MarkdownTyperCMD
|
|
103
|
+
ref={cmdRef}
|
|
104
|
+
interval={30}
|
|
105
|
+
/>
|
|
183
106
|
);
|
|
184
107
|
}
|
|
185
108
|
```
|
|
186
109
|
|
|
187
|
-
###
|
|
110
|
+
### Cursor Effect
|
|
188
111
|
|
|
189
112
|
```tsx
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
113
|
+
// String cursor
|
|
114
|
+
<MarkdownTyperCMD
|
|
115
|
+
ref={cmdRef}
|
|
116
|
+
showCursor={true}
|
|
117
|
+
cursor="|"
|
|
118
|
+
interval={50}
|
|
119
|
+
/>
|
|
120
|
+
|
|
121
|
+
// Custom ReactNode cursor
|
|
122
|
+
<MarkdownTyperCMD
|
|
123
|
+
ref={cmdRef}
|
|
124
|
+
showCursor={true}
|
|
125
|
+
cursor={
|
|
126
|
+
<span style={{
|
|
127
|
+
color: '#007acc',
|
|
128
|
+
animation: 'blink 1s infinite'
|
|
129
|
+
}}>|</span>
|
|
130
|
+
}
|
|
131
|
+
interval={50}
|
|
132
|
+
/>
|
|
207
133
|
```
|
|
208
134
|
|
|
209
|
-
###
|
|
135
|
+
### Control Animation
|
|
210
136
|
|
|
211
137
|
```tsx
|
|
212
|
-
|
|
213
|
-
const [answer, setAnswer] = useState('');
|
|
214
|
-
|
|
215
|
-
const handleAsk = () => {
|
|
216
|
-
setAnswer(`# About React 19
|
|
217
|
-
|
|
218
|
-
React 19 brings many exciting new features:
|
|
138
|
+
const cmdRef = useRef<MarkdownTyperCMDRef>(null);
|
|
219
139
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
Let's explore these new features together!`);
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
return (
|
|
229
|
-
<div>
|
|
230
|
-
<button onClick={handleAsk}>Ask AI</button>
|
|
231
|
-
|
|
232
|
-
{answer && <MarkdownTyper interval={15}>{answer}</MarkdownTyper>}
|
|
233
|
-
</div>
|
|
234
|
-
);
|
|
235
|
-
}
|
|
140
|
+
// Control methods
|
|
141
|
+
cmdRef.current?.stop(); // Pause
|
|
142
|
+
cmdRef.current?.resume(); // Resume
|
|
143
|
+
cmdRef.current?.restart(); // Restart
|
|
144
|
+
cmdRef.current?.clear(); // Clear
|
|
236
145
|
```
|
|
237
146
|
|
|
238
|
-
|
|
147
|
+
---
|
|
239
148
|
|
|
240
|
-
|
|
241
|
-
import { useRef, useState } from 'react';
|
|
242
|
-
import { MarkdownCMD, MarkdownTyperCMDRef } from 'react-markdown-typer';
|
|
149
|
+
## API Documentation
|
|
243
150
|
|
|
244
|
-
|
|
245
|
-
const markdownRef = useRef<MarkdownTyperCMDRef>(null);
|
|
246
|
-
const [typingStats, setTypingStats] = useState({ progress: 0, currentChar: '', totalChars: 0 });
|
|
151
|
+
### MarkdownTyper Props
|
|
247
152
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
153
|
+
| Property | Type | Default | Description |
|
|
154
|
+
|----------|------|---------|-------------|
|
|
155
|
+
| `children` | `string` | - | Markdown content (required) |
|
|
156
|
+
| `interval` | `number \| IntervalType` | `30` | Typing interval (milliseconds) |
|
|
157
|
+
| `timerType` | `'setTimeout' \| 'requestAnimationFrame'` | `'setTimeout'` | Timer type |
|
|
158
|
+
| `showCursor` | `boolean` | `false` | Whether to show cursor |
|
|
159
|
+
| `cursor` | `React.ReactNode` | `"\|"` | Cursor content |
|
|
160
|
+
| `disableTyping` | `boolean` | `false` | Disable typing animation |
|
|
161
|
+
| `autoStartTyping` | `boolean` | `true` | Auto start typing |
|
|
162
|
+
| `onStart` | `(data) => void` | - | Typing start callback |
|
|
163
|
+
| `onEnd` | `(data) => void` | - | Typing end callback |
|
|
164
|
+
| `onTypedChar` | `(data) => void` | - | Callback after each character |
|
|
165
|
+
| `reactMarkdownProps` | `Options` | - | react-markdown configuration |
|
|
251
166
|
|
|
252
|
-
|
|
253
|
-
if (data.currentChar === '!') {
|
|
254
|
-
await new Promise((resolve) => setTimeout(resolve, 500)); // Simulate delay
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
const handleTypedChar = (data) => {
|
|
259
|
-
// Update typing stats
|
|
260
|
-
setTypingStats({
|
|
261
|
-
progress: Math.round(data.percent),
|
|
262
|
-
currentChar: data.currentChar,
|
|
263
|
-
totalChars: data.currentIndex + 1,
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
// Add sound effects, animations, etc. here
|
|
267
|
-
if (data.currentChar === '.') {
|
|
268
|
-
// Play period sound effect
|
|
269
|
-
console.log('Play period sound');
|
|
270
|
-
}
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
const handleStart = (data) => {
|
|
274
|
-
console.log('Typing started:', data.currentChar);
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
const handleEnd = (data) => {
|
|
278
|
-
console.log('Typing finished:', data.str);
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
const startDemo = () => {
|
|
282
|
-
markdownRef.current?.clear();
|
|
283
|
-
markdownRef.current?.push(
|
|
284
|
-
'# Advanced Callback Demo\n\n' +
|
|
285
|
-
'This example shows how to use `onBeforeTypedChar` and `onTypedChar` callbacks:\n\n' +
|
|
286
|
-
'- 🎯 **Before typing callback**: Async operations before displaying a character\n' +
|
|
287
|
-
'- 📊 **After typing callback**: Real-time progress updates and effects\n' +
|
|
288
|
-
'- ⚡ **Performance**: Async operations without affecting typing smoothness\n\n' +
|
|
289
|
-
'Current progress: ' +
|
|
290
|
-
typingStats.progress +
|
|
291
|
-
'%\n' +
|
|
292
|
-
'Characters typed: ' +
|
|
293
|
-
typingStats.totalChars +
|
|
294
|
-
'\n\n' +
|
|
295
|
-
'This is a very powerful feature!',
|
|
296
|
-
'answer',
|
|
297
|
-
);
|
|
298
|
-
};
|
|
167
|
+
### MarkdownTyperCMD Props
|
|
299
168
|
|
|
300
|
-
|
|
301
|
-
<div>
|
|
302
|
-
<button onClick={startDemo}>🚀 Start Advanced Demo</button>
|
|
169
|
+
Same as `MarkdownTyper`, but without `children`.
|
|
303
170
|
|
|
304
|
-
|
|
305
|
-
<strong>Typing Stats:</strong> Progress {typingStats.progress}% | Current char: "{typingStats.currentChar}" | Total chars: {typingStats.totalChars}
|
|
306
|
-
</div>
|
|
171
|
+
### MarkdownTyper Methods
|
|
307
172
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
173
|
+
| Method | Description |
|
|
174
|
+
|--------|-------------|
|
|
175
|
+
| `start()` | Start typing animation |
|
|
176
|
+
| `stop()` | Pause typing animation |
|
|
177
|
+
| `resume()` | Resume typing animation |
|
|
178
|
+
| `restart()` | Restart animation |
|
|
313
179
|
|
|
314
|
-
###
|
|
180
|
+
### MarkdownTyperCMD Methods
|
|
315
181
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
const startContent = () => {
|
|
326
|
-
markdownRef.current?.clear();
|
|
327
|
-
markdownRef.current?.push(
|
|
328
|
-
'# Restart Animation Demo\n\n' +
|
|
329
|
-
'This example shows how to use the `restart()` method:\n\n' +
|
|
330
|
-
'- 🔄 **Restart**: Play current content from the beginning\n' +
|
|
331
|
-
'- ⏸️ **Pause/Resume**: Pause and resume anytime\n' +
|
|
332
|
-
'- 🎯 **Precise control**: Full control over animation state\n\n' +
|
|
333
|
-
'Current state: ' +
|
|
334
|
-
(isPlaying ? 'Playing' : 'Paused') +
|
|
335
|
-
'\n\n' +
|
|
336
|
-
'This is a very practical feature!',
|
|
337
|
-
'answer',
|
|
338
|
-
);
|
|
339
|
-
setIsPlaying(true);
|
|
340
|
-
};
|
|
341
|
-
|
|
342
|
-
const handleStart = () => {
|
|
343
|
-
if (hasStarted) {
|
|
344
|
-
// If already started, restart
|
|
345
|
-
markdownRef.current?.restart();
|
|
346
|
-
} else {
|
|
347
|
-
// First start
|
|
348
|
-
markdownRef.current?.start();
|
|
349
|
-
setHasStarted(true);
|
|
350
|
-
}
|
|
351
|
-
setIsPlaying(true);
|
|
352
|
-
};
|
|
182
|
+
| Method | Parameters | Description |
|
|
183
|
+
|--------|------------|-------------|
|
|
184
|
+
| `push(content)` | `string` | Add content and start typing |
|
|
185
|
+
| `clear()` | - | Clear all content and state |
|
|
186
|
+
| `start()` | - | Start typing animation |
|
|
187
|
+
| `stop()` | - | Pause typing animation |
|
|
188
|
+
| `resume()` | - | Resume typing animation |
|
|
189
|
+
| `restart()` | - | Restart animation |
|
|
353
190
|
|
|
354
|
-
|
|
355
|
-
markdownRef.current?.stop();
|
|
356
|
-
setIsPlaying(false);
|
|
357
|
-
};
|
|
191
|
+
### IntervalType
|
|
358
192
|
|
|
359
|
-
|
|
360
|
-
markdownRef.current?.resume();
|
|
361
|
-
setIsPlaying(true);
|
|
362
|
-
};
|
|
193
|
+
Supports dynamic typing speed:
|
|
363
194
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
setIsPlaying(false);
|
|
371
|
-
};
|
|
372
|
-
|
|
373
|
-
return (
|
|
374
|
-
<div>
|
|
375
|
-
<div style={{ marginBottom: '10px', display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
|
|
376
|
-
<button onClick={startContent}>🚀 Start Content</button>
|
|
377
|
-
<button onClick={handleStart} disabled={isPlaying}>
|
|
378
|
-
{hasStarted ? '🔄 Restart' : '▶️ Start'}
|
|
379
|
-
</button>
|
|
380
|
-
<button onClick={handleStop} disabled={!isPlaying}>
|
|
381
|
-
⏸️ Pause
|
|
382
|
-
</button>
|
|
383
|
-
<button onClick={handleResume} disabled={isPlaying}>
|
|
384
|
-
▶️ Resume
|
|
385
|
-
</button>
|
|
386
|
-
<button onClick={handleRestart}>🔄 Restart</button>
|
|
387
|
-
</div>
|
|
388
|
-
|
|
389
|
-
<div style={{ margin: '10px 0', padding: '10px', background: '#f5f5f5', borderRadius: '4px' }}>
|
|
390
|
-
<strong>Animation State:</strong> {isPlaying ? '🟢 Playing' : '🔴 Paused'}
|
|
391
|
-
</div>
|
|
392
|
-
|
|
393
|
-
<MarkdownCMD ref={markdownRef} interval={25} onEnd={handleEnd} />
|
|
394
|
-
</div>
|
|
395
|
-
);
|
|
195
|
+
```typescript
|
|
196
|
+
type IntervalType = number | {
|
|
197
|
+
max: number; // Maximum interval
|
|
198
|
+
min: number; // Minimum interval
|
|
199
|
+
curve?: 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out' | 'linear';
|
|
200
|
+
curveFn?: (x: number) => number; // Custom curve function
|
|
396
201
|
}
|
|
397
202
|
```
|
|
398
203
|
|
|
399
|
-
|
|
400
|
-
|
|
204
|
+
**Example**:
|
|
401
205
|
```tsx
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
markdownRef.current?.clear();
|
|
412
|
-
markdownRef.current?.push(
|
|
413
|
-
'# Manual Start Animation Demo\n\n' +
|
|
414
|
-
'This example shows how to use the `start()` method:\n\n' +
|
|
415
|
-
'- 🎯 **Manual control**: When `autoStartTyping=false`, you need to call `start()` manually\n' +
|
|
416
|
-
'- ⏱️ **Delayed start**: Start animation after user interaction\n' +
|
|
417
|
-
'- 🎮 **Gamification**: Suitable for scenarios requiring user trigger\n\n' +
|
|
418
|
-
'Click the "Start Animation" button to manually trigger typing!',
|
|
419
|
-
'answer',
|
|
420
|
-
);
|
|
421
|
-
setIsPlaying(false);
|
|
422
|
-
};
|
|
423
|
-
|
|
424
|
-
const handleStart = () => {
|
|
425
|
-
if (hasStarted) {
|
|
426
|
-
// If already started, restart
|
|
427
|
-
markdownRef.current?.restart();
|
|
428
|
-
} else {
|
|
429
|
-
// First start
|
|
430
|
-
markdownRef.current?.start();
|
|
431
|
-
setHasStarted(true);
|
|
432
|
-
}
|
|
433
|
-
setIsPlaying(true);
|
|
434
|
-
};
|
|
435
|
-
|
|
436
|
-
const handleEnd = () => {
|
|
437
|
-
setIsPlaying(false);
|
|
438
|
-
};
|
|
439
|
-
|
|
440
|
-
return (
|
|
441
|
-
<div>
|
|
442
|
-
<div style={{ marginBottom: '10px', display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
|
|
443
|
-
<button onClick={loadContent}>📝 Load Content</button>
|
|
444
|
-
<button onClick={handleStart} disabled={isPlaying}>
|
|
445
|
-
{hasStarted ? '🔄 Restart' : '▶️ Start Animation'}
|
|
446
|
-
</button>
|
|
447
|
-
</div>
|
|
448
|
-
|
|
449
|
-
<div style={{ margin: '10px 0', padding: '10px', background: '#f5f5f5', borderRadius: '4px' }}>
|
|
450
|
-
<strong>Status:</strong> {isPlaying ? '🟢 Animation Playing' : '🔴 Waiting to Start'}
|
|
451
|
-
</div>
|
|
452
|
-
|
|
453
|
-
<MarkdownCMD ref={markdownRef} interval={30} autoStartTyping={false} onEnd={handleEnd} />
|
|
454
|
-
</div>
|
|
455
|
-
);
|
|
456
|
-
}
|
|
206
|
+
<MarkdownTyper
|
|
207
|
+
interval={{
|
|
208
|
+
min: 10,
|
|
209
|
+
max: 100,
|
|
210
|
+
curve: 'ease-out' // Fast start, slow end
|
|
211
|
+
}}
|
|
212
|
+
>
|
|
213
|
+
Content...
|
|
214
|
+
</MarkdownTyper>
|
|
457
215
|
```
|
|
458
216
|
|
|
459
217
|
---
|
|
460
218
|
|
|
461
|
-
##
|
|
219
|
+
## Math Formulas
|
|
462
220
|
|
|
463
|
-
|
|
221
|
+
Install KaTeX plugins:
|
|
464
222
|
|
|
465
|
-
```
|
|
466
|
-
|
|
223
|
+
```bash
|
|
224
|
+
npm install remark-math rehype-katex katex
|
|
467
225
|
```
|
|
468
226
|
|
|
469
|
-
|
|
470
|
-
| ----------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
|
|
471
|
-
| `interval` | `number` | Typing interval (ms) | `30` |
|
|
472
|
-
| `timerType` | `'setTimeout'` \| `'requestAnimationFrame'` | Timer type, not dynamically changeable | Default is `setTimeout`, will switch to `requestAnimationFrame` in the future |
|
|
473
|
-
| `theme` | `'light'` \| `'dark'` | Theme type | `'light'` |
|
|
474
|
-
| `customConvertMarkdownString` | `(markdownString: string) => string` | Custom markdown string conversion function | - |
|
|
475
|
-
| `onEnd` | `(data: EndData) => void` | Typing end callback | - |
|
|
476
|
-
| `onStart` | `(data: StartData) => void` | Typing start callback | - |
|
|
477
|
-
| `onBeforeTypedChar` | `(data: IBeforeTypedChar) => Promise<void>` | Callback before typing a character, supports async, blocks next typing | - |
|
|
478
|
-
| `onTypedChar` | `(data: ITypedChar) => void` | Callback after each character | - |
|
|
479
|
-
| `disableTyping` | `boolean` | Disable typing animation | `false` |
|
|
480
|
-
| `autoStartTyping` | `boolean` | Whether to auto start typing animation, set false to trigger manually, not dynamically changeable | `true` |
|
|
481
|
-
|
|
482
|
-
> Note: If `disableTyping` changes from `true` to `false` during typing, all remaining characters will be displayed at once on the next typing trigger.
|
|
483
|
-
|
|
484
|
-
### IBeforeTypedChar
|
|
227
|
+
Usage:
|
|
485
228
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
229
|
+
```tsx
|
|
230
|
+
import remarkMath from 'remark-math';
|
|
231
|
+
import rehypeKatex from 'rehype-katex';
|
|
232
|
+
import 'katex/dist/katex.min.css';
|
|
233
|
+
|
|
234
|
+
<MarkdownTyper
|
|
235
|
+
interval={20}
|
|
236
|
+
reactMarkdownProps={{
|
|
237
|
+
remarkPlugins: [remarkMath],
|
|
238
|
+
rehypePlugins: [rehypeKatex]
|
|
239
|
+
}}
|
|
240
|
+
>
|
|
241
|
+
Inline formula: $E = mc^2$
|
|
242
|
+
|
|
243
|
+
Block formula:
|
|
244
|
+
$$
|
|
245
|
+
\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}
|
|
246
|
+
$$
|
|
247
|
+
</MarkdownTyper>
|
|
248
|
+
```
|
|
504
249
|
|
|
505
|
-
|
|
250
|
+
---
|
|
506
251
|
|
|
507
|
-
|
|
508
|
-
- Content filtering or sanitization
|
|
509
|
-
- Integration with external markdown processors
|
|
510
|
-
- Custom link handling or formatting
|
|
252
|
+
## Plugin System
|
|
511
253
|
|
|
512
|
-
|
|
254
|
+
Fully compatible with [react-markdown](https://github.com/remarkjs/react-markdown) plugin ecosystem:
|
|
513
255
|
|
|
514
256
|
```tsx
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
257
|
+
import rehypeHighlight from 'rehype-highlight';
|
|
258
|
+
import remarkGfm from 'remark-gfm';
|
|
259
|
+
import 'highlight.js/styles/github.css';
|
|
260
|
+
|
|
261
|
+
<MarkdownTyper
|
|
262
|
+
reactMarkdownProps={{
|
|
263
|
+
remarkPlugins: [remarkGfm],
|
|
264
|
+
rehypePlugins: [rehypeHighlight]
|
|
265
|
+
}}
|
|
266
|
+
>
|
|
267
|
+
```javascript
|
|
268
|
+
console.log('Syntax highlighting');
|
|
269
|
+
```
|
|
270
|
+
</MarkdownTyper>
|
|
519
271
|
```
|
|
520
272
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
You can pass all react-markdown props via `reactMarkdownProps` to support plugins.
|
|
524
|
-
|
|
525
|
-
### Exposed Methods
|
|
273
|
+
---
|
|
526
274
|
|
|
527
|
-
|
|
275
|
+
## Timer Modes
|
|
528
276
|
|
|
529
|
-
|
|
530
|
-
| --------- | ------ | ------------------------------------------- |
|
|
531
|
-
| `start` | - | Start typing animation |
|
|
532
|
-
| `stop` | - | Pause typing animation |
|
|
533
|
-
| `resume` | - | Resume typing animation |
|
|
534
|
-
| `restart` | - | Restart typing animation from the beginning |
|
|
277
|
+
### `requestAnimationFrame` Mode (Recommended)
|
|
535
278
|
|
|
536
|
-
|
|
279
|
+
- Time-driven, batch character processing
|
|
280
|
+
- Synchronized with browser 60fps refresh rate
|
|
281
|
+
- Suitable for high-frequency typing (interval < 16ms)
|
|
537
282
|
|
|
538
|
-
|
|
539
|
-
| ----------------- | ------------------------------------------- | ------------------------------------------- |
|
|
540
|
-
| `push` | `(content: string, answerType: AnswerType)` | Add content and start typing |
|
|
541
|
-
| `clear` | - | Clear all content and state |
|
|
542
|
-
| `triggerWholeEnd` | - | Manually trigger completion callback |
|
|
543
|
-
| `start` | - | Start typing animation |
|
|
544
|
-
| `stop` | - | Pause typing animation |
|
|
545
|
-
| `resume` | - | Resume typing animation |
|
|
546
|
-
| `restart` | - | Restart typing animation from the beginning |
|
|
283
|
+
### `setTimeout` Mode
|
|
547
284
|
|
|
548
|
-
|
|
285
|
+
- Single character processing, fixed interval
|
|
286
|
+
- Precise time control
|
|
287
|
+
- Suitable for low-frequency typing or scenarios requiring precise rhythm
|
|
549
288
|
|
|
550
289
|
```tsx
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
290
|
+
// High frequency: recommend requestAnimationFrame
|
|
291
|
+
<MarkdownTyper interval={5} timerType="requestAnimationFrame">
|
|
292
|
+
Fast typing
|
|
293
|
+
</MarkdownTyper>
|
|
294
|
+
|
|
295
|
+
// Low frequency: recommend setTimeout
|
|
296
|
+
<MarkdownTyper interval={100} timerType="setTimeout">
|
|
297
|
+
Slow typing
|
|
298
|
+
</MarkdownTyper>
|
|
555
299
|
```
|
|
556
300
|
|
|
557
301
|
---
|
|
558
302
|
|
|
559
|
-
##
|
|
303
|
+
## Advanced Features
|
|
560
304
|
|
|
561
|
-
###
|
|
305
|
+
### Custom Markdown Conversion
|
|
562
306
|
|
|
563
307
|
```tsx
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
};
|
|
574
|
-
|
|
575
|
-
return (
|
|
576
|
-
<MarkdownTyper interval={20} customConvertMarkdownString={customConvertMarkdownString}>
|
|
577
|
-
# Custom Markdown Processing This is **bold text** and *italic text*. Check out [our website](https://example.com) for more info!
|
|
578
|
-
</MarkdownTyper>
|
|
579
|
-
);
|
|
580
|
-
}
|
|
308
|
+
<MarkdownTyper
|
|
309
|
+
customConvertMarkdownString={(str) => {
|
|
310
|
+
// Custom processing logic
|
|
311
|
+
return str.replace(/\[([^\]]+)\]\(([^)]+)\)/g,
|
|
312
|
+
'<a href="$2" target="_blank">$1</a>');
|
|
313
|
+
}}
|
|
314
|
+
>
|
|
315
|
+
[Link](https://example.com)
|
|
316
|
+
</MarkdownTyper>
|
|
581
317
|
```
|
|
582
318
|
|
|
583
|
-
###
|
|
584
|
-
|
|
585
|
-
````tsx
|
|
586
|
-
// Complex custom processing example
|
|
587
|
-
const customConvertMarkdownString = (markdownString) => {
|
|
588
|
-
return (
|
|
589
|
-
markdownString
|
|
590
|
-
// Custom emoji processing
|
|
591
|
-
.replace(/:([a-zA-Z0-9_]+):/g, '<span class="emoji">:$1:</span>')
|
|
592
|
-
// Custom mention processing
|
|
593
|
-
.replace(/@([a-zA-Z0-9_]+)/g, '<span class="mention">@$1</span>')
|
|
594
|
-
// Custom code block processing
|
|
595
|
-
.replace(/```(\w+)\n([\s\S]*?)```/g, '<pre class="code-block"><code class="language-$1">$2</code></pre>')
|
|
596
|
-
// Custom link processing with security
|
|
597
|
-
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, url) => {
|
|
598
|
-
if (url.startsWith('http')) {
|
|
599
|
-
return `<a href="${url}" target="_blank" rel="noopener noreferrer">${text}</a>`;
|
|
600
|
-
}
|
|
601
|
-
return match;
|
|
602
|
-
})
|
|
603
|
-
);
|
|
604
|
-
};
|
|
605
|
-
````
|
|
606
|
-
|
|
607
|
-
### Integration with External Processors
|
|
319
|
+
### Callback Functions
|
|
608
320
|
|
|
609
321
|
```tsx
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
322
|
+
<MarkdownTyper
|
|
323
|
+
onStart={(data) => console.log('Typing started', data)}
|
|
324
|
+
onEnd={(data) => console.log('Typing ended', data)}
|
|
325
|
+
onTypedChar={(data) => {
|
|
326
|
+
console.log('Progress:', data.percent + '%');
|
|
327
|
+
}}
|
|
328
|
+
>
|
|
329
|
+
Content...
|
|
330
|
+
</MarkdownTyper>
|
|
619
331
|
```
|
|
620
332
|
|
|
621
|
-
###
|
|
333
|
+
### Disable Typing Animation
|
|
622
334
|
|
|
623
335
|
```tsx
|
|
624
|
-
const
|
|
625
|
-
// Filter out sensitive content
|
|
626
|
-
const filteredContent = markdownString.replace(/password[:\s]*[^\s]+/gi, 'password: [FILTERED]').replace(/token[:\s]*[^\s]+/gi, 'token: [FILTERED]');
|
|
336
|
+
const [disable, setDisable] = useState(false);
|
|
627
337
|
|
|
628
|
-
|
|
629
|
-
|
|
338
|
+
<MarkdownTyper disableTyping={disable}>
|
|
339
|
+
Content displays immediately, no animation
|
|
340
|
+
</MarkdownTyper>
|
|
630
341
|
```
|
|
631
342
|
|
|
632
343
|
---
|
|
633
344
|
|
|
634
|
-
##
|
|
635
|
-
|
|
636
|
-
See [react-markdown](https://github.com/remarkjs/react-markdown)
|
|
637
|
-
|
|
638
|
-
---
|
|
639
|
-
|
|
640
|
-
## 🎛️ Timer Modes Explained
|
|
641
|
-
|
|
642
|
-
### `requestAnimationFrame` Mode 🌟 (Recommended)
|
|
643
|
-
|
|
644
|
-
```typescript
|
|
645
|
-
// 🎯 Features
|
|
646
|
-
- Time-driven: Calculates character count based on real elapsed time
|
|
647
|
-
- Batch processing: Multiple characters per frame
|
|
648
|
-
- Frame sync: Syncs with browser 60fps refresh rate
|
|
649
|
-
- High-frequency optimization: Perfect for interval < 16ms
|
|
650
|
-
|
|
651
|
-
// 🎯 Use cases
|
|
652
|
-
- Default for modern web apps
|
|
653
|
-
- Pursue smooth animation
|
|
654
|
-
- High-frequency typing (interval > 0)
|
|
655
|
-
- AI real-time chat
|
|
656
|
-
```
|
|
345
|
+
## Example Projects
|
|
657
346
|
|
|
658
|
-
|
|
347
|
+
Clone the repository to view complete examples:
|
|
659
348
|
|
|
660
|
-
```
|
|
661
|
-
|
|
662
|
-
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
- Precise control: For specific timing needs
|
|
666
|
-
|
|
667
|
-
// 🎯 Use cases
|
|
668
|
-
- Need precise timing
|
|
669
|
-
- Retro typewriter effect
|
|
670
|
-
- High compatibility scenarios
|
|
349
|
+
```bash
|
|
350
|
+
git clone https://github.com/onshinpei/react-markdown-typer.git
|
|
351
|
+
cd react-markdown-typer
|
|
352
|
+
npm install
|
|
353
|
+
npm run dev
|
|
671
354
|
```
|
|
672
355
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
| **High freq** | ✅ Excellent (5ms → 3 chars) | ❌ May stutter |
|
|
679
|
-
| **Low freq** | ✅ Normal (100ms → 1 char/6f) | ✅ Precise |
|
|
680
|
-
| **Visual** | 🎬 Smooth animation | ⚡ Rhythmic |
|
|
681
|
-
| **Perf cost** | 🟢 Low (frame sync) | 🟡 Medium (timer) |
|
|
682
|
-
|
|
683
|
-
High frequency: use `requestAnimationFrame`, low frequency: use `setTimeout`
|
|
356
|
+
Example locations:
|
|
357
|
+
- `example/basic/` - Basic usage
|
|
358
|
+
- `example/cmd/` - Imperative API
|
|
359
|
+
- `example/cursor/` - Cursor effects
|
|
360
|
+
- `example/katex/` - Math formulas
|
|
684
361
|
|
|
685
362
|
---
|
|
686
363
|
|
|
687
|
-
##
|
|
688
|
-
|
|
689
|
-
### 📝 AI Streaming Chat
|
|
690
|
-
|
|
691
|
-
<!-- [DEMO: 🔧 Try on StackBlitz](https://stackblitz.com/edit/vitejs-vite-2ri8kex3?file=src%2FApp.tsx) -->
|
|
692
|
-
|
|
693
|
-
````tsx
|
|
694
|
-
import { useRef } from 'react';
|
|
695
|
-
import { MarkdownCMD, MarkdownTyperCMDRef } from 'react-markdown-typer';
|
|
696
|
-
|
|
697
|
-
function StreamingChat() {
|
|
698
|
-
const markdownRef = useRef<MarkdownTyperCMDRef>(null);
|
|
699
|
-
|
|
700
|
-
// Simulate AI streaming response
|
|
701
|
-
const simulateAIResponse = async () => {
|
|
702
|
-
markdownRef.current?.clear();
|
|
703
|
-
|
|
704
|
-
// Thinking phase
|
|
705
|
-
markdownRef.current?.push('🤔 Analyzing your question...', 'thinking');
|
|
706
|
-
await delay(1000);
|
|
707
|
-
markdownRef.current?.push('\n\n✅ Analysis complete, starting answer', 'thinking');
|
|
708
|
-
|
|
709
|
-
// Streaming answer
|
|
710
|
-
const chunks = [
|
|
711
|
-
'# React 19 New Features\n\n',
|
|
712
|
-
'## 🚀 React Compiler\n',
|
|
713
|
-
'The highlight of React 19 is the introduction of **React Compiler**:\n\n',
|
|
714
|
-
'- 🎯 **Auto optimization**: No need for manual memo/useMemo\n',
|
|
715
|
-
'- ⚡ **Performance boost**: Compile-time optimization, zero runtime cost\n',
|
|
716
|
-
'- 🔧 **Backward compatible**: No code changes needed\n\n',
|
|
717
|
-
'## 📝 Actions Simplify Forms\n',
|
|
718
|
-
'The new Actions API makes form handling easier:\n\n',
|
|
719
|
-
'```tsx\n',
|
|
720
|
-
'function ContactForm({ action }) {\n',
|
|
721
|
-
' const [state, formAction] = useActionState(action, null);\n',
|
|
722
|
-
' return (\n',
|
|
723
|
-
' <form action={formAction}>\n',
|
|
724
|
-
' <input name="email" type="email" />\n',
|
|
725
|
-
' <button>Submit</button>\n',
|
|
726
|
-
' </form>\n',
|
|
727
|
-
' );\n',
|
|
728
|
-
'}\n',
|
|
729
|
-
'```\n\n',
|
|
730
|
-
'Hope this helps! 🎉',
|
|
731
|
-
];
|
|
732
|
-
|
|
733
|
-
for (const chunk of chunks) {
|
|
734
|
-
await delay(100);
|
|
735
|
-
markdownRef.current?.push(chunk, 'answer');
|
|
736
|
-
}
|
|
737
|
-
};
|
|
738
|
-
|
|
739
|
-
return (
|
|
740
|
-
<div className="chat-container">
|
|
741
|
-
<button onClick={simulateAIResponse}>🤖 Ask about React 19 features</button>
|
|
742
|
-
|
|
743
|
-
<MarkdownCMD ref={markdownRef} interval={10} timerType="requestAnimationFrame" onEnd={(data) => console.log('Paragraph done:', data)} />
|
|
744
|
-
</div>
|
|
745
|
-
);
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
749
|
-
````
|
|
750
|
-
|
|
751
|
-
### 🔧 Custom Markdown Processing Demo
|
|
752
|
-
|
|
753
|
-
```tsx
|
|
754
|
-
function CustomMarkdownStreamingDemo() {
|
|
755
|
-
const markdownRef = useRef<MarkdownTyperCMDRef>(null);
|
|
756
|
-
|
|
757
|
-
const customConvertMarkdownString = (markdownString) => {
|
|
758
|
-
return markdownString
|
|
759
|
-
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>')
|
|
760
|
-
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
|
|
761
|
-
.replace(/\*([^*]+)\*/g, '<em>$1</em>')
|
|
762
|
-
.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
763
|
-
};
|
|
764
|
-
|
|
765
|
-
const simulateCustomResponse = async () => {
|
|
766
|
-
markdownRef.current?.clear();
|
|
767
|
-
|
|
768
|
-
const customChunks = [
|
|
769
|
-
'# Custom Markdown Processing\n\n',
|
|
770
|
-
'This demo shows how to use **custom markdown processing** with streaming content:\n\n',
|
|
771
|
-
'## Features\n',
|
|
772
|
-
'- *Custom link handling*\n',
|
|
773
|
-
'- **Bold and italic** text processing\n',
|
|
774
|
-
'- `Inline code` formatting\n',
|
|
775
|
-
'- [External links](https://example.com) with security attributes\n\n',
|
|
776
|
-
'The `customConvertMarkdownString` function allows you to preprocess content before rendering!',
|
|
777
|
-
];
|
|
778
|
-
|
|
779
|
-
for (const chunk of customChunks) {
|
|
780
|
-
await delay(150);
|
|
781
|
-
markdownRef.current?.push(chunk, 'answer');
|
|
782
|
-
}
|
|
783
|
-
};
|
|
784
|
-
|
|
785
|
-
return (
|
|
786
|
-
<div>
|
|
787
|
-
<button onClick={simulateCustomResponse}>🔧 Custom Markdown Demo</button>
|
|
788
|
-
|
|
789
|
-
<MarkdownCMD ref={markdownRef} interval={20} timerType="requestAnimationFrame" customConvertMarkdownString={customConvertMarkdownString} />
|
|
790
|
-
</div>
|
|
791
|
-
);
|
|
792
|
-
}
|
|
793
|
-
```
|
|
794
|
-
|
|
795
|
-
### 🎯 Advanced Callback Control
|
|
364
|
+
## Contributing
|
|
796
365
|
|
|
797
|
-
|
|
798
|
-
import { useRef, useState } from 'react';
|
|
799
|
-
import { MarkdownCMD, MarkdownTyperCMDRef } from 'react-markdown-typer';
|
|
366
|
+
Issues and Pull Requests are welcome!
|
|
800
367
|
|
|
801
|
-
|
|
802
|
-
const markdownRef = useRef<MarkdownTyperCMDRef>(null);
|
|
803
|
-
const [typingStats, setTypingStats] = useState({ progress: 0, currentChar: '', totalChars: 0 });
|
|
368
|
+
## License
|
|
804
369
|
|
|
805
|
-
|
|
806
|
-
// Async operation before typing a character
|
|
807
|
-
console.log('About to type:', data.currentChar);
|
|
370
|
+
MIT © [onshinpei](https://github.com/onshinpei)
|
|
808
371
|
|
|
809
|
-
|
|
810
|
-
if (data.currentChar === '!') {
|
|
811
|
-
await new Promise((resolve) => setTimeout(resolve, 500)); // Simulate delay
|
|
812
|
-
}
|
|
813
|
-
};
|
|
814
|
-
|
|
815
|
-
const handleTypedChar = (data) => {
|
|
816
|
-
// Update typing stats
|
|
817
|
-
setTypingStats({
|
|
818
|
-
progress: Math.round(data.percent),
|
|
819
|
-
currentChar: data.currentChar,
|
|
820
|
-
totalChars: data.currentIndex + 1,
|
|
821
|
-
});
|
|
822
|
-
|
|
823
|
-
// Add sound effects, animations, etc. here
|
|
824
|
-
if (data.currentChar === '.') {
|
|
825
|
-
// Play period sound effect
|
|
826
|
-
console.log('Play period sound');
|
|
827
|
-
}
|
|
828
|
-
};
|
|
829
|
-
|
|
830
|
-
const handleStart = (data) => {
|
|
831
|
-
console.log('Typing started:', data.currentChar);
|
|
832
|
-
};
|
|
833
|
-
|
|
834
|
-
const handleEnd = (data) => {
|
|
835
|
-
console.log('Typing finished:', data.str);
|
|
836
|
-
};
|
|
837
|
-
|
|
838
|
-
const startDemo = () => {
|
|
839
|
-
markdownRef.current?.clear();
|
|
840
|
-
markdownRef.current?.push(
|
|
841
|
-
'# Advanced Callback Demo\n\n' +
|
|
842
|
-
'This example shows how to use `onBeforeTypedChar` and `onTypedChar` callbacks:\n\n' +
|
|
843
|
-
'- 🎯 **Before typing callback**: Async operations before displaying a character\n' +
|
|
844
|
-
'- 📊 **After typing callback**: Real-time progress updates and effects\n' +
|
|
845
|
-
'- ⚡ **Performance**: Async operations without affecting typing smoothness\n\n' +
|
|
846
|
-
'Current progress: ' +
|
|
847
|
-
typingStats.progress +
|
|
848
|
-
'%\n' +
|
|
849
|
-
'Characters typed: ' +
|
|
850
|
-
typingStats.totalChars +
|
|
851
|
-
'\n\n' +
|
|
852
|
-
'This is a very powerful feature!',
|
|
853
|
-
'answer',
|
|
854
|
-
);
|
|
855
|
-
};
|
|
372
|
+
---
|
|
856
373
|
|
|
857
|
-
|
|
858
|
-
<div>
|
|
859
|
-
<button onClick={startDemo}>🚀 Start Advanced Demo</button>
|
|
374
|
+
## Related Projects
|
|
860
375
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
</div>
|
|
376
|
+
- [react-markdown](https://github.com/remarkjs/react-markdown) - Markdown rendering core
|
|
377
|
+
- [ds-markdown](https://github.com/onshinpei/ds-markdown) - Enhanced version with styling (supports mermaid charts)
|
|
864
378
|
|
|
865
|
-
<MarkdownCMD ref={markdownRef} interval={30} onBeforeTypedChar={handleBeforeTypedChar} onTypedChar={handleTypedChar} onStart={handleStart} onEnd={handleEnd} />
|
|
866
|
-
</div>
|
|
867
|
-
);
|
|
868
|
-
}
|
|
869
|
-
```
|