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 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
+ [![npm version](https://img.shields.io/npm/v/react-markdown-typer)](https://www.npmjs.com/package/react-markdown-typer)
12
+ [![npm downloads](https://img.shields.io/npm/dm/react-markdown-typer.svg)](https://www.npmjs.com/package/react-markdown-typer)
13
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/react-markdown-typer)](https://bundlephobia.com/package/react-markdown-typer)
14
+ [![React](https://img.shields.io/badge/React-16.8+-blue)](https://react.dev)
15
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue)](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
+ ```