ds-markdown 0.1.2-beta.3 → 0.1.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.
package/README.en.md CHANGED
@@ -18,6 +18,57 @@ A React component designed specifically for modern AI applications, providing sm
18
18
 
19
19
  ---
20
20
 
21
+ ## 📋 Table of Contents
22
+
23
+ - [✨ Core Features](#-core-features)
24
+ - [📦 Quick Installation](#-quick-installation)
25
+ - [🚀 5-Minute Quick Start](#-5-minute-quick-start)
26
+ - [Basic Usage](#basic-usage)
27
+ - [Disable Typing Animation](#disable-typing-animation)
28
+ - [Mathematical Formula Support](#mathematical-formula-support)
29
+ - [AI Conversation Scenario](#ai-conversation-scenario)
30
+ - [🎯 Advanced Callback Control](#-advanced-callback-control)
31
+ - [🔄 Restart Animation Demo](#-restart-animation-demo)
32
+ - [▶️ Manual Start Animation Demo](#️-manual-start-animation-demo)
33
+ - [📚 Complete API Documentation](#-complete-api-documentation)
34
+ - [🧮 Mathematical Formula Usage Guide](#-mathematical-formula-usage-guide)
35
+ - [🔌 Plugin System](#-plugin-system)
36
+ - [🎛️ Timer Mode Details](#️-timer-mode-details)
37
+ - [💡 Practical Examples](#-practical-examples)
38
+ - [🔧 Best Practices](#-best-practices)
39
+
40
+ ---
41
+
42
+ ## ❓ Why use ds-markdown?
43
+
44
+ - **Ultimate AI Chat Experience**
45
+ Faithfully recreates the typing animation and streaming response of leading AI chat interfaces (like DeepSeek), delivering a truly immersive "AI is thinking/answering" experience.
46
+
47
+ - **Perfect for Streaming Backend Data**
48
+ Many AI/LLM backends (OpenAI, DeepSeek, etc.) send data chunks containing multiple characters at once.
49
+ **ds-markdown automatically splits each chunk into single characters and animates them one by one, ensuring smooth typing even if the backend sends several characters at a time.**
50
+
51
+ - **Full Markdown & Math Formula Support**
52
+ Built-in KaTeX, supports all major Markdown syntax and math formulas—ideal for technical Q&A, education, and knowledge bases.
53
+
54
+ - **Excellent Developer Experience**
55
+ Rich imperative API, supports streaming data, async callbacks, and plugin extensions for flexible animation and content control.
56
+
57
+ - **Lightweight & High Performance**
58
+ Small bundle size, fast, mobile and desktop ready. The only core dependency is [react-markdown](https://github.com/remarkjs/react-markdown) (a widely used, mature Markdown renderer). No other heavy dependencies—works out of the box.
59
+
60
+ - **Multi-theme & Plugin Architecture**
61
+ Light/dark theme switching, remark/rehype plugin compatibility, and advanced extensibility.
62
+
63
+ - **Wide Range of Use Cases**
64
+ - AI chatbots/assistants
65
+ - Real-time Q&A/knowledge bases
66
+ - Education/math/programming content
67
+ - Product demos, interactive docs
68
+ - Any scenario needing "typewriter" animation and streaming Markdown rendering
69
+
70
+ ---
71
+
21
72
  ## ✨ Core Features
22
73
 
23
74
  ### 🤖 **AI Conversation Scenarios**
@@ -184,6 +235,227 @@ Let's explore these new features together!`);
184
235
  }
185
236
  ```
186
237
 
238
+ ### 🎯 Advanced Callback Control
239
+
240
+ ```tsx
241
+ import { useRef, useState } from 'react';
242
+ import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
243
+
244
+ function AdvancedCallbackDemo() {
245
+ const markdownRef = useRef<MarkdownCMDRef>(null);
246
+ const [typingStats, setTypingStats] = useState({ progress: 0, currentChar: '', totalChars: 0 });
247
+
248
+ const handleBeforeTypedChar = async (data) => {
249
+ // Perform async operations before character typing
250
+ console.log('About to type:', data.currentChar);
251
+
252
+ // Can perform network requests, data validation, etc.
253
+ if (data.currentChar === '!') {
254
+ await new Promise((resolve) => setTimeout(resolve, 500)); // Simulate delay
255
+ }
256
+ };
257
+
258
+ const handleTypedChar = (data) => {
259
+ // Update typing statistics
260
+ setTypingStats({
261
+ progress: Math.round(data.percent),
262
+ currentChar: data.currentChar,
263
+ totalChars: data.currentIndex + 1,
264
+ });
265
+
266
+ // Can add sound effects, animations, etc.
267
+ if (data.currentChar === '.') {
268
+ // Play period sound effect
269
+ console.log('Play period sound effect');
270
+ }
271
+ };
272
+
273
+ const handleStart = (data) => {
274
+ console.log('Start typing:', data.currentChar);
275
+ };
276
+
277
+ const handleEnd = (data) => {
278
+ console.log('Typing complete:', 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
+ '- 🎯 **Pre-typing callback**: Can perform async operations before character display\n' +
287
+ '- 📊 **Post-typing callback**: Can update progress in real-time and add effects\n' +
288
+ '- ⚡ **Performance optimization**: Supports 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
+ };
299
+
300
+ return (
301
+ <div>
302
+ <button onClick={startDemo}>🚀 Start Advanced Demo</button>
303
+
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>
307
+
308
+ <MarkdownCMD ref={markdownRef} interval={30} onBeforeTypedChar={handleBeforeTypedChar} onTypedChar={handleTypedChar} onStart={handleStart} onEnd={handleEnd} />
309
+ </div>
310
+ );
311
+ }
312
+ ```
313
+
314
+ ### 🔄 Restart Animation Demo
315
+
316
+ ```tsx
317
+ import { useRef, useState } from 'react';
318
+ import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
319
+
320
+ function RestartDemo() {
321
+ const markdownRef = useRef<MarkdownCMDRef>(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**: Can pause and resume at any time\n' +
332
+ '- 🎯 **Precise Control**: Complete control over animation playback 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 time start
348
+ markdownRef.current?.start();
349
+ setHasStarted(true);
350
+ }
351
+ setIsPlaying(true);
352
+ };
353
+
354
+ const handleStop = () => {
355
+ markdownRef.current?.stop();
356
+ setIsPlaying(false);
357
+ };
358
+
359
+ const handleResume = () => {
360
+ markdownRef.current?.resume();
361
+ setIsPlaying(true);
362
+ };
363
+
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
+ );
396
+ }
397
+ ```
398
+
399
+ ### ▶️ Manual Start Animation Demo
400
+
401
+ ```tsx
402
+ import { useRef, useState } from 'react';
403
+ import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
404
+
405
+ function StartDemo() {
406
+ const markdownRef = useRef<MarkdownCMDRef>(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`, need to manually call `start()`\n' +
416
+ '- ⏱️ **Delayed Start**: Can start animation after user interaction\n' +
417
+ '- 🎮 **Gamification**: Suitable for scenarios requiring user initiative\n\n' +
418
+ 'Click the "Start Animation" button to manually trigger the typing effect!',
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 time 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>State:</strong> {isPlaying ? '🟢 Animation Playing' : '🔴 Waiting to Start'}
451
+ </div>
452
+
453
+ <MarkdownCMD ref={markdownRef} interval={30} autoStartTyping={false} onEnd={handleEnd} />
454
+ </div>
455
+ );
456
+ }
457
+ ```
458
+
187
459
  ---
188
460
 
189
461
  ## 📚 Complete API Documentation
@@ -639,6 +911,7 @@ import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
639
911
  function RestartDemo() {
640
912
  const markdownRef = useRef<MarkdownCMDRef>(null);
641
913
  const [isPlaying, setIsPlaying] = useState(false);
914
+ const [hasStarted, setHasStarted] = useState(false);
642
915
 
643
916
  const startContent = () => {
644
917
  markdownRef.current?.clear();
@@ -658,7 +931,14 @@ function RestartDemo() {
658
931
  };
659
932
 
660
933
  const handleStart = () => {
661
- markdownRef.current?.start();
934
+ if (hasStarted) {
935
+ // If already started, restart
936
+ markdownRef.current?.restart();
937
+ } else {
938
+ // First time start
939
+ markdownRef.current?.start();
940
+ setHasStarted(true);
941
+ }
662
942
  setIsPlaying(true);
663
943
  };
664
944
 
@@ -686,7 +966,7 @@ function RestartDemo() {
686
966
  <div style={{ marginBottom: '10px', display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
687
967
  <button onClick={startContent}>🚀 Start Content</button>
688
968
  <button onClick={handleStart} disabled={isPlaying}>
689
- ▶️ Start
969
+ {hasStarted ? '🔄 Restart' : '▶️ Start'}
690
970
  </button>
691
971
  <button onClick={handleStop} disabled={!isPlaying}>
692
972
  ⏸️ Pause
@@ -716,6 +996,7 @@ import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
716
996
  function StartDemo() {
717
997
  const markdownRef = useRef<MarkdownCMDRef>(null);
718
998
  const [isPlaying, setIsPlaying] = useState(false);
999
+ const [hasStarted, setHasStarted] = useState(false);
719
1000
 
720
1001
  const loadContent = () => {
721
1002
  markdownRef.current?.clear();
@@ -732,7 +1013,14 @@ function StartDemo() {
732
1013
  };
733
1014
 
734
1015
  const handleStart = () => {
735
- markdownRef.current?.start();
1016
+ if (hasStarted) {
1017
+ // If already started, restart
1018
+ markdownRef.current?.restart();
1019
+ } else {
1020
+ // First time start
1021
+ markdownRef.current?.start();
1022
+ setHasStarted(true);
1023
+ }
736
1024
  setIsPlaying(true);
737
1025
  };
738
1026
 
@@ -745,7 +1033,7 @@ function StartDemo() {
745
1033
  <div style={{ marginBottom: '10px', display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
746
1034
  <button onClick={loadContent}>📝 Load Content</button>
747
1035
  <button onClick={handleStart} disabled={isPlaying}>
748
- ▶️ Start Animation
1036
+ {hasStarted ? '🔄 Restart' : '▶️ Start Animation'}
749
1037
  </button>
750
1038
  </div>
751
1039
 
package/README.ja.md CHANGED
@@ -18,6 +18,57 @@
18
18
 
19
19
  ---
20
20
 
21
+ ## 📋 目次
22
+
23
+ - [✨ コア機能](#-コア機能)
24
+ - [📦 クイックインストール](#-クイックインストール)
25
+ - [🚀 5分クイックスタート](#-5分クイックスタート)
26
+ - [基本的な使用法](#基本的な使用法)
27
+ - [タイピングアニメーションの無効化](#タイピングアニメーションの無効化)
28
+ - [数式サポート](#数式サポート)
29
+ - [AI 会話シナリオ](#ai-会話シナリオ)
30
+ - [🎯 高度なコールバック制御](#-高度なコールバック制御)
31
+ - [🔄 アニメーション再開デモ](#-アニメーション再開デモ)
32
+ - [▶️ 手動開始アニメーションデモ](#️-手動開始アニメーションデモ)
33
+ - [📚 完全 API ドキュメント](#-完全-api-ドキュメント)
34
+ - [🧮 数式使用ガイド](#-数式使用ガイド)
35
+ - [🔌 プラグインシステム](#-プラグインシステム)
36
+ - [🎛️ タイマーモード詳細](#️-タイマーモード詳細)
37
+ - [💡 実戦例](#-実戦例)
38
+ - [🔧 ベストプラクティス](#-ベストプラクティス)
39
+
40
+ ---
41
+
42
+ ## ❓ なぜ ds-markdown を使うのか?
43
+
44
+ - **AI チャット体験を完全再現**
45
+ DeepSeek などの主要な AI チャット UI のタイピングアニメーションとストリーミング応答を1:1で再現し、「AI が考え中/回答中」のリアルな体験を提供します。
46
+
47
+ - **バックエンドのストリーミングデータに完全対応**
48
+ 多くの AI/LLM バックエンド(OpenAI、DeepSeek など)は、一度に複数文字を含む chunk を送信します。
49
+ **ds-markdown は各 chunk を自動的に1文字ずつ分割し、どんなにまとめて送られても滑らかに1文字ずつアニメーション表示します。**
50
+
51
+ - **Markdown & 数式完全対応**
52
+ KaTeX 内蔵、主要な Markdown 構文と数式をすべてサポート。技術 Q&A、教育、ナレッジベースに最適。
53
+
54
+ - **優れた開発体験**
55
+ 豊富な命令型 API、ストリーミングデータ・非同期コールバック・プラグイン拡張に対応し、柔軟な制御が可能。
56
+
57
+ - **軽量・高性能**
58
+ 小さなバンドルサイズ、高速、モバイル・デスクトップ両対応。コア依存は [react-markdown](https://github.com/remarkjs/react-markdown)(業界標準の成熟した Markdown レンダラー)のみで、他に重い依存はありません。すぐに使えます。
59
+
60
+ - **多テーマ・プラグインアーキテクチャ**
61
+ ライト/ダークテーマ切替、remark/rehype プラグイン互換、拡張性抜群。
62
+
63
+ - **幅広い用途**
64
+ - AI チャットボット/アシスタント
65
+ - リアルタイム Q&A/ナレッジベース
66
+ - 教育/数学/プログラミングコンテンツ
67
+ - プロダクトデモ、インタラクティブドキュメント
68
+ - 「タイプライター」アニメーションやストリーミング Markdown が必要なあらゆる場面
69
+
70
+ ---
71
+
21
72
  ## ✨ コア機能
22
73
 
23
74
  ### 🤖 **AI 会話シナリオ**
@@ -179,6 +230,195 @@ React 19 は多くのエキサイティングな新機能をもたらします
179
230
  }
180
231
  ```
181
232
 
233
+ ### 🎯 高度なコールバック制御
234
+
235
+ ```tsx
236
+ import { useRef } from 'react';
237
+ import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
238
+
239
+ function AdvancedCallbackDemo() {
240
+ const markdownRef = useRef<MarkdownCMDRef>(null);
241
+
242
+ const handleStart = () => {
243
+ markdownRef.current?.start();
244
+ };
245
+
246
+ const handleStop = () => {
247
+ markdownRef.current?.stop();
248
+ };
249
+
250
+ const handleResume = () => {
251
+ markdownRef.current?.resume();
252
+ };
253
+
254
+ const handleRestart = () => {
255
+ markdownRef.current?.restart();
256
+ };
257
+
258
+ const handleEnd = (data: EndData) => {
259
+ console.log('セクション完了:', data);
260
+ };
261
+
262
+ return (
263
+ <div>
264
+ <div style={{ marginBottom: '10px', display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
265
+ <button onClick={handleStart}>🚀 コンテンツ開始</button>
266
+ <button onClick={handleStop}>⏸️ 一時停止</button>
267
+ <button onClick={handleResume}>▶️ 再開</button>
268
+ <button onClick={handleRestart}>🔄 再開</button>
269
+ </div>
270
+
271
+ <MarkdownCMD ref={markdownRef} interval={20} onEnd={handleEnd} />
272
+ </div>
273
+ );
274
+ }
275
+ ```
276
+
277
+ ### 🔄 アニメーション再開デモ
278
+
279
+ ```tsx
280
+ import { useRef, useState } from 'react';
281
+ import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
282
+
283
+ function RestartDemo() {
284
+ const markdownRef = useRef<MarkdownCMDRef>(null);
285
+ const [isPlaying, setIsPlaying] = useState(false);
286
+ const [hasStarted, setHasStarted] = useState(false);
287
+
288
+ const startContent = () => {
289
+ markdownRef.current?.clear();
290
+ markdownRef.current?.push(
291
+ '# アニメーション再開デモ\n\n' +
292
+ 'この例は `restart()` メソッドの使用方法を示しています:\n\n' +
293
+ '- 🔄 **再開**:現在のコンテンツを最初から再生\n' +
294
+ '- ⏸️ **一時停止/再開**:いつでも一時停止と再開が可能\n' +
295
+ '- 🎯 **精密制御**:アニメーション再生状態の完全制御\n\n' +
296
+ '現在の状態:' +
297
+ (isPlaying ? '再生中' : '一時停止') +
298
+ '\n\n' +
299
+ 'これは非常に実用的な機能です!',
300
+ 'answer',
301
+ );
302
+ setIsPlaying(true);
303
+ };
304
+
305
+ const handleStart = () => {
306
+ if (hasStarted) {
307
+ // 既に開始されている場合は再開
308
+ markdownRef.current?.restart();
309
+ } else {
310
+ // 初回開始
311
+ markdownRef.current?.start();
312
+ setHasStarted(true);
313
+ }
314
+ setIsPlaying(true);
315
+ };
316
+
317
+ const handleStop = () => {
318
+ markdownRef.current?.stop();
319
+ setIsPlaying(false);
320
+ };
321
+
322
+ const handleResume = () => {
323
+ markdownRef.current?.resume();
324
+ setIsPlaying(true);
325
+ };
326
+
327
+ const handleRestart = () => {
328
+ markdownRef.current?.restart();
329
+ setIsPlaying(true);
330
+ };
331
+
332
+ const handleEnd = () => {
333
+ setIsPlaying(false);
334
+ };
335
+
336
+ return (
337
+ <div>
338
+ <div style={{ marginBottom: '10px', display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
339
+ <button onClick={startContent}>🚀 コンテンツ開始</button>
340
+ <button onClick={handleStart} disabled={isPlaying}>
341
+ {hasStarted ? '🔄 再開' : '▶️ 開始'}
342
+ </button>
343
+ <button onClick={handleStop} disabled={!isPlaying}>
344
+ ⏸️ 一時停止
345
+ </button>
346
+ <button onClick={handleResume} disabled={isPlaying}>
347
+ ▶️ 再開
348
+ </button>
349
+ <button onClick={handleRestart}>🔄 再開</button>
350
+ </div>
351
+
352
+ <div style={{ margin: '10px 0', padding: '10px', background: '#f5f5f5', borderRadius: '4px' }}>
353
+ <strong>アニメーション状態:</strong> {isPlaying ? '🟢 再生中' : '🔴 一時停止'}
354
+ </div>
355
+
356
+ <MarkdownCMD ref={markdownRef} interval={25} onEnd={handleEnd} />
357
+ </div>
358
+ );
359
+ }
360
+ ```
361
+
362
+ ### ▶️ 手動開始アニメーションデモ
363
+
364
+ ```tsx
365
+ import { useRef, useState } from 'react';
366
+ import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
367
+
368
+ function StartDemo() {
369
+ const markdownRef = useRef<MarkdownCMDRef>(null);
370
+ const [isPlaying, setIsPlaying] = useState(false);
371
+ const [hasStarted, setHasStarted] = useState(false);
372
+
373
+ const loadContent = () => {
374
+ markdownRef.current?.clear();
375
+ markdownRef.current?.push(
376
+ '# 手動開始アニメーションデモ\n\n' +
377
+ 'この例は `start()` メソッドの使用方法を示しています:\n\n' +
378
+ '- 🎯 **手動制御**:`autoStartTyping=false` の場合、手動で `start()` を呼び出す必要があります\n' +
379
+ '- ⏱️ **遅延開始**:ユーザーインタラクション後にアニメーションを開始できます\n' +
380
+ '- 🎮 **ゲーミフィケーション**:ユーザーの積極性を必要とするシナリオに適しています\n\n' +
381
+ '"アニメーション開始"ボタンをクリックしてタイピング効果を手動でトリガーしてください!',
382
+ 'answer',
383
+ );
384
+ setIsPlaying(false);
385
+ };
386
+
387
+ const handleStart = () => {
388
+ if (hasStarted) {
389
+ // 既に開始されている場合は再開
390
+ markdownRef.current?.restart();
391
+ } else {
392
+ // 初回開始
393
+ markdownRef.current?.start();
394
+ setHasStarted(true);
395
+ }
396
+ setIsPlaying(true);
397
+ };
398
+
399
+ const handleEnd = () => {
400
+ setIsPlaying(false);
401
+ };
402
+
403
+ return (
404
+ <div>
405
+ <div style={{ marginBottom: '10px', display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
406
+ <button onClick={loadContent}>📝 コンテンツ読み込み</button>
407
+ <button onClick={handleStart} disabled={isPlaying}>
408
+ {hasStarted ? '🔄 再開' : '▶️ アニメーション開始'}
409
+ </button>
410
+ </div>
411
+
412
+ <div style={{ margin: '10px 0', padding: '10px', background: '#f5f5f5', borderRadius: '4px' }}>
413
+ <strong>状態:</strong> {isPlaying ? '🟢 アニメーション再生中' : '🔴 開始待機中'}
414
+ </div>
415
+
416
+ <MarkdownCMD ref={markdownRef} interval={30} autoStartTyping={false} onEnd={handleEnd} />
417
+ </div>
418
+ );
419
+ }
420
+ ```
421
+
182
422
  ---
183
423
 
184
424
  ## 📚 完全 API ドキュメント
package/README.ko.md CHANGED
@@ -18,6 +18,57 @@
18
18
 
19
19
  ---
20
20
 
21
+ ## 📋 목차
22
+
23
+ - [✨ 핵심 기능](#-핵심-기능)
24
+ - [📦 빠른 설치](#-빠른-설치)
25
+ - [🚀 5분 빠른 시작](#-5분-빠른-시작)
26
+ - [기본 사용법](#기본-사용법)
27
+ - [타이핑 애니메이션 비활성화](#타이핑-애니메이션-비활성화)
28
+ - [수학 공식 지원](#수학-공식-지원)
29
+ - [AI 대화 시나리오](#ai-대화-시나리오)
30
+ - [🎯 고급 콜백 제어](#-고급-콜백-제어)
31
+ - [🔄 애니메이션 재시작 데모](#-애니메이션-재시작-데모)
32
+ - [▶️ 수동 시작 애니메이션 데모](#️-수동-시작-애니메이션-데모)
33
+ - [📚 완전 API 문서](#-완전-api-문서)
34
+ - [🧮 수학 공식 사용 가이드](#-수학-공식-사용-가이드)
35
+ - [🔌 플러그인 시스템](#-플러그인-시스템)
36
+ - [🎛️ 타이머 모드 상세](#️-타이머-모드-상세)
37
+ - [💡 실전 예제](#-실전-예제)
38
+ - [🔧 모범 사례](#-모범-사례)
39
+
40
+ ---
41
+
42
+ ## ❓ 왜 ds-markdown을 써야 하나요?
43
+
44
+ - **AI 채팅 경험 완벽 재현**
45
+ DeepSeek 등 주요 AI 채팅 UI의 타이핑 애니메이션과 스트리밍 응답을 1:1로 재현, "AI가 생각/답변 중"인 진짜 같은 경험을 제공합니다.
46
+
47
+ - **백엔드 스트리밍 데이터 완벽 대응**
48
+ 많은 AI/LLM 백엔드(OpenAI, DeepSeek 등)는 한 번에 여러 글자가 포함된 chunk를 보냅니다.
49
+ **ds-markdown은 각 chunk를 자동으로 한 글자씩 분리해, 백엔드가 여러 글자를 한 번에 보내도 항상 부드럽게 한 글자씩 타이핑 애니메이션을 보여줍니다.**
50
+
51
+ - **완벽한 Markdown & 수식 지원**
52
+ KaTeX 내장, 모든 주요 Markdown 구문과 수식 지원—기술 Q&A, 교육, 지식베이스에 최적.
53
+
54
+ - **최고의 개발 경험**
55
+ 풍부한 명령형 API, 스트리밍 데이터, 비동기 콜백, 플러그인 확장 등으로 유연한 제어 가능.
56
+
57
+ - **가볍고 고성능**
58
+ 작은 용량, 빠른 속도, 모바일/데스크탑 모두 지원. 핵심 의존성은 [react-markdown](https://github.com/remarkjs/react-markdown) (업계 표준의 성숙한 Markdown 렌더러) 하나뿐이며, 그 외 무거운 의존성은 없습니다. 바로 사용 가능합니다.
59
+
60
+ - **다중 테마 및 플러그인 아키텍처**
61
+ 라이트/다크 테마 전환, remark/rehype 플러그인 호환, 고급 확장성.
62
+
63
+ - **다양한 활용 사례**
64
+ - AI 챗봇/어시스턴트
65
+ - 실시간 Q&A/지식베이스
66
+ - 교육/수학/프로그래밍 콘텐츠
67
+ - 제품 데모, 인터랙티브 문서
68
+ - "타자기" 애니메이션과 스트리밍 Markdown이 필요한 모든 상황
69
+
70
+ ---
71
+
21
72
  ## ✨ 핵심 기능
22
73
 
23
74
  ### 🤖 **AI 대화 시나리오**
@@ -607,3 +658,148 @@ import { MarkdownCMDRef } from 'ds-markdown';
607
658
  const ref = useRef<MarkdownCMDRef>(null);
608
659
  // 완전한 TypeScript 타입 힌트
609
660
  ```
661
+
662
+ ### 🔄 애니메이션 재시작 데모
663
+
664
+ ```tsx
665
+ import { useRef, useState } from 'react';
666
+ import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
667
+
668
+ function RestartDemo() {
669
+ const markdownRef = useRef<MarkdownCMDRef>(null);
670
+ const [isPlaying, setIsPlaying] = useState(false);
671
+ const [hasStarted, setHasStarted] = useState(false);
672
+
673
+ const startContent = () => {
674
+ markdownRef.current?.clear();
675
+ markdownRef.current?.push(
676
+ '# 애니메이션 재시작 데모\n\n' +
677
+ '이 예제는 `restart()` 메서드의 사용법을 보여줍니다:\n\n' +
678
+ '- 🔄 **재시작**: 현재 콘텐츠를 처음부터 재생\n' +
679
+ '- ⏸️ **일시정지/재개**: 언제든지 일시정지와 재개 가능\n' +
680
+ '- 🎯 **정밀 제어**: 애니메이션 재생 상태의 완전한 제어\n\n' +
681
+ '현재 상태: ' +
682
+ (isPlaying ? '재생 중' : '일시정지') +
683
+ '\n\n' +
684
+ '이는 매우 실용적인 기능입니다!',
685
+ 'answer',
686
+ );
687
+ setIsPlaying(true);
688
+ };
689
+
690
+ const handleStart = () => {
691
+ if (hasStarted) {
692
+ // 이미 시작된 경우 재시작
693
+ markdownRef.current?.restart();
694
+ } else {
695
+ // 첫 번째 시작
696
+ markdownRef.current?.start();
697
+ setHasStarted(true);
698
+ }
699
+ setIsPlaying(true);
700
+ };
701
+
702
+ const handleStop = () => {
703
+ markdownRef.current?.stop();
704
+ setIsPlaying(false);
705
+ };
706
+
707
+ const handleResume = () => {
708
+ markdownRef.current?.resume();
709
+ setIsPlaying(true);
710
+ };
711
+
712
+ const handleRestart = () => {
713
+ markdownRef.current?.restart();
714
+ setIsPlaying(true);
715
+ };
716
+
717
+ const handleEnd = () => {
718
+ setIsPlaying(false);
719
+ };
720
+
721
+ return (
722
+ <div>
723
+ <div style={{ marginBottom: '10px', display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
724
+ <button onClick={startContent}>🚀 콘텐츠 시작</button>
725
+ <button onClick={handleStart} disabled={isPlaying}>
726
+ {hasStarted ? '🔄 재시작' : '▶️ 시작'}
727
+ </button>
728
+ <button onClick={handleStop} disabled={!isPlaying}>
729
+ ⏸️ 일시정지
730
+ </button>
731
+ <button onClick={handleResume} disabled={isPlaying}>
732
+ ▶️ 재개
733
+ </button>
734
+ <button onClick={handleRestart}>🔄 재시작</button>
735
+ </div>
736
+
737
+ <div style={{ margin: '10px 0', padding: '10px', background: '#f5f5f5', borderRadius: '4px' }}>
738
+ <strong>애니메이션 상태:</strong> {isPlaying ? '🟢 재생 중' : '🔴 일시정지'}
739
+ </div>
740
+
741
+ <MarkdownCMD ref={markdownRef} interval={25} onEnd={handleEnd} />
742
+ </div>
743
+ );
744
+ }
745
+ ```
746
+
747
+ ### ▶️ 수동 시작 애니메이션 데모
748
+
749
+ ```tsx
750
+ import { useRef, useState } from 'react';
751
+ import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
752
+
753
+ function StartDemo() {
754
+ const markdownRef = useRef<MarkdownCMDRef>(null);
755
+ const [isPlaying, setIsPlaying] = useState(false);
756
+ const [hasStarted, setHasStarted] = useState(false);
757
+
758
+ const loadContent = () => {
759
+ markdownRef.current?.clear();
760
+ markdownRef.current?.push(
761
+ '# 수동 시작 애니메이션 데모\n\n' +
762
+ '이 예제는 `start()` 메서드의 사용법을 보여줍니다:\n\n' +
763
+ '- 🎯 **수동 제어**: `autoStartTyping=false`일 때 수동으로 `start()`를 호출해야 합니다\n' +
764
+ '- ⏱️ **지연 시작**: 사용자 상호작용 후 애니메이션을 시작할 수 있습니다\n' +
765
+ '- 🎮 **게임화**: 사용자의 적극성이 필요한 시나리오에 적합합니다\n\n' +
766
+ '"애니메이션 시작" 버튼을 클릭하여 타이핑 효과를 수동으로 트리거하세요!',
767
+ 'answer',
768
+ );
769
+ setIsPlaying(false);
770
+ };
771
+
772
+ const handleStart = () => {
773
+ if (hasStarted) {
774
+ // 이미 시작된 경우 재시작
775
+ markdownRef.current?.restart();
776
+ } else {
777
+ // 첫 번째 시작
778
+ markdownRef.current?.start();
779
+ setHasStarted(true);
780
+ }
781
+ setIsPlaying(true);
782
+ };
783
+
784
+ const handleEnd = () => {
785
+ setIsPlaying(false);
786
+ };
787
+
788
+ return (
789
+ <div>
790
+ <div style={{ marginBottom: '10px', display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
791
+ <button onClick={loadContent}>📝 콘텐츠 로드</button>
792
+ <button onClick={handleStart} disabled={isPlaying}>
793
+ {hasStarted ? '🔄 재시작' : '▶️ 애니메이션 시작'}
794
+ </button>
795
+ </div>
796
+
797
+ <div style={{ margin: '10px 0', padding: '10px', background: '#f5f5f5', borderRadius: '4px' }}>
798
+ <strong>상태:</strong> {isPlaying ? '🟢 애니메이션 재생 중' : '🔴 시작 대기 중'}
799
+ </div>
800
+
801
+ <MarkdownCMD ref={markdownRef} interval={30} autoStartTyping={false} onEnd={handleEnd} />
802
+ </div>
803
+ );
804
+ }
805
+ ```
package/README.md CHANGED
@@ -18,6 +18,57 @@
18
18
 
19
19
  ---
20
20
 
21
+ ## ❓ 为什么要用 ds-markdown?
22
+
23
+ - **AI 聊天体验极致还原**
24
+ 1:1 复刻 DeepSeek 等主流 AI 聊天界面的打字动画和流式响应,带来真实的"AI 正在思考/回答"体验,极大提升用户沉浸感。
25
+
26
+ - **后端流式数据完美适配**
27
+ 很多 AI/LLM 后端接口(如 OpenAI、DeepSeek 等)推送的数据 chunk 往往一次包含多个字符,普通打字机实现会出现卡顿、跳字等问题。
28
+ **ds-markdown 会自动将每个 chunk 拆分为单个字符,逐字流畅渲染动画,无论后端一次推送多少字,都能保证每个字都流畅打字。**
29
+
30
+ - **完整 Markdown & 数学公式支持**
31
+ 内置 KaTeX,支持所有主流 Markdown 语法和数学公式,适合技术问答、教育、知识库等内容丰富的应用。
32
+
33
+ - **极致开发体验**
34
+ 丰富的命令式 API,支持流式数据、异步回调、插件扩展,开发者可灵活控制动画和内容。
35
+
36
+ - **轻量高性能**
37
+ 体积小、性能优,适配移动端和桌面端。核心依赖 [react-markdown](https://github.com/remarkjs/react-markdown)(业界主流、成熟的 Markdown 渲染库),无其它重量级依赖,开箱即用。
38
+
39
+ - **多主题与插件化架构**
40
+ 支持亮/暗主题切换,兼容 remark/rehype 插件,满足个性化和高级扩展需求。
41
+
42
+ - **适用场景广泛**
43
+ - AI 聊天机器人/助手
44
+ - 实时问答/知识库
45
+ - 教育/数学/编程内容展示
46
+ - 产品演示、交互式文档
47
+ - 任何需要"打字机"动画和流式 Markdown 渲染的场景
48
+
49
+ ---
50
+
51
+ ## 📋 目录
52
+
53
+ - [✨ 核心特性](#-核心特性)
54
+ - [📦 快速安装](#-快速安装)
55
+ - [🚀 5分钟上手](#-5分钟上手)
56
+ - [基础用法](#基础用法)
57
+ - [禁用打字动画](#禁用打字动画)
58
+ - [数学公式支持](#数学公式支持)
59
+ - [AI 对话场景](#ai-对话场景)
60
+ - [🎯 高级回调控制](#-高级回调控制)
61
+ - [🔄 重新开始动画演示](#-重新开始动画演示)
62
+ - [▶️ 手动开始动画演示](#️-手动开始动画演示)
63
+ - [📚 完整 API 文档](#-完整-api-文档)
64
+ - [🧮 数学公式使用指南](#-数学公式使用指南)
65
+ - [🔌 插件系统](#-插件系统)
66
+ - [🎛️ 定时器模式详解](#️-定时器模式详解)
67
+ - [💡 实战示例](#-实战示例)
68
+ - [🔧 最佳实践](#-最佳实践)
69
+
70
+ ---
71
+
21
72
  ## ✨ 核心特性
22
73
 
23
74
  ### 🤖 **AI 对话场景**
@@ -267,6 +318,7 @@ import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
267
318
  function RestartDemo() {
268
319
  const markdownRef = useRef<MarkdownCMDRef>(null);
269
320
  const [isPlaying, setIsPlaying] = useState(false);
321
+ const [hasStarted, setHasStarted] = useState(false);
270
322
 
271
323
  const startContent = () => {
272
324
  markdownRef.current?.clear();
@@ -286,7 +338,14 @@ function RestartDemo() {
286
338
  };
287
339
 
288
340
  const handleStart = () => {
289
- markdownRef.current?.start();
341
+ if (hasStarted) {
342
+ // 如果已经开始过,则重新开始
343
+ markdownRef.current?.restart();
344
+ } else {
345
+ // 第一次开始
346
+ markdownRef.current?.start();
347
+ setHasStarted(true);
348
+ }
290
349
  setIsPlaying(true);
291
350
  };
292
351
 
@@ -314,7 +373,7 @@ function RestartDemo() {
314
373
  <div style={{ marginBottom: '10px', display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
315
374
  <button onClick={startContent}>🚀 开始内容</button>
316
375
  <button onClick={handleStart} disabled={isPlaying}>
317
- ▶️ 开始
376
+ {hasStarted ? '🔄 重新开始' : '▶️ 开始'}
318
377
  </button>
319
378
  <button onClick={handleStop} disabled={!isPlaying}>
320
379
  ⏸️ 暂停
@@ -344,6 +403,7 @@ import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
344
403
  function StartDemo() {
345
404
  const markdownRef = useRef<MarkdownCMDRef>(null);
346
405
  const [isPlaying, setIsPlaying] = useState(false);
406
+ const [hasStarted, setHasStarted] = useState(false);
347
407
 
348
408
  const loadContent = () => {
349
409
  markdownRef.current?.clear();
@@ -360,7 +420,14 @@ function StartDemo() {
360
420
  };
361
421
 
362
422
  const handleStart = () => {
363
- markdownRef.current?.start();
423
+ if (hasStarted) {
424
+ // 如果已经开始过,则重新开始
425
+ markdownRef.current?.restart();
426
+ } else {
427
+ // 第一次开始
428
+ markdownRef.current?.start();
429
+ setHasStarted(true);
430
+ }
364
431
  setIsPlaying(true);
365
432
  };
366
433
 
@@ -373,7 +440,7 @@ function StartDemo() {
373
440
  <div style={{ marginBottom: '10px', display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
374
441
  <button onClick={loadContent}>📝 加载内容</button>
375
442
  <button onClick={handleStart} disabled={isPlaying}>
376
- ▶️ 开始动画
443
+ {hasStarted ? '🔄 重新开始' : '▶️ 开始动画'}
377
444
  </button>
378
445
  </div>
379
446
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ds-markdown",
3
3
  "private": false,
4
- "version": "0.1.2-beta.3",
4
+ "version": "0.1.2",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "types": "./dist/cjs/index.d.ts",
7
7
  "module": "./dist/esm/index.js",
@@ -125,6 +125,6 @@
125
125
  "react-markdown"
126
126
  ],
127
127
  "publishConfig": {
128
- "tag": "beta"
128
+ "tag": "latest"
129
129
  }
130
130
  }