react-markdown-typer 0.0.1 → 1.0.0-beta.1
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 +875 -0
- package/README.zh.md +8 -11
- package/es/Markdown/index.d.ts +5 -0
- package/es/Markdown/index.d.ts.map +1 -0
- package/es/Markdown/index.js +62 -0
- package/es/Markdown/index.js.map +1 -0
- package/es/MarkdownCMD/index.d.ts +4 -0
- package/es/MarkdownCMD/index.d.ts.map +1 -0
- package/es/MarkdownCMD/index.js +160 -0
- package/es/MarkdownCMD/index.js.map +1 -0
- package/es/components/HighReactMarkdown/index.d.ts +9 -0
- package/es/components/HighReactMarkdown/index.d.ts.map +1 -0
- package/es/components/HighReactMarkdown/index.js +7 -0
- package/es/components/HighReactMarkdown/index.js.map +1 -0
- package/es/constant.d.ts +4 -0
- package/es/constant.d.ts.map +1 -0
- package/es/constant.js +4 -0
- package/es/constant.js.map +1 -0
- package/{dist/cjs/index.d.ts → es/defined.d.ts} +47 -21
- package/es/defined.d.ts.map +1 -0
- package/es/defined.js +2 -0
- package/es/defined.js.map +1 -0
- package/es/hooks/useTypingTask.d.ts +32 -0
- package/es/hooks/useTypingTask.d.ts.map +1 -0
- package/{dist/esm/index.js → es/hooks/useTypingTask.js} +7 -260
- package/es/hooks/useTypingTask.js.map +1 -0
- package/es/index.d.ts +7 -0
- package/es/index.d.ts.map +1 -0
- package/es/index.js +5 -0
- package/es/index.js.map +1 -0
- package/package.json +11 -15
- package/dist/cjs/index.js +0 -570
- package/dist/cjs/index.js.map +0 -1
- package/dist/esm/index.d.ts +0 -77
- package/dist/esm/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -0,0 +1,875 @@
|
|
|
1
|
+
# react-markdown-typer
|
|
2
|
+
|
|
3
|
+
> 🚀 React Markdown typing animation component
|
|
4
|
+
|
|
5
|
+
**if you need styling, support for mathematical formulas, and mermaid chart rendering, we recommend using [ds-markdown](https://github.com/onshinpei/ds-markdown)**
|
|
6
|
+
|
|
7
|
+
**[🇨🇳 中文](./README.zh.md) | 🇺🇸 English**
|
|
8
|
+
|
|
9
|
+
A React component designed for modern AI applications, providing smooth real-time typing animation and full Markdown rendering capabilities.
|
|
10
|
+
|
|
11
|
+
[](https://www.npmjs.com/package/react-markdown-typer)
|
|
12
|
+
[](https://www.npmjs.com/package/react-markdown-typer)
|
|
13
|
+
[](https://bundlephobia.com/package/react-markdown-typer)
|
|
14
|
+
[](https://react.dev)
|
|
15
|
+
[](https://www.typescriptlang.org/)
|
|
16
|
+
|
|
17
|
+
[📖 Online Demo](https://onshinpei.github.io/react-markdown-typer/)
|
|
18
|
+
|
|
19
|
+
[DEMO: 🔧 Try on StackBlitz](https://stackblitz.com/edit/vitejs-vite-zbrdonvx?file=src%2FApp.tsx)
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## ❓ Why use react-markdown-typer?
|
|
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.
|
|
34
|
+
|
|
35
|
+
- **Multi-theme & plugin architecture**
|
|
36
|
+
compatible with [react-markdown](https://github.com/remarkjs/react-markdown) remark/rehype plugins for advanced customization and extension.
|
|
37
|
+
|
|
38
|
+
- **Wide range of use cases**
|
|
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
|
|
44
|
+
|
|
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
|
+
- [Math Formula Support](#math-formula-support)
|
|
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
|
+
- [IMarkdownMath](#imarkdownmath)
|
|
72
|
+
- [IMarkdownPlugin](#imarkdownplugin)
|
|
73
|
+
- [Exposed Methods](#exposed-methods)
|
|
74
|
+
- [Default Export MarkdownTyper](#default-export-markdowntyper)
|
|
75
|
+
- [MarkdownCMD Exposed Methods](#markdowncmd-exposed-methods)
|
|
76
|
+
- [🧮 Math Formula Guide](#-math-formula-guide)
|
|
77
|
+
- [Basic Syntax](#basic-syntax)
|
|
78
|
+
- [Delimiter Selection](#delimiter-selection)
|
|
79
|
+
- [Streaming Math Formulas](#streaming-math-formulas)
|
|
80
|
+
- [Style Customization](#style-customization)
|
|
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
|
+
- [🧮 Streaming Math Formula Rendering](#-streaming-math-formula-rendering)
|
|
89
|
+
- [🎯 Advanced Callback Control](#-advanced-callback-control-1)
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## ✨ Core Features
|
|
94
|
+
|
|
95
|
+
### 🤖 **AI Chat Scenario**
|
|
96
|
+
|
|
97
|
+
- 1:1 replica of [DeepSeek official site](https://chat.deepseek.com/) chat response effect
|
|
98
|
+
- Supports both `thinking` and `answer` modes
|
|
99
|
+
- Perfectly fits streaming data, zero-latency user response
|
|
100
|
+
|
|
101
|
+
### 📊 **Content Display Scenario**
|
|
102
|
+
|
|
103
|
+
- Full Markdown support, including code highlighting, tables, lists, etc.
|
|
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
|
|
107
|
+
|
|
108
|
+
### 🔧 **Developer Experience**
|
|
109
|
+
|
|
110
|
+
- Supports typing interruption with `stop` and resume with `resume`
|
|
111
|
+
- Typing animation can be enabled/disabled
|
|
112
|
+
|
|
113
|
+
### 🎬 **Smooth Animation**
|
|
114
|
+
|
|
115
|
+
- Dual timer optimization: supports both `requestAnimationFrame` and `setTimeout` modes
|
|
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
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 📦 Quick Installation
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# npm
|
|
126
|
+
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
|
+
```
|
|
147
|
+
|
|
148
|
+
## 🚀 5-Minute Quick Start
|
|
149
|
+
|
|
150
|
+
### Basic Usage
|
|
151
|
+
|
|
152
|
+
[DEMO](https://stackblitz.com/edit/vitejs-vite-z94syu8j?file=src%2FApp.tsx)
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
import MarkdownTyper from 'react-markdown-typer';
|
|
156
|
+
import 'react-markdown-typer/style.css';
|
|
157
|
+
|
|
158
|
+
function App() {
|
|
159
|
+
return (
|
|
160
|
+
<MarkdownTyper interval={20}>
|
|
161
|
+
# Hello react-markdown-typer This is a **high-performance** typing animation component! ## Features - ⚡ Zero-latency streaming - 🎬 Smooth typing animation - 🎯 Perfect syntax support
|
|
162
|
+
</MarkdownTyper>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Disable Typing Animation
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
import MarkdownTyper from 'react-markdown-typer';
|
|
171
|
+
import 'react-markdown-typer/style.css';
|
|
172
|
+
|
|
173
|
+
function StaticDemo() {
|
|
174
|
+
const [disableTyping, setDisableTyping] = useState(false);
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<div>
|
|
178
|
+
<button onClick={() => setDisableTyping(!disableTyping)}>{disableTyping ? 'Enable' : 'Disable'} typewriter effect</button>
|
|
179
|
+
|
|
180
|
+
<MarkdownTyper interval={20} disableTyping={disableTyping}>
|
|
181
|
+
# 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 -
|
|
182
|
+
⚡ Quick content preview
|
|
183
|
+
</MarkdownTyper>
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Math Formula Support
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
import MarkdownTyper from 'react-markdown-typer';
|
|
193
|
+
// If you need to display formulas, import the formula plugins
|
|
194
|
+
import remarkMath from 'remark-math';
|
|
195
|
+
import rehypeKatex from 'rehype-katex';
|
|
196
|
+
|
|
197
|
+
function MathDemo() {
|
|
198
|
+
return (
|
|
199
|
+
<MarkdownTyper interval={20} reactMarkdownProps={{ remarkPlugins: [remarkMath], rehypePlugins: [rehypeKatex] }} math={{ splitSymbol: 'dollar' }}>
|
|
200
|
+
# Pythagorean Theorem In a right triangle, the square of the hypotenuse equals the sum of the squares of the other two sides: $a^2 + b^2 = c^2$ Where: - $a$ and $b$ are the legs - $c$ is the
|
|
201
|
+
hypotenuse For the classic "3-4-5 triangle": $c = \sqrt{3 ^ (2 + 4) ^ 2} = \sqrt{25} = 5$
|
|
202
|
+
</MarkdownTyper>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### AI Chat Scenario
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
function ChatDemo() {
|
|
211
|
+
const [answer, setAnswer] = useState('');
|
|
212
|
+
|
|
213
|
+
const handleAsk = () => {
|
|
214
|
+
setAnswer(`# About React 19
|
|
215
|
+
|
|
216
|
+
React 19 brings many exciting new features:
|
|
217
|
+
|
|
218
|
+
## 🚀 Major Updates
|
|
219
|
+
1. **React Compiler** - Automatic performance optimization
|
|
220
|
+
2. **Actions** - Simplified form handling
|
|
221
|
+
3. **Document Metadata** - Built-in SEO support
|
|
222
|
+
|
|
223
|
+
Let's explore these new features together!`);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<div>
|
|
228
|
+
<button onClick={handleAsk}>Ask AI</button>
|
|
229
|
+
|
|
230
|
+
{answer && <MarkdownTyper interval={15}>{answer}</MarkdownTyper>}
|
|
231
|
+
</div>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### 🎯 Advanced Callback Control
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
import { useRef, useState } from 'react';
|
|
240
|
+
import { MarkdownCMD, MarkdownCMDRef } from 'react-markdown-typer';
|
|
241
|
+
|
|
242
|
+
function AdvancedCallbackDemo() {
|
|
243
|
+
const markdownRef = useRef<MarkdownCMDRef>(null);
|
|
244
|
+
const [typingStats, setTypingStats] = useState({ progress: 0, currentChar: '', totalChars: 0 });
|
|
245
|
+
|
|
246
|
+
const handleBeforeTypedChar = async (data) => {
|
|
247
|
+
// Async operation before typing a character
|
|
248
|
+
console.log('About to type:', data.currentChar);
|
|
249
|
+
|
|
250
|
+
// You can do network requests, data validation, etc. here
|
|
251
|
+
if (data.currentChar === '!') {
|
|
252
|
+
await new Promise((resolve) => setTimeout(resolve, 500)); // Simulate delay
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const handleTypedChar = (data) => {
|
|
257
|
+
// Update typing stats
|
|
258
|
+
setTypingStats({
|
|
259
|
+
progress: Math.round(data.percent),
|
|
260
|
+
currentChar: data.currentChar,
|
|
261
|
+
totalChars: data.currentIndex + 1,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// Add sound effects, animations, etc. here
|
|
265
|
+
if (data.currentChar === '.') {
|
|
266
|
+
// Play period sound effect
|
|
267
|
+
console.log('Play period sound');
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const handleStart = (data) => {
|
|
272
|
+
console.log('Typing started:', data.currentChar);
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const handleEnd = (data) => {
|
|
276
|
+
console.log('Typing finished:', data.str);
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const startDemo = () => {
|
|
280
|
+
markdownRef.current?.clear();
|
|
281
|
+
markdownRef.current?.push(
|
|
282
|
+
'# Advanced Callback Demo\n\n' +
|
|
283
|
+
'This example shows how to use `onBeforeTypedChar` and `onTypedChar` callbacks:\n\n' +
|
|
284
|
+
'- 🎯 **Before typing callback**: Async operations before displaying a character\n' +
|
|
285
|
+
'- 📊 **After typing callback**: Real-time progress updates and effects\n' +
|
|
286
|
+
'- ⚡ **Performance**: Async operations without affecting typing smoothness\n\n' +
|
|
287
|
+
'Current progress: ' +
|
|
288
|
+
typingStats.progress +
|
|
289
|
+
'%\n' +
|
|
290
|
+
'Characters typed: ' +
|
|
291
|
+
typingStats.totalChars +
|
|
292
|
+
'\n\n' +
|
|
293
|
+
'This is a very powerful feature!',
|
|
294
|
+
'answer',
|
|
295
|
+
);
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
return (
|
|
299
|
+
<div>
|
|
300
|
+
<button onClick={startDemo}>🚀 Start Advanced Demo</button>
|
|
301
|
+
|
|
302
|
+
<div style={{ margin: '10px 0', padding: '10px', background: '#f5f5f5', borderRadius: '4px' }}>
|
|
303
|
+
<strong>Typing Stats:</strong> Progress {typingStats.progress}% | Current char: "{typingStats.currentChar}" | Total chars: {typingStats.totalChars}
|
|
304
|
+
</div>
|
|
305
|
+
|
|
306
|
+
<MarkdownCMD ref={markdownRef} interval={30} onBeforeTypedChar={handleBeforeTypedChar} onTypedChar={handleTypedChar} onStart={handleStart} onEnd={handleEnd} />
|
|
307
|
+
</div>
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### 🔄 Restart Animation Demo
|
|
313
|
+
|
|
314
|
+
```tsx
|
|
315
|
+
import { useRef, useState } from 'react';
|
|
316
|
+
import { MarkdownCMD, MarkdownCMDRef } from 'react-markdown-typer';
|
|
317
|
+
|
|
318
|
+
function RestartDemo() {
|
|
319
|
+
const markdownRef = useRef<MarkdownCMDRef>(null);
|
|
320
|
+
const [isPlaying, setIsPlaying] = useState(false);
|
|
321
|
+
const [hasStarted, setHasStarted] = useState(false);
|
|
322
|
+
|
|
323
|
+
const startContent = () => {
|
|
324
|
+
markdownRef.current?.clear();
|
|
325
|
+
markdownRef.current?.push(
|
|
326
|
+
'# Restart Animation Demo\n\n' +
|
|
327
|
+
'This example shows how to use the `restart()` method:\n\n' +
|
|
328
|
+
'- 🔄 **Restart**: Play current content from the beginning\n' +
|
|
329
|
+
'- ⏸️ **Pause/Resume**: Pause and resume anytime\n' +
|
|
330
|
+
'- 🎯 **Precise control**: Full control over animation state\n\n' +
|
|
331
|
+
'Current state: ' +
|
|
332
|
+
(isPlaying ? 'Playing' : 'Paused') +
|
|
333
|
+
'\n\n' +
|
|
334
|
+
'This is a very practical feature!',
|
|
335
|
+
'answer',
|
|
336
|
+
);
|
|
337
|
+
setIsPlaying(true);
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const handleStart = () => {
|
|
341
|
+
if (hasStarted) {
|
|
342
|
+
// If already started, restart
|
|
343
|
+
markdownRef.current?.restart();
|
|
344
|
+
} else {
|
|
345
|
+
// First start
|
|
346
|
+
markdownRef.current?.start();
|
|
347
|
+
setHasStarted(true);
|
|
348
|
+
}
|
|
349
|
+
setIsPlaying(true);
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
const handleStop = () => {
|
|
353
|
+
markdownRef.current?.stop();
|
|
354
|
+
setIsPlaying(false);
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
const handleResume = () => {
|
|
358
|
+
markdownRef.current?.resume();
|
|
359
|
+
setIsPlaying(true);
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const handleRestart = () => {
|
|
363
|
+
markdownRef.current?.restart();
|
|
364
|
+
setIsPlaying(true);
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const handleEnd = () => {
|
|
368
|
+
setIsPlaying(false);
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
return (
|
|
372
|
+
<div>
|
|
373
|
+
<div style={{ marginBottom: '10px', display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
|
|
374
|
+
<button onClick={startContent}>🚀 Start Content</button>
|
|
375
|
+
<button onClick={handleStart} disabled={isPlaying}>
|
|
376
|
+
{hasStarted ? '🔄 Restart' : '▶️ Start'}
|
|
377
|
+
</button>
|
|
378
|
+
<button onClick={handleStop} disabled={!isPlaying}>
|
|
379
|
+
⏸️ Pause
|
|
380
|
+
</button>
|
|
381
|
+
<button onClick={handleResume} disabled={isPlaying}>
|
|
382
|
+
▶️ Resume
|
|
383
|
+
</button>
|
|
384
|
+
<button onClick={handleRestart}>🔄 Restart</button>
|
|
385
|
+
</div>
|
|
386
|
+
|
|
387
|
+
<div style={{ margin: '10px 0', padding: '10px', background: '#f5f5f5', borderRadius: '4px' }}>
|
|
388
|
+
<strong>Animation State:</strong> {isPlaying ? '🟢 Playing' : '🔴 Paused'}
|
|
389
|
+
</div>
|
|
390
|
+
|
|
391
|
+
<MarkdownCMD ref={markdownRef} interval={25} onEnd={handleEnd} />
|
|
392
|
+
</div>
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### ▶️ Manual Start Animation Demo
|
|
398
|
+
|
|
399
|
+
```tsx
|
|
400
|
+
import { useRef, useState } from 'react';
|
|
401
|
+
import { MarkdownCMD, MarkdownCMDRef } from 'react-markdown-typer';
|
|
402
|
+
|
|
403
|
+
function StartDemo() {
|
|
404
|
+
const markdownRef = useRef<MarkdownCMDRef>(null);
|
|
405
|
+
const [isPlaying, setIsPlaying] = useState(false);
|
|
406
|
+
const [hasStarted, setHasStarted] = useState(false);
|
|
407
|
+
|
|
408
|
+
const loadContent = () => {
|
|
409
|
+
markdownRef.current?.clear();
|
|
410
|
+
markdownRef.current?.push(
|
|
411
|
+
'# Manual Start Animation Demo\n\n' +
|
|
412
|
+
'This example shows how to use the `start()` method:\n\n' +
|
|
413
|
+
'- 🎯 **Manual control**: When `autoStartTyping=false`, you need to call `start()` manually\n' +
|
|
414
|
+
'- ⏱️ **Delayed start**: Start animation after user interaction\n' +
|
|
415
|
+
'- 🎮 **Gamification**: Suitable for scenarios requiring user trigger\n\n' +
|
|
416
|
+
'Click the "Start Animation" button to manually trigger typing!',
|
|
417
|
+
'answer',
|
|
418
|
+
);
|
|
419
|
+
setIsPlaying(false);
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
const handleStart = () => {
|
|
423
|
+
if (hasStarted) {
|
|
424
|
+
// If already started, restart
|
|
425
|
+
markdownRef.current?.restart();
|
|
426
|
+
} else {
|
|
427
|
+
// First start
|
|
428
|
+
markdownRef.current?.start();
|
|
429
|
+
setHasStarted(true);
|
|
430
|
+
}
|
|
431
|
+
setIsPlaying(true);
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
const handleEnd = () => {
|
|
435
|
+
setIsPlaying(false);
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
return (
|
|
439
|
+
<div>
|
|
440
|
+
<div style={{ marginBottom: '10px', display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
|
|
441
|
+
<button onClick={loadContent}>📝 Load Content</button>
|
|
442
|
+
<button onClick={handleStart} disabled={isPlaying}>
|
|
443
|
+
{hasStarted ? '🔄 Restart' : '▶️ Start Animation'}
|
|
444
|
+
</button>
|
|
445
|
+
</div>
|
|
446
|
+
|
|
447
|
+
<div style={{ margin: '10px 0', padding: '10px', background: '#f5f5f5', borderRadius: '4px' }}>
|
|
448
|
+
<strong>Status:</strong> {isPlaying ? '🟢 Animation Playing' : '🔴 Waiting to Start'}
|
|
449
|
+
</div>
|
|
450
|
+
|
|
451
|
+
<MarkdownCMD ref={markdownRef} interval={30} autoStartTyping={false} onEnd={handleEnd} />
|
|
452
|
+
</div>
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
## 📚 Full API Documentation
|
|
460
|
+
|
|
461
|
+
### Default Exported Props for MarkdownTyper and MarkdownCMD
|
|
462
|
+
|
|
463
|
+
```js
|
|
464
|
+
import MarkdownTyper, { MarkdownCMD } from 'react-markdown-typer';
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
| Prop | Type | Description | Default |
|
|
468
|
+
| ------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
|
|
469
|
+
| `interval` | `number` | Typing interval (ms) | `30` |
|
|
470
|
+
| `timerType` | `'setTimeout'` \| `'requestAnimationFrame'` | Timer type, not dynamically changeable | Default is `setTimeout`, will switch to `requestAnimationFrame` in the future |
|
|
471
|
+
| `theme` | `'light'` \| `'dark'` | Theme type | `'light'` |
|
|
472
|
+
| `math` | [IMarkdownMath](#IMarkdownMath) | Math formula config | `{ splitSymbol: 'dollar' }` |
|
|
473
|
+
| `onEnd` | `(data: EndData) => void` | Typing end callback | - |
|
|
474
|
+
| `onStart` | `(data: StartData) => void` | Typing start callback | - |
|
|
475
|
+
| `onBeforeTypedChar` | `(data: IBeforeTypedChar) => Promise<void>` | Callback before typing a character, supports async, blocks next typing | - |
|
|
476
|
+
| `onTypedChar` | `(data: ITypedChar) => void` | Callback after each character | - |
|
|
477
|
+
| `disableTyping` | `boolean` | Disable typing animation | `false` |
|
|
478
|
+
| `autoStartTyping` | `boolean` | Whether to auto start typing animation, set false to trigger manually, not dynamically changeable | `true` |
|
|
479
|
+
|
|
480
|
+
> Note: If `disableTyping` changes from `true` to `false` during typing, all remaining characters will be displayed at once on the next typing trigger.
|
|
481
|
+
|
|
482
|
+
### IBeforeTypedChar
|
|
483
|
+
|
|
484
|
+
| Prop | Type | Description | Default |
|
|
485
|
+
| -------------- | -------- | ------------------------------- | ------- |
|
|
486
|
+
| `currentIndex` | `number` | Index of current character | `0` |
|
|
487
|
+
| `currentChar` | `string` | Character to be typed | - |
|
|
488
|
+
| `prevStr` | `string` | Prefix string of current type | - |
|
|
489
|
+
| `percent` | `number` | Typing progress percent (0-100) | `0` |
|
|
490
|
+
|
|
491
|
+
### ITypedChar
|
|
492
|
+
|
|
493
|
+
| Prop | Type | Description | Default |
|
|
494
|
+
| -------------- | -------- | ------------------------------- | ------- |
|
|
495
|
+
| `currentIndex` | `number` | Index of current character | `0` |
|
|
496
|
+
| `currentChar` | `string` | Character just typed | - |
|
|
497
|
+
| `prevStr` | `string` | Prefix string of current type | - |
|
|
498
|
+
| `currentStr` | `string` | Full string of current type | - |
|
|
499
|
+
| `percent` | `number` | Typing progress percent (0-100) | `0` |
|
|
500
|
+
|
|
501
|
+
#### IMarkdownMath
|
|
502
|
+
|
|
503
|
+
| Prop | Type | Description | Default |
|
|
504
|
+
| ------------- | ------------------------- | --------------------------- | ---------- |
|
|
505
|
+
| `splitSymbol` | `'dollar'` \| `'bracket'` | Math formula delimiter type | `'dollar'` |
|
|
506
|
+
|
|
507
|
+
**Delimiter Explanation:**
|
|
508
|
+
|
|
509
|
+
- `'dollar'`: Use `$...$` and `$$...$$` syntax
|
|
510
|
+
- `'bracket'`: Use `\(...\)` and `\[...\]` syntax
|
|
511
|
+
|
|
512
|
+
#### IMarkdownPlugin
|
|
513
|
+
|
|
514
|
+
You can pass all react-markdown props via `reactMarkdownProps` to support plugins.
|
|
515
|
+
|
|
516
|
+
### Exposed Methods
|
|
517
|
+
|
|
518
|
+
#### Default Export MarkdownTyper
|
|
519
|
+
|
|
520
|
+
| Method | Params | Description |
|
|
521
|
+
| --------- | ------ | ------------------------------------------- |
|
|
522
|
+
| `start` | - | Start typing animation |
|
|
523
|
+
| `stop` | - | Pause typing animation |
|
|
524
|
+
| `resume` | - | Resume typing animation |
|
|
525
|
+
| `restart` | - | Restart typing animation from the beginning |
|
|
526
|
+
|
|
527
|
+
#### MarkdownCMD Exposed Methods
|
|
528
|
+
|
|
529
|
+
| Method | Params | Description |
|
|
530
|
+
| ----------------- | ------------------------------------------- | ------------------------------------------- |
|
|
531
|
+
| `push` | `(content: string, answerType: AnswerType)` | Add content and start typing |
|
|
532
|
+
| `clear` | - | Clear all content and state |
|
|
533
|
+
| `triggerWholeEnd` | - | Manually trigger completion callback |
|
|
534
|
+
| `start` | - | Start typing animation |
|
|
535
|
+
| `stop` | - | Pause typing animation |
|
|
536
|
+
| `resume` | - | Resume typing animation |
|
|
537
|
+
| `restart` | - | Restart typing animation from the beginning |
|
|
538
|
+
|
|
539
|
+
**Usage Example:**
|
|
540
|
+
|
|
541
|
+
```tsx
|
|
542
|
+
markdownRef.current?.start(); // Start animation
|
|
543
|
+
markdownRef.current?.stop(); // Pause animation
|
|
544
|
+
markdownRef.current?.resume(); // Resume animation
|
|
545
|
+
markdownRef.current?.restart(); // Restart animation
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
---
|
|
549
|
+
|
|
550
|
+
## 🧮 Math Formula Guide
|
|
551
|
+
|
|
552
|
+
<!-- [DEMO1: Pythagorean Theorem](https://stackblitz.com/edit/vitejs-vite-z94syu8j?file=src%2FApp.tsx) -->
|
|
553
|
+
|
|
554
|
+
<!-- [DEMO2: Problem Solution](https://stackblitz.com/edit/vitejs-vite-xk9lxagc?file=README.md) -->
|
|
555
|
+
|
|
556
|
+
### Basic Syntax
|
|
557
|
+
|
|
558
|
+
```tsx
|
|
559
|
+
import remarkMath from 'remark-math';
|
|
560
|
+
import rehypeKatex from 'rehype-katex';
|
|
561
|
+
|
|
562
|
+
// 1. Enable math formula support
|
|
563
|
+
<MarkdownTyper reactMarkdownProps={{ remarkPlugins: [remarkMath], rehypePlugins: [rehypeKatex]}}>
|
|
564
|
+
# Math Formula Example
|
|
565
|
+
|
|
566
|
+
// Inline formula
|
|
567
|
+
This is an inline formula: $E = mc^2$
|
|
568
|
+
|
|
569
|
+
// Block formula
|
|
570
|
+
$$\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}$$
|
|
571
|
+
</MarkdownTyper>
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
### Delimiter Selection
|
|
575
|
+
|
|
576
|
+
```tsx
|
|
577
|
+
// Use dollar sign delimiter (default)
|
|
578
|
+
<MarkdownTyper
|
|
579
|
+
reactMarkdownProps={{ remarkPlugins: [remarkMath], rehypePlugins: [rehypeKatex]}}
|
|
580
|
+
math={{ splitSymbol: 'dollar' }}
|
|
581
|
+
>
|
|
582
|
+
Inline: $a + b = c$
|
|
583
|
+
Block: $$\sum_{i=1}^{n} x_i = x_1 + x_2 + \cdots + x_n$$
|
|
584
|
+
</MarkdownTyper>
|
|
585
|
+
|
|
586
|
+
// Use bracket delimiter
|
|
587
|
+
<MarkdownTyper
|
|
588
|
+
reactMarkdownProps={{ remarkPlugins: [remarkMath], rehypePlugins: [rehypeKatex]}}
|
|
589
|
+
math={{ splitSymbol: 'bracket' }}
|
|
590
|
+
>
|
|
591
|
+
Inline: \(a + b = c\)
|
|
592
|
+
Block: \[\sum_{i=1}^{n} x_i = x_1 + x_2 + \cdots + x_n\]
|
|
593
|
+
</MarkdownTyper>
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
### Streaming Math Formulas
|
|
597
|
+
|
|
598
|
+
```tsx
|
|
599
|
+
// Perfectly supports streaming output of math formulas
|
|
600
|
+
const mathContent = [
|
|
601
|
+
'Pythagorean theorem:',
|
|
602
|
+
'$a^2 + b^2 = c^2$',
|
|
603
|
+
'\n\n',
|
|
604
|
+
'Where:',
|
|
605
|
+
'- $a$ and $b$ are the legs\n',
|
|
606
|
+
'- $c$ is the hypotenuse\n\n',
|
|
607
|
+
'For the classic "3-4-5 triangle":\n',
|
|
608
|
+
'$c = \\sqrt{3^2 + 4^2} = \\sqrt{25} = 5$\n\n',
|
|
609
|
+
'This theorem has wide applications in geometry!',
|
|
610
|
+
];
|
|
611
|
+
|
|
612
|
+
mathContent.forEach((chunk) => {
|
|
613
|
+
markdownRef.current?.push(chunk, 'answer');
|
|
614
|
+
});
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
### Style Customization
|
|
618
|
+
|
|
619
|
+
```css
|
|
620
|
+
/* Math formula style customization */
|
|
621
|
+
.katex {
|
|
622
|
+
font-size: 1.1em;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
.katex-display {
|
|
626
|
+
margin: 1em 0;
|
|
627
|
+
text-align: center;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/* Dark theme adaptation */
|
|
631
|
+
[data-theme='dark'] .katex {
|
|
632
|
+
color: #e1e1e1;
|
|
633
|
+
}
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
---
|
|
637
|
+
|
|
638
|
+
## 🔌 Plugin System
|
|
639
|
+
|
|
640
|
+
See [react-markdown](https://github.com/remarkjs/react-markdown)
|
|
641
|
+
|
|
642
|
+
---
|
|
643
|
+
|
|
644
|
+
## 🎛️ Timer Modes Explained
|
|
645
|
+
|
|
646
|
+
### `requestAnimationFrame` Mode 🌟 (Recommended)
|
|
647
|
+
|
|
648
|
+
```typescript
|
|
649
|
+
// 🎯 Features
|
|
650
|
+
- Time-driven: Calculates character count based on real elapsed time
|
|
651
|
+
- Batch processing: Multiple characters per frame
|
|
652
|
+
- Frame sync: Syncs with browser 60fps refresh rate
|
|
653
|
+
- High-frequency optimization: Perfect for interval < 16ms
|
|
654
|
+
|
|
655
|
+
// 🎯 Use cases
|
|
656
|
+
- Default for modern web apps
|
|
657
|
+
- Pursue smooth animation
|
|
658
|
+
- High-frequency typing (interval > 0)
|
|
659
|
+
- AI real-time chat
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
### `setTimeout` Mode 📟 (Compatible)
|
|
663
|
+
|
|
664
|
+
```typescript
|
|
665
|
+
// 🎯 Features
|
|
666
|
+
- Single character: Processes one character at a time
|
|
667
|
+
- Fixed interval: Executes strictly by set time
|
|
668
|
+
- Rhythmic: Classic typewriter rhythm
|
|
669
|
+
- Precise control: For specific timing needs
|
|
670
|
+
|
|
671
|
+
// 🎯 Use cases
|
|
672
|
+
- Need precise timing
|
|
673
|
+
- Retro typewriter effect
|
|
674
|
+
- High compatibility scenarios
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
### 📊 Performance Comparison
|
|
678
|
+
|
|
679
|
+
| Feature | requestAnimationFrame | setTimeout |
|
|
680
|
+
| ------------- | ----------------------------- | ----------------- |
|
|
681
|
+
| **Char proc** | Multiple chars per frame | One char per call |
|
|
682
|
+
| **High freq** | ✅ Excellent (5ms → 3 chars) | ❌ May stutter |
|
|
683
|
+
| **Low freq** | ✅ Normal (100ms → 1 char/6f) | ✅ Precise |
|
|
684
|
+
| **Visual** | 🎬 Smooth animation | ⚡ Rhythmic |
|
|
685
|
+
| **Perf cost** | 🟢 Low (frame sync) | 🟡 Medium (timer) |
|
|
686
|
+
|
|
687
|
+
High frequency: use `requestAnimationFrame`, low frequency: use `setTimeout`
|
|
688
|
+
|
|
689
|
+
---
|
|
690
|
+
|
|
691
|
+
## 💡 Practical Examples
|
|
692
|
+
|
|
693
|
+
### 📝 AI Streaming Chat
|
|
694
|
+
|
|
695
|
+
<!-- [DEMO: 🔧 Try on StackBlitz](https://stackblitz.com/edit/vitejs-vite-2ri8kex3?file=src%2FApp.tsx) -->
|
|
696
|
+
|
|
697
|
+
````tsx
|
|
698
|
+
import { useRef } from 'react';
|
|
699
|
+
import { MarkdownCMD, MarkdownCMDRef } from 'react-markdown-typer';
|
|
700
|
+
|
|
701
|
+
function StreamingChat() {
|
|
702
|
+
const markdownRef = useRef<MarkdownCMDRef>(null);
|
|
703
|
+
|
|
704
|
+
// Simulate AI streaming response
|
|
705
|
+
const simulateAIResponse = async () => {
|
|
706
|
+
markdownRef.current?.clear();
|
|
707
|
+
|
|
708
|
+
// Thinking phase
|
|
709
|
+
markdownRef.current?.push('🤔 Analyzing your question...', 'thinking');
|
|
710
|
+
await delay(1000);
|
|
711
|
+
markdownRef.current?.push('\n\n✅ Analysis complete, starting answer', 'thinking');
|
|
712
|
+
|
|
713
|
+
// Streaming answer
|
|
714
|
+
const chunks = [
|
|
715
|
+
'# React 19 New Features\n\n',
|
|
716
|
+
'## 🚀 React Compiler\n',
|
|
717
|
+
'The highlight of React 19 is the introduction of **React Compiler**:\n\n',
|
|
718
|
+
'- 🎯 **Auto optimization**: No need for manual memo/useMemo\n',
|
|
719
|
+
'- ⚡ **Performance boost**: Compile-time optimization, zero runtime cost\n',
|
|
720
|
+
'- 🔧 **Backward compatible**: No code changes needed\n\n',
|
|
721
|
+
'## 📝 Actions Simplify Forms\n',
|
|
722
|
+
'The new Actions API makes form handling easier:\n\n',
|
|
723
|
+
'```tsx\n',
|
|
724
|
+
'function ContactForm({ action }) {\n',
|
|
725
|
+
' const [state, formAction] = useActionState(action, null);\n',
|
|
726
|
+
' return (\n',
|
|
727
|
+
' <form action={formAction}>\n',
|
|
728
|
+
' <input name="email" type="email" />\n',
|
|
729
|
+
' <button>Submit</button>\n',
|
|
730
|
+
' </form>\n',
|
|
731
|
+
' );\n',
|
|
732
|
+
'}\n',
|
|
733
|
+
'```\n\n',
|
|
734
|
+
'Hope this helps! 🎉',
|
|
735
|
+
];
|
|
736
|
+
|
|
737
|
+
for (const chunk of chunks) {
|
|
738
|
+
await delay(100);
|
|
739
|
+
markdownRef.current?.push(chunk, 'answer');
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
return (
|
|
744
|
+
<div className="chat-container">
|
|
745
|
+
<button onClick={simulateAIResponse}>🤖 Ask about React 19 features</button>
|
|
746
|
+
|
|
747
|
+
<MarkdownCMD ref={markdownRef} interval={10} timerType="requestAnimationFrame" onEnd={(data) => console.log('Paragraph done:', data)} />
|
|
748
|
+
</div>
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
753
|
+
````
|
|
754
|
+
|
|
755
|
+
### 🧮 Streaming Math Formula Rendering
|
|
756
|
+
|
|
757
|
+
```tsx
|
|
758
|
+
import remarkMath from 'remark-math';
|
|
759
|
+
import rehypeKatex from 'rehype-katex';
|
|
760
|
+
|
|
761
|
+
function MathStreamingDemo() {
|
|
762
|
+
const markdownRef = useRef<MarkdownCMDRef>(null);
|
|
763
|
+
|
|
764
|
+
const simulateMathResponse = async () => {
|
|
765
|
+
markdownRef.current?.clear();
|
|
766
|
+
|
|
767
|
+
const mathChunks = [
|
|
768
|
+
'# Pythagorean Theorem Explained\n\n',
|
|
769
|
+
'In a right triangle, the square of the hypotenuse equals the sum of the squares of the other two sides:\n\n',
|
|
770
|
+
'$a^2 + b^2 = c^2$\n\n',
|
|
771
|
+
'Where:\n',
|
|
772
|
+
'- $a$ and $b$ are the legs\n',
|
|
773
|
+
'- $c$ is the hypotenuse\n\n',
|
|
774
|
+
'For the classic "3-4-5 triangle":\n',
|
|
775
|
+
'$c = \\sqrt{3^2 + 4^2} = \\sqrt{25} = 5$\n\n',
|
|
776
|
+
'This theorem has wide applications in geometry!',
|
|
777
|
+
];
|
|
778
|
+
|
|
779
|
+
for (const chunk of mathChunks) {
|
|
780
|
+
await delay(150);
|
|
781
|
+
markdownRef.current?.push(chunk, 'answer');
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
return (
|
|
786
|
+
<div>
|
|
787
|
+
<button onClick={simulateMathResponse}>📐 Explain Pythagorean Theorem</button>
|
|
788
|
+
|
|
789
|
+
<MarkdownCMD
|
|
790
|
+
ref={markdownRef}
|
|
791
|
+
interval={20}
|
|
792
|
+
timerType="requestAnimationFrame"
|
|
793
|
+
reactMarkdownProps={{ remarkPlugins: [remarkMath], rehypePlugins: [rehypeKatex] }}
|
|
794
|
+
math={{ splitSymbol: 'dollar' }}
|
|
795
|
+
/>
|
|
796
|
+
</div>
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
### 🎯 Advanced Callback Control
|
|
802
|
+
|
|
803
|
+
```tsx
|
|
804
|
+
import { useRef, useState } from 'react';
|
|
805
|
+
import { MarkdownCMD, MarkdownCMDRef } from 'react-markdown-typer';
|
|
806
|
+
|
|
807
|
+
function AdvancedCallbackDemo() {
|
|
808
|
+
const markdownRef = useRef<MarkdownCMDRef>(null);
|
|
809
|
+
const [typingStats, setTypingStats] = useState({ progress: 0, currentChar: '', totalChars: 0 });
|
|
810
|
+
|
|
811
|
+
const handleBeforeTypedChar = async (data) => {
|
|
812
|
+
// Async operation before typing a character
|
|
813
|
+
console.log('About to type:', data.currentChar);
|
|
814
|
+
|
|
815
|
+
// You can do network requests, data validation, etc. here
|
|
816
|
+
if (data.currentChar === '!') {
|
|
817
|
+
await new Promise((resolve) => setTimeout(resolve, 500)); // Simulate delay
|
|
818
|
+
}
|
|
819
|
+
};
|
|
820
|
+
|
|
821
|
+
const handleTypedChar = (data) => {
|
|
822
|
+
// Update typing stats
|
|
823
|
+
setTypingStats({
|
|
824
|
+
progress: Math.round(data.percent),
|
|
825
|
+
currentChar: data.currentChar,
|
|
826
|
+
totalChars: data.currentIndex + 1,
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
// Add sound effects, animations, etc. here
|
|
830
|
+
if (data.currentChar === '.') {
|
|
831
|
+
// Play period sound effect
|
|
832
|
+
console.log('Play period sound');
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
const handleStart = (data) => {
|
|
837
|
+
console.log('Typing started:', data.currentChar);
|
|
838
|
+
};
|
|
839
|
+
|
|
840
|
+
const handleEnd = (data) => {
|
|
841
|
+
console.log('Typing finished:', data.str);
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
const startDemo = () => {
|
|
845
|
+
markdownRef.current?.clear();
|
|
846
|
+
markdownRef.current?.push(
|
|
847
|
+
'# Advanced Callback Demo\n\n' +
|
|
848
|
+
'This example shows how to use `onBeforeTypedChar` and `onTypedChar` callbacks:\n\n' +
|
|
849
|
+
'- 🎯 **Before typing callback**: Async operations before displaying a character\n' +
|
|
850
|
+
'- 📊 **After typing callback**: Real-time progress updates and effects\n' +
|
|
851
|
+
'- ⚡ **Performance**: Async operations without affecting typing smoothness\n\n' +
|
|
852
|
+
'Current progress: ' +
|
|
853
|
+
typingStats.progress +
|
|
854
|
+
'%\n' +
|
|
855
|
+
'Characters typed: ' +
|
|
856
|
+
typingStats.totalChars +
|
|
857
|
+
'\n\n' +
|
|
858
|
+
'This is a very powerful feature!',
|
|
859
|
+
'answer',
|
|
860
|
+
);
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
return (
|
|
864
|
+
<div>
|
|
865
|
+
<button onClick={startDemo}>🚀 Start Advanced Demo</button>
|
|
866
|
+
|
|
867
|
+
<div style={{ margin: '10px 0', padding: '10px', background: '#f5f5f5', borderRadius: '4px' }}>
|
|
868
|
+
<strong>Typing Stats:</strong> Progress {typingStats.progress}% | Current char: "{typingStats.currentChar}" | Total chars: {typingStats.totalChars}
|
|
869
|
+
</div>
|
|
870
|
+
|
|
871
|
+
<MarkdownCMD ref={markdownRef} interval={30} onBeforeTypedChar={handleBeforeTypedChar} onTypedChar={handleTypedChar} onStart={handleStart} onEnd={handleEnd} />
|
|
872
|
+
</div>
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
```
|