react-markdown-typer 0.0.1 โ†’ 0.0.2

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