react-markdown-typer 1.0.3 → 1.0.4-beta.0

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