@incremark/react 0.2.5 → 0.2.7

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/dist/index.d.ts +105 -33
  2. package/dist/index.js +364 -167
  3. package/package.json +5 -5
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { ParserOptions, AnimationEffect, TransformerPlugin, ParsedBlock, Root, IncrementalUpdate, IncremarkParser, SourceBlock, TransformerOptions, DisplayBlock, BlockTransformer, RootContent as RootContent$1 } from '@incremark/core';
1
+ import { ParserOptions, AnimationEffect, TransformerPlugin, ParsedBlock, Root, IncrementalUpdate, IncremarkParser, SourceBlock, TransformerOptions, DisplayBlock, BlockTransformer } from '@incremark/core';
2
2
  export { AnimationEffect, BlockTransformer, DisplayBlock, IncrementalUpdate, ParsedBlock, ParserOptions, Root, RootContent, SourceBlock, TransformerOptions, TransformerPlugin, TransformerState, allPlugins, cloneNode, codeBlockPlugin, countChars, createBlockTransformer, createPlugin, defaultPlugins, imagePlugin, mathPlugin, mermaidPlugin, sliceAst, thematicBreakPlugin } from '@incremark/core';
3
- import React, { ReactNode } from 'react';
3
+ import React$1, { ReactNode, ComponentType } from 'react';
4
4
  import { Definition, FootnoteDefinition, RootContent, PhrasingContent } from 'mdast';
5
5
  import * as _incremark_devtools from '@incremark/devtools';
6
6
  import { DevToolsOptions } from '@incremark/devtools';
@@ -69,7 +69,7 @@ interface DefinitionsProviderProps {
69
69
  * }
70
70
  * ```
71
71
  */
72
- declare const DefinitionsProvider: React.FC<DefinitionsProviderProps>;
72
+ declare const DefinitionsProvider: React$1.FC<DefinitionsProviderProps>;
73
73
  /**
74
74
  * useDefinitions Hook
75
75
  *
@@ -111,10 +111,9 @@ interface UseIncremarkOptions extends ParserOptions {
111
111
  /** 打字机配置,传入即创建 transformer(可通过 enabled 控制是否启用) */
112
112
  typewriter?: TypewriterOptions;
113
113
  }
114
- interface BlockWithStableId$1 extends ParsedBlock {
115
- stableId: string;
114
+ type RenderableBlock = ParsedBlock & {
116
115
  isLastPending?: boolean;
117
- }
116
+ };
118
117
  /** 打字机控制对象 */
119
118
  interface TypewriterControls {
120
119
  /** 是否启用 */
@@ -175,7 +174,7 @@ declare function useIncremark(options?: UseIncremarkOptions): {
175
174
  /** 当前完整的 AST */
176
175
  ast: Root;
177
176
  /** 用于渲染的 blocks(根据打字机设置自动处理) */
178
- blocks: BlockWithStableId$1[];
177
+ blocks: RenderableBlock[];
179
178
  /** 是否正在加载 */
180
179
  isLoading: boolean;
181
180
  /** 是否已完成(finalize) */
@@ -294,32 +293,81 @@ interface UseBlockTransformerReturn<T = unknown> {
294
293
  */
295
294
  declare function useBlockTransformer<T = unknown>(sourceBlocks: SourceBlock<T>[], options?: UseBlockTransformerOptions): UseBlockTransformerReturn<T>;
296
295
 
297
- interface BlockWithStableId extends ParsedBlock {
298
- stableId: string;
299
- isLastPending?: boolean;
296
+ type ComponentMap = Partial<Record<string, ComponentType<{
297
+ node: any;
298
+ }>>>;
299
+ /**
300
+ * 代码块配置
301
+ */
302
+ interface CodeBlockConfig$1 {
303
+ /** 是否从一开始就接管渲染,而不是等到 completed 状态 */
304
+ takeOver?: boolean;
305
+ }
306
+ interface IncremarkContentProps {
307
+ stream?: () => AsyncGenerator<string>;
308
+ content?: string;
309
+ components?: ComponentMap;
310
+ /** 自定义容器组件映射,key 为容器名称(如 'warning', 'info') */
311
+ customContainers?: Record<string, ComponentType<{
312
+ name: string;
313
+ options?: Record<string, any>;
314
+ children?: React.ReactNode;
315
+ }>>;
316
+ /** 自定义代码块组件映射,key 为代码语言名称(如 'echart', 'mermaid') */
317
+ customCodeBlocks?: Record<string, ComponentType<{
318
+ codeStr: string;
319
+ lang?: string;
320
+ completed?: boolean;
321
+ takeOver?: boolean;
322
+ }>>;
323
+ /** 代码块配置映射,key 为代码语言名称 */
324
+ codeBlockConfigs?: Record<string, CodeBlockConfig$1>;
325
+ isFinished?: boolean;
326
+ incremarkOptions?: UseIncremarkOptions;
327
+ pendingClass?: string;
328
+ showBlockStatus?: boolean;
329
+ }
330
+
331
+ /**
332
+ * 代码块配置
333
+ */
334
+ interface CodeBlockConfig {
335
+ /** 是否从一开始就接管渲染,而不是等到 completed 状态 */
336
+ takeOver?: boolean;
300
337
  }
301
338
  interface IncremarkProps {
302
339
  /** 要渲染的块列表 */
303
- blocks?: BlockWithStableId[];
340
+ blocks?: RenderableBlock[];
341
+ /** 内容是否完全显示完成(用于控制脚注等需要在内容完全显示后才出现的元素)
342
+ * 如果传入了 incremark,则会自动使用 incremark.isDisplayComplete,此 prop 被忽略 */
343
+ isDisplayComplete?: boolean;
304
344
  /** 自定义组件映射 */
305
- components?: Partial<Record<string, React.ComponentType<{
345
+ components?: Partial<Record<string, React$1.ComponentType<{
306
346
  node: any;
307
347
  }>>>;
308
348
  /** 自定义容器组件映射,key 为容器名称(如 'warning', 'info') */
309
- customContainers?: Record<string, React.ComponentType<{
349
+ customContainers?: Record<string, React$1.ComponentType<{
310
350
  name: string;
311
351
  options?: Record<string, any>;
312
- children?: React.ReactNode;
352
+ children?: React$1.ReactNode;
313
353
  }>>;
314
354
  /** 自定义代码块组件映射,key 为代码语言名称(如 'echart', 'mermaid') */
315
- customCodeBlocks?: Record<string, React.ComponentType<{
355
+ customCodeBlocks?: Record<string, React$1.ComponentType<{
316
356
  codeStr: string;
317
- lang?: string;
357
+ lang?: string | undefined;
358
+ completed?: boolean | undefined;
359
+ takeOver?: boolean | undefined;
318
360
  }>>;
361
+ /** 代码块配置映射,key 为代码语言名称 */
362
+ codeBlockConfigs?: Record<string, CodeBlockConfig>;
319
363
  /** 是否显示块状态(待处理块边框) */
320
364
  showBlockStatus?: boolean;
321
365
  /** 自定义类名 */
322
366
  className?: string;
367
+ /** 待处理块的样式类名 */
368
+ pendingClass?: string;
369
+ /** 已完成块的样式类名 */
370
+ completedClass?: string;
323
371
  /** 可选:useIncremark 返回的对象(用于自动提供 context) */
324
372
  incremark?: UseIncremarkReturn;
325
373
  }
@@ -337,7 +385,26 @@ interface IncremarkProps {
337
385
  * }
338
386
  * ```
339
387
  */
340
- declare const Incremark: React.FC<IncremarkProps>;
388
+ declare const Incremark: React$1.FC<IncremarkProps>;
389
+
390
+ /**
391
+ * IncremarkContent 组件
392
+ *
393
+ * 提供开箱即用的 Markdown 渲染体验,自动处理流式内容和普通内容
394
+ *
395
+ * @example
396
+ * ```tsx
397
+ * // 普通内容
398
+ * <IncremarkContent content={markdownString} />
399
+ *
400
+ * // 流式内容
401
+ * <IncremarkContent stream={() => streamGenerator()} />
402
+ *
403
+ * // 增量更新内容
404
+ * <IncremarkContent content={partialContent} isFinished={false} />
405
+ * ```
406
+ */
407
+ declare const IncremarkContent: React$1.FC<IncremarkContentProps>;
341
408
 
342
409
  /**
343
410
  * 容器节点类型定义
@@ -351,29 +418,34 @@ interface ContainerNode {
351
418
  }
352
419
 
353
420
  interface IncremarkRendererProps {
354
- node: RootContent$1 | ContainerNode;
355
- components?: Partial<Record<string, React.ComponentType<{
356
- node: RootContent$1;
421
+ node: RootContent | ContainerNode;
422
+ components?: Partial<Record<string, React$1.ComponentType<{
423
+ node: any;
357
424
  }>>>;
358
- customContainers?: Record<string, React.ComponentType<{
425
+ customContainers?: Record<string, React$1.ComponentType<{
359
426
  name: string;
360
427
  options?: Record<string, any>;
361
- children?: React.ReactNode;
428
+ children?: ReactNode;
362
429
  }>>;
363
- customCodeBlocks?: Record<string, React.ComponentType<{
430
+ customCodeBlocks?: Record<string, React$1.ComponentType<{
364
431
  codeStr: string;
365
432
  lang?: string;
433
+ completed?: boolean;
434
+ takeOver?: boolean;
366
435
  }>>;
436
+ codeBlockConfigs?: Record<string, {
437
+ takeOver?: boolean;
438
+ }>;
367
439
  blockStatus?: 'pending' | 'stable' | 'completed';
368
440
  }
369
441
  /**
370
442
  * 渲染单个 AST 节点
371
443
  */
372
- declare const IncremarkRenderer: React.FC<IncremarkRendererProps>;
444
+ declare const IncremarkRenderer: React$1.FC<IncremarkRendererProps>;
373
445
 
374
446
  interface AutoScrollContainerProps {
375
447
  /** 子元素 */
376
- children: React.ReactNode;
448
+ children: React$1.ReactNode;
377
449
  /** 是否启用自动滚动 */
378
450
  enabled?: boolean;
379
451
  /** 触发自动滚动的底部阈值(像素) */
@@ -381,7 +453,7 @@ interface AutoScrollContainerProps {
381
453
  /** 滚动行为 */
382
454
  behavior?: ScrollBehavior;
383
455
  /** 容器样式 */
384
- style?: React.CSSProperties;
456
+ style?: React$1.CSSProperties;
385
457
  /** 容器类名 */
386
458
  className?: string;
387
459
  }
@@ -411,7 +483,7 @@ interface AutoScrollContainerRef {
411
483
  * scrollRef.current?.scrollToBottom()
412
484
  * ```
413
485
  */
414
- declare const AutoScrollContainer: React.ForwardRefExoticComponent<AutoScrollContainerProps & React.RefAttributes<AutoScrollContainerRef>>;
486
+ declare const AutoScrollContainer: React$1.ForwardRefExoticComponent<AutoScrollContainerProps & React$1.RefAttributes<AutoScrollContainerRef>>;
415
487
 
416
488
  interface IncremarkInlineProps {
417
489
  nodes: PhrasingContent[];
@@ -431,7 +503,7 @@ interface IncremarkInlineProps {
431
503
  *
432
504
  * 注意:此组件与 Vue 版本保持完全一致的逻辑和结构
433
505
  */
434
- declare const IncremarkInline: React.FC<IncremarkInlineProps>;
506
+ declare const IncremarkInline: React$1.FC<IncremarkInlineProps>;
435
507
 
436
508
  /**
437
509
  * HtmlElementNode 类型定义(与 @incremark/core 中的定义一致)
@@ -455,7 +527,7 @@ interface IncremarkHtmlElementProps {
455
527
  *
456
528
  * 渲染结构化的 HTML 元素节点
457
529
  */
458
- declare const IncremarkHtmlElement: React.FC<IncremarkHtmlElementProps>;
530
+ declare const IncremarkHtmlElement: React$1.FC<IncremarkHtmlElementProps>;
459
531
 
460
532
  /**
461
533
  * 脚注列表组件
@@ -482,7 +554,7 @@ declare const IncremarkHtmlElement: React.FC<IncremarkHtmlElementProps>;
482
554
  * <IncremarkFootnotes />
483
555
  * ```
484
556
  */
485
- declare const IncremarkFootnotes: React.FC;
557
+ declare const IncremarkFootnotes: React$1.FC;
486
558
 
487
559
  /**
488
560
  * @file IncremarkContainerProvider - Incremark 容器级 Provider
@@ -514,7 +586,7 @@ interface IncremarkContainerProviderProps {
514
586
  * @param props - 组件参数
515
587
  * @returns ReactElement
516
588
  */
517
- declare const IncremarkContainerProvider: React.FC<IncremarkContainerProviderProps>;
589
+ declare const IncremarkContainerProvider: React$1.FC<IncremarkContainerProviderProps>;
518
590
 
519
591
  interface ThemeProviderProps {
520
592
  /** 主题配置,可以是:
@@ -546,6 +618,6 @@ interface ThemeProviderProps {
546
618
  * </ThemeProvider>
547
619
  * ```
548
620
  */
549
- declare const ThemeProvider: React.FC<ThemeProviderProps>;
621
+ declare const ThemeProvider: React$1.FC<ThemeProviderProps>;
550
622
 
551
- export { AutoScrollContainer, type AutoScrollContainerProps, type AutoScrollContainerRef, type DefinitionsContextValue, DefinitionsProvider, type DefinitionsProviderProps, type HtmlElementNode, Incremark, IncremarkContainerProvider, type IncremarkContainerProviderProps, IncremarkFootnotes, IncremarkHtmlElement, type IncremarkHtmlElementProps, IncremarkInline, type IncremarkInlineProps, type IncremarkProps, IncremarkRenderer, type IncremarkRendererProps, ThemeProvider, type ThemeProviderProps, type TypewriterControls, type TypewriterOptions, type UseBlockTransformerOptions, type UseBlockTransformerReturn, type UseDevToolsOptions, type UseIncremarkOptions, type UseIncremarkReturn, useBlockTransformer, useDefinitions, useDevTools, useIncremark };
623
+ export { AutoScrollContainer, type AutoScrollContainerProps, type AutoScrollContainerRef, type DefinitionsContextValue, DefinitionsProvider, type DefinitionsProviderProps, type HtmlElementNode, Incremark, IncremarkContainerProvider, type IncremarkContainerProviderProps, IncremarkContent, type IncremarkContentProps, IncremarkFootnotes, IncremarkHtmlElement, type IncremarkHtmlElementProps, IncremarkInline, type IncremarkInlineProps, type IncremarkProps, IncremarkRenderer, type IncremarkRendererProps, ThemeProvider, type ThemeProviderProps, type TypewriterControls, type TypewriterOptions, type UseBlockTransformerOptions, type UseBlockTransformerReturn, type UseDevToolsOptions, type UseIncremarkOptions, type UseIncremarkReturn, useBlockTransformer, useDefinitions, useDevTools, useIncremark };
package/dist/index.js CHANGED
@@ -193,17 +193,7 @@ function useTypewriter(options) {
193
193
  }, [sourceBlocks, transformer]);
194
194
  const blocks = useMemo(() => {
195
195
  if (!typewriterEnabled || !transformer) {
196
- const result = [];
197
- for (const block of completedBlocks) {
198
- result.push({ ...block, stableId: block.id });
199
- }
200
- for (let i = 0; i < pendingBlocks.length; i++) {
201
- result.push({
202
- ...pendingBlocks[i],
203
- stableId: `pending-${i}`
204
- });
205
- }
206
- return result;
196
+ return [...completedBlocks, ...pendingBlocks];
207
197
  }
208
198
  return displayBlocks.map((db, index) => {
209
199
  const isPending = !db.isDisplayComplete;
@@ -220,7 +210,6 @@ function useTypewriter(options) {
220
210
  }
221
211
  const block = {
222
212
  id: db.id,
223
- stableId: db.id,
224
213
  status: db.isDisplayComplete ? "completed" : "pending",
225
214
  isLastPending,
226
215
  node,
@@ -326,12 +315,34 @@ function useIncremark(options = {}) {
326
315
  const [pendingBlocks, setPendingBlocks] = useState3([]);
327
316
  const [isLoading, setIsLoading] = useState3(false);
328
317
  const [isFinalized, setIsFinalized] = useState3(false);
318
+ const handleUpdate = useCallback3(
319
+ (update, isFinalize) => {
320
+ setMarkdown(parser.getBuffer());
321
+ if (update.completed.length > 0) {
322
+ setCompletedBlocks((prev) => [...prev, ...update.completed]);
323
+ }
324
+ setPendingBlocks(update.pending);
325
+ if (isFinalize) {
326
+ setIsLoading(false);
327
+ setIsFinalized(true);
328
+ } else {
329
+ setIsLoading(true);
330
+ }
331
+ setFootnoteReferenceOrder(update.footnoteReferenceOrder);
332
+ },
333
+ [parser, setFootnoteReferenceOrder]
334
+ );
329
335
  const { blocks, typewriter, transformer, isAnimationComplete } = useTypewriter({
330
336
  typewriter: options.typewriter,
331
337
  completedBlocks,
332
338
  pendingBlocks
333
339
  });
334
- const isDisplayComplete = isFinalized && isAnimationComplete;
340
+ const isDisplayComplete = useMemo2(() => {
341
+ if (!options.typewriter || !typewriter.enabled) {
342
+ return isFinalized;
343
+ }
344
+ return isFinalized && isAnimationComplete;
345
+ }, [options.typewriter, typewriter.enabled, isFinalized, isAnimationComplete]);
335
346
  const ast = useMemo2(
336
347
  () => ({
337
348
  type: "root",
@@ -341,30 +352,17 @@ function useIncremark(options = {}) {
341
352
  );
342
353
  const append = useCallback3(
343
354
  (chunk) => {
344
- setIsLoading(true);
345
355
  const update = parser.append(chunk);
346
- setMarkdown(parser.getBuffer());
347
- if (update.completed.length > 0) {
348
- setCompletedBlocks((prev) => [...prev, ...update.completed]);
349
- }
350
- setPendingBlocks(update.pending);
351
- setFootnoteReferenceOrder(update.footnoteReferenceOrder);
356
+ handleUpdate(update, false);
352
357
  return update;
353
358
  },
354
- [parser, setFootnoteReferenceOrder]
359
+ [parser, handleUpdate]
355
360
  );
356
361
  const finalize = useCallback3(() => {
357
362
  const update = parser.finalize();
358
- setMarkdown(parser.getBuffer());
359
- if (update.completed.length > 0) {
360
- setCompletedBlocks((prev) => [...prev, ...update.completed]);
361
- }
362
- setPendingBlocks([]);
363
- setIsLoading(false);
364
- setIsFinalized(true);
365
- setFootnoteReferenceOrder(update.footnoteReferenceOrder);
363
+ handleUpdate(update, true);
366
364
  return update;
367
- }, [parser, setFootnoteReferenceOrder]);
365
+ }, [parser, handleUpdate]);
368
366
  const abort = useCallback3(() => {
369
367
  return finalize();
370
368
  }, [finalize]);
@@ -443,8 +441,8 @@ function useDevTools(incremark, options = {}) {
443
441
  devtoolsRef.current = devtools;
444
442
  incremark.parser.setOnChange((state) => {
445
443
  const blocks = [
446
- ...state.completedBlocks.map((b) => ({ ...b, stableId: b.id })),
447
- ...state.pendingBlocks.map((b, i) => ({ ...b, stableId: `pending-${i}` }))
444
+ ...state.completedBlocks,
445
+ ...state.pendingBlocks
448
446
  ];
449
447
  devtools.update({
450
448
  blocks,
@@ -538,9 +536,6 @@ function useBlockTransformer(sourceBlocks, options = {}) {
538
536
  };
539
537
  }
540
538
 
541
- // src/components/IncremarkRenderer.tsx
542
- import React6 from "react";
543
-
544
539
  // src/components/IncremarkInline.tsx
545
540
  import React3 from "react";
546
541
  import {
@@ -838,37 +833,169 @@ var IncremarkParagraph = ({ node }) => {
838
833
  };
839
834
 
840
835
  // src/components/IncremarkCode.tsx
841
- import React4, { useState as useState5, useEffect as useEffect4, useRef as useRef5, useCallback as useCallback5 } from "react";
836
+ import React5, { useState as useState5, useEffect as useEffect4, useRef as useRef5, useCallback as useCallback5 } from "react";
837
+
838
+ // src/hooks/useShiki.ts
839
+ import React4 from "react";
840
+ var ShikiManager = class _ShikiManager {
841
+ static instance = null;
842
+ /** 存储 highlighter 实例,key 为主题名称 */
843
+ highlighters = /* @__PURE__ */ new Map();
844
+ constructor() {
845
+ }
846
+ static getInstance() {
847
+ if (!_ShikiManager.instance) {
848
+ _ShikiManager.instance = new _ShikiManager();
849
+ }
850
+ return _ShikiManager.instance;
851
+ }
852
+ /**
853
+ * 获取或创建 highlighter
854
+ * @param theme 主题名称
855
+ * @returns highlighter 实例
856
+ */
857
+ async getHighlighter(theme) {
858
+ if (this.highlighters.has(theme)) {
859
+ return this.highlighters.get(theme);
860
+ }
861
+ const { createHighlighter } = await import("shiki");
862
+ const highlighter = await createHighlighter({
863
+ themes: [theme],
864
+ langs: []
865
+ });
866
+ const info = {
867
+ highlighter,
868
+ loadedLanguages: /* @__PURE__ */ new Set(),
869
+ loadedThemes: /* @__PURE__ */ new Set([theme])
870
+ };
871
+ this.highlighters.set(theme, info);
872
+ return info;
873
+ }
874
+ /**
875
+ * 加载语言(按需)
876
+ * @param theme 主题名称
877
+ * @param lang 语言名称
878
+ */
879
+ async loadLanguage(theme, lang) {
880
+ const info = this.highlighters.get(theme);
881
+ if (!info || info.loadedLanguages.has(lang)) return;
882
+ try {
883
+ await info.highlighter.loadLanguage(lang);
884
+ info.loadedLanguages.add(lang);
885
+ } catch {
886
+ }
887
+ }
888
+ /**
889
+ * 加载主题(按需)
890
+ * @param theme 主题名称
891
+ */
892
+ async loadTheme(theme) {
893
+ const info = this.highlighters.get(theme);
894
+ if (!info || info.loadedThemes.has(theme)) return;
895
+ try {
896
+ await info.highlighter.loadTheme(theme);
897
+ info.loadedThemes.add(theme);
898
+ } catch {
899
+ }
900
+ }
901
+ /**
902
+ * 高亮代码
903
+ * @param theme 主题名称
904
+ * @param code 代码内容
905
+ * @param lang 语言名称
906
+ * @param fallbackTheme 回退主题
907
+ * @returns 高亮后的 HTML
908
+ */
909
+ async codeToHtml(theme, code, lang, fallbackTheme) {
910
+ const info = this.highlighters.get(theme);
911
+ if (!info) throw new Error("Highlighter not found");
912
+ const actualLang = info.loadedLanguages.has(lang) ? lang : "text";
913
+ const actualTheme = info.loadedThemes.has(theme) ? theme : fallbackTheme;
914
+ return info.highlighter.codeToHtml(code, {
915
+ lang: actualLang,
916
+ theme: actualTheme
917
+ });
918
+ }
919
+ /**
920
+ * 清理所有 highlighter(应用退出或需要重置时调用)
921
+ */
922
+ disposeAll() {
923
+ for (const [, info] of this.highlighters) {
924
+ if (info.highlighter?.dispose) {
925
+ info.highlighter.dispose();
926
+ }
927
+ }
928
+ this.highlighters.clear();
929
+ }
930
+ };
931
+ var shikiManager = ShikiManager.getInstance();
932
+ function useShiki(theme) {
933
+ const [isHighlighting, setIsHighlighting] = React4.useState(false);
934
+ const highlighterInfoRef = React4.useRef(null);
935
+ const getHighlighter = React4.useCallback(async () => {
936
+ if (!highlighterInfoRef.current) {
937
+ highlighterInfoRef.current = await shikiManager.getHighlighter(theme);
938
+ }
939
+ return highlighterInfoRef.current;
940
+ }, [theme]);
941
+ const highlight = React4.useCallback(async (code, lang, fallbackTheme) => {
942
+ setIsHighlighting(true);
943
+ try {
944
+ const info = await getHighlighter();
945
+ if (!info.loadedLanguages.has(lang) && lang !== "text") {
946
+ await shikiManager.loadLanguage(theme, lang);
947
+ }
948
+ if (!info.loadedThemes.has(theme)) {
949
+ await shikiManager.loadTheme(theme);
950
+ }
951
+ return await shikiManager.codeToHtml(theme, code, lang, fallbackTheme);
952
+ } catch (e) {
953
+ throw e;
954
+ } finally {
955
+ setIsHighlighting(false);
956
+ }
957
+ }, [getHighlighter, theme]);
958
+ return {
959
+ isHighlighting,
960
+ highlight
961
+ };
962
+ }
963
+
964
+ // src/components/IncremarkCode.tsx
842
965
  import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
843
966
  var IncremarkCode = ({
844
967
  node,
845
968
  theme = "github-dark",
969
+ fallbackTheme = "github-dark",
846
970
  disableHighlight = false,
847
971
  mermaidDelay = 500,
848
972
  customCodeBlocks,
849
- blockStatus = "completed"
973
+ blockStatus = "completed",
974
+ codeBlockConfigs
850
975
  }) => {
851
976
  const [copied, setCopied] = useState5(false);
852
977
  const [highlightedHtml, setHighlightedHtml] = useState5("");
853
- const [isHighlighting, setIsHighlighting] = useState5(false);
854
978
  const [mermaidSvg, setMermaidSvg] = useState5("");
855
979
  const [mermaidLoading, setMermaidLoading] = useState5(false);
856
980
  const [mermaidViewMode, setMermaidViewMode] = useState5("preview");
857
981
  const mermaidRef = useRef5(null);
858
- const highlighterRef = useRef5(null);
859
982
  const mermaidTimerRef = useRef5(null);
860
- const loadedLanguagesRef = useRef5(/* @__PURE__ */ new Set());
861
- const loadedThemesRef = useRef5(/* @__PURE__ */ new Set());
862
983
  const language = node.lang || "text";
863
984
  const code = node.value;
864
985
  const isMermaid = language === "mermaid";
865
- const CustomCodeBlock = React4.useMemo(() => {
866
- if (blockStatus === "pending") {
986
+ const { isHighlighting, highlight } = useShiki(theme);
987
+ const CustomCodeBlock = React5.useMemo(() => {
988
+ const component = customCodeBlocks?.[language];
989
+ if (!component) return null;
990
+ const config = codeBlockConfigs?.[language];
991
+ if (config?.takeOver) {
992
+ return component;
993
+ }
994
+ if (blockStatus !== "completed") {
867
995
  return null;
868
996
  }
869
- return customCodeBlocks?.[language] || null;
870
- }, [customCodeBlocks, language, blockStatus]);
871
- const useCustomComponent = !!CustomCodeBlock;
997
+ return component;
998
+ }, [customCodeBlocks, language, blockStatus, codeBlockConfigs]);
872
999
  const toggleMermaidView = useCallback5(() => {
873
1000
  setMermaidViewMode((prev) => prev === "preview" ? "source" : "preview");
874
1001
  }, []);
@@ -889,7 +1016,8 @@ var IncremarkCode = ({
889
1016
  mermaidRef.current.initialize({
890
1017
  startOnLoad: false,
891
1018
  theme: "dark",
892
- securityLevel: "loose"
1019
+ securityLevel: "loose",
1020
+ suppressErrorRendering: true
893
1021
  });
894
1022
  }
895
1023
  const mermaid = mermaidRef.current;
@@ -912,7 +1040,7 @@ var IncremarkCode = ({
912
1040
  doRenderMermaid();
913
1041
  }, mermaidDelay);
914
1042
  }, [isMermaid, code, mermaidDelay, doRenderMermaid]);
915
- const highlight = useCallback5(async () => {
1043
+ const doHighlight = useCallback5(async () => {
916
1044
  if (isMermaid) {
917
1045
  scheduleRenderMermaid();
918
1046
  return;
@@ -921,46 +1049,16 @@ var IncremarkCode = ({
921
1049
  setHighlightedHtml("");
922
1050
  return;
923
1051
  }
924
- setIsHighlighting(true);
925
1052
  try {
926
- if (!highlighterRef.current) {
927
- const { createHighlighter } = await import("shiki");
928
- highlighterRef.current = await createHighlighter({
929
- themes: [theme],
930
- langs: []
931
- });
932
- loadedThemesRef.current.add(theme);
933
- }
934
- const highlighter = highlighterRef.current;
935
- const lang = language;
936
- if (!loadedLanguagesRef.current.has(lang) && lang !== "text") {
937
- try {
938
- await highlighter.loadLanguage(lang);
939
- loadedLanguagesRef.current.add(lang);
940
- } catch {
941
- }
942
- }
943
- if (!loadedThemesRef.current.has(theme)) {
944
- try {
945
- await highlighter.loadTheme(theme);
946
- loadedThemesRef.current.add(theme);
947
- } catch {
948
- }
949
- }
950
- const html = highlighter.codeToHtml(code, {
951
- lang: loadedLanguagesRef.current.has(lang) ? lang : "text",
952
- theme: loadedThemesRef.current.has(theme) ? theme : "github-dark"
953
- });
1053
+ const html = await highlight(code, language, fallbackTheme);
954
1054
  setHighlightedHtml(html);
955
1055
  } catch {
956
1056
  setHighlightedHtml("");
957
- } finally {
958
- setIsHighlighting(false);
959
1057
  }
960
- }, [code, language, theme, disableHighlight, isMermaid, scheduleRenderMermaid]);
1058
+ }, [code, language, fallbackTheme, disableHighlight, isMermaid, highlight, scheduleRenderMermaid]);
961
1059
  useEffect4(() => {
962
- highlight();
963
- }, [highlight]);
1060
+ doHighlight();
1061
+ }, [doHighlight]);
964
1062
  useEffect4(() => {
965
1063
  return () => {
966
1064
  if (mermaidTimerRef.current) {
@@ -968,8 +1066,17 @@ var IncremarkCode = ({
968
1066
  }
969
1067
  };
970
1068
  }, []);
971
- if (useCustomComponent && CustomCodeBlock) {
972
- return /* @__PURE__ */ jsx6(CustomCodeBlock, { codeStr: code, lang: language });
1069
+ if (CustomCodeBlock) {
1070
+ const config = codeBlockConfigs?.[language];
1071
+ return /* @__PURE__ */ jsx6(
1072
+ CustomCodeBlock,
1073
+ {
1074
+ codeStr: code,
1075
+ lang: language,
1076
+ completed: blockStatus === "completed",
1077
+ takeOver: config?.takeOver
1078
+ }
1079
+ );
973
1080
  }
974
1081
  if (isMermaid) {
975
1082
  return /* @__PURE__ */ jsxs2("div", { className: "incremark-mermaid", children: [
@@ -1002,6 +1109,7 @@ var IncremarkCode = ({
1002
1109
  };
1003
1110
 
1004
1111
  // src/components/IncremarkList.tsx
1112
+ import React6 from "react";
1005
1113
  import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
1006
1114
  function getItemInlineContent(item) {
1007
1115
  const firstChild = item.children[0];
@@ -1021,7 +1129,7 @@ function getItemBlockChildren(item) {
1021
1129
  var IncremarkList = ({ node }) => {
1022
1130
  const Tag = node.ordered ? "ol" : "ul";
1023
1131
  const isTaskList = node.children?.some((item) => item.checked !== null && item.checked !== void 0);
1024
- return /* @__PURE__ */ jsx7(Tag, { className: `incremark-list ${isTaskList ? "task-list" : ""}`, children: node.children?.map((item, index) => {
1132
+ return /* @__PURE__ */ jsx7(Tag, { className: `incremark-list ${isTaskList ? "task-list" : ""}`, start: node.start || void 0, children: node.children?.map((item, index) => {
1025
1133
  const isTaskItem = item.checked !== null && item.checked !== void 0;
1026
1134
  const inlineContent = getItemInlineContent(item);
1027
1135
  const blockChildren = getItemBlockChildren(item);
@@ -1041,25 +1149,16 @@ var IncremarkList = ({ node }) => {
1041
1149
  }
1042
1150
  return /* @__PURE__ */ jsxs3("li", { className: "incremark-list-item", children: [
1043
1151
  /* @__PURE__ */ jsx7(IncremarkInline, { nodes: inlineContent }),
1044
- blockChildren.map((child, childIndex) => {
1045
- if (child.type === "list") {
1046
- return /* @__PURE__ */ jsx7(IncremarkList, { node: child }, childIndex);
1047
- }
1048
- return null;
1049
- })
1152
+ blockChildren.map((child, childIndex) => /* @__PURE__ */ jsx7(React6.Fragment, { children: /* @__PURE__ */ jsx7(IncremarkRenderer, { node: child }) }, childIndex))
1050
1153
  ] }, index);
1051
1154
  }) });
1052
1155
  };
1053
1156
 
1054
1157
  // src/components/IncremarkBlockquote.tsx
1158
+ import React7 from "react";
1055
1159
  import { jsx as jsx8 } from "react/jsx-runtime";
1056
1160
  var IncremarkBlockquote = ({ node }) => {
1057
- return /* @__PURE__ */ jsx8("blockquote", { className: "incremark-blockquote", children: node.children.map((child, index) => {
1058
- if (child.type === "paragraph") {
1059
- return /* @__PURE__ */ jsx8(IncremarkParagraph, { node: child }, index);
1060
- }
1061
- return /* @__PURE__ */ jsx8("div", { className: "unknown-child", children: child.type }, index);
1062
- }) });
1161
+ return /* @__PURE__ */ jsx8("blockquote", { className: "incremark-blockquote", children: node.children.map((child, index) => /* @__PURE__ */ jsx8(React7.Fragment, { children: /* @__PURE__ */ jsx8(IncremarkRenderer, { node: child }) }, index)) });
1063
1162
  };
1064
1163
 
1065
1164
  // src/components/IncremarkTable.tsx
@@ -1156,17 +1255,8 @@ var IncremarkMath = ({
1156
1255
  return /* @__PURE__ */ jsx11("div", { className: "incremark-math-block", children: renderedHtml && !isLoading ? /* @__PURE__ */ jsx11("div", { className: "math-rendered", dangerouslySetInnerHTML: { __html: renderedHtml } }) : /* @__PURE__ */ jsx11("pre", { className: "math-source-block", children: /* @__PURE__ */ jsx11("code", { children: formula }) }) });
1157
1256
  };
1158
1257
 
1159
- // src/components/IncremarkDefault.tsx
1160
- import { jsx as jsx12, jsxs as jsxs5 } from "react/jsx-runtime";
1161
- var IncremarkDefault = ({ node }) => {
1162
- return /* @__PURE__ */ jsxs5("div", { className: "incremark-default", children: [
1163
- /* @__PURE__ */ jsx12("span", { className: "type-badge", children: node.type }),
1164
- /* @__PURE__ */ jsx12("pre", { children: JSON.stringify(node, null, 2) })
1165
- ] });
1166
- };
1167
-
1168
1258
  // src/components/IncremarkContainer.tsx
1169
- import { jsx as jsx13 } from "react/jsx-runtime";
1259
+ import { jsx as jsx12 } from "react/jsx-runtime";
1170
1260
  function parseOptions(attributes) {
1171
1261
  if (!attributes) return {};
1172
1262
  const options = {};
@@ -1184,52 +1274,57 @@ var IncremarkContainer = ({ node, customContainers }) => {
1184
1274
  const options = parseOptions(node.attributes);
1185
1275
  const CustomContainer = customContainers?.[containerName];
1186
1276
  if (CustomContainer) {
1187
- return /* @__PURE__ */ jsx13(CustomContainer, { name: containerName, options, children: node.children?.map((child, index) => /* @__PURE__ */ jsx13(IncremarkRenderer, { node: child }, index)) });
1277
+ return /* @__PURE__ */ jsx12(CustomContainer, { name: containerName, options, children: node.children?.map((child, index) => /* @__PURE__ */ jsx12(IncremarkRenderer, { node: child }, index)) });
1188
1278
  }
1189
- return /* @__PURE__ */ jsx13("div", { className: `incremark-container incremark-container-${containerName}`, children: node.children && node.children.length > 0 && /* @__PURE__ */ jsx13("div", { className: "incremark-container-content", children: node.children.map((child, index) => /* @__PURE__ */ jsx13(IncremarkRenderer, { node: child }, index)) }) });
1279
+ return /* @__PURE__ */ jsx12("div", { className: `incremark-container incremark-container-${containerName}`, children: node.children && node.children.length > 0 && /* @__PURE__ */ jsx12("div", { className: "incremark-container-content", children: node.children.map((child, index) => /* @__PURE__ */ jsx12(IncremarkRenderer, { node: child }, index)) }) });
1190
1280
  };
1191
1281
 
1192
- // src/components/IncremarkRenderer.tsx
1193
- import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs6 } from "react/jsx-runtime";
1194
- var DefaultHeading = ({ node }) => {
1195
- return /* @__PURE__ */ jsx14(IncremarkHeading, { node });
1282
+ // src/components/IncremarkDefault.tsx
1283
+ import { jsx as jsx13, jsxs as jsxs5 } from "react/jsx-runtime";
1284
+ var IncremarkDefault = ({ node }) => {
1285
+ return /* @__PURE__ */ jsxs5("div", { className: "incremark-default", children: [
1286
+ /* @__PURE__ */ jsx13("span", { className: "type-badge", children: node.type }),
1287
+ /* @__PURE__ */ jsx13("pre", { children: JSON.stringify(node, null, 2) })
1288
+ ] });
1196
1289
  };
1197
- var DefaultParagraph = ({ node }) => /* @__PURE__ */ jsx14(IncremarkParagraph, { node });
1198
- var DefaultCode = ({ node }) => /* @__PURE__ */ jsx14(IncremarkCode, { node });
1199
- var DefaultList = ({ node }) => /* @__PURE__ */ jsx14(IncremarkList, { node });
1200
- var DefaultBlockquote = ({ node }) => /* @__PURE__ */ jsx14(IncremarkBlockquote, { node });
1201
- var DefaultTable = ({ node }) => /* @__PURE__ */ jsx14(IncremarkTable, { node });
1202
- var DefaultThematicBreak = () => /* @__PURE__ */ jsx14(IncremarkThematicBreak, {});
1203
- var DefaultMath = ({ node }) => /* @__PURE__ */ jsx14(IncremarkMath, { node });
1204
- var DefaultHtmlElement = ({ node }) => /* @__PURE__ */ jsx14(IncremarkHtmlElement, { node });
1205
- var DefaultDefault = ({ node }) => /* @__PURE__ */ jsx14(IncremarkDefault, { node });
1290
+
1291
+ // src/components/IncremarkRenderer.tsx
1292
+ import { jsx as jsx14 } from "react/jsx-runtime";
1206
1293
  var defaultComponents = {
1207
- heading: DefaultHeading,
1208
- paragraph: DefaultParagraph,
1209
- code: DefaultCode,
1210
- list: DefaultList,
1211
- blockquote: DefaultBlockquote,
1212
- table: DefaultTable,
1213
- thematicBreak: DefaultThematicBreak,
1214
- math: DefaultMath,
1215
- inlineMath: DefaultMath,
1216
- htmlElement: DefaultHtmlElement,
1217
- default: DefaultDefault
1294
+ heading: IncremarkHeading,
1295
+ paragraph: IncremarkParagraph,
1296
+ list: IncremarkList,
1297
+ blockquote: IncremarkBlockquote,
1298
+ table: IncremarkTable,
1299
+ thematicBreak: IncremarkThematicBreak,
1300
+ math: IncremarkMath,
1301
+ inlineMath: IncremarkMath,
1302
+ htmlElement: IncremarkHtmlElement,
1303
+ containerDirective: IncremarkContainer,
1304
+ leafDirective: IncremarkContainer,
1305
+ textDirective: IncremarkContainer
1218
1306
  };
1219
1307
  function isContainerNode(node) {
1220
1308
  return node.type === "containerDirective" || node.type === "leafDirective" || node.type === "textDirective";
1221
1309
  }
1310
+ function isHtmlNode2(node) {
1311
+ return node.type === "html";
1312
+ }
1313
+ function getComponent(type, userComponents) {
1314
+ return userComponents[type] || defaultComponents[type] || IncremarkDefault;
1315
+ }
1222
1316
  var IncremarkRenderer = ({
1223
1317
  node,
1224
1318
  components = {},
1225
1319
  customContainers,
1226
1320
  customCodeBlocks,
1321
+ codeBlockConfigs,
1227
1322
  blockStatus
1228
1323
  }) => {
1229
1324
  if (node.type === "footnoteDefinition") {
1230
1325
  return null;
1231
1326
  }
1232
- if (node.type === "html") {
1327
+ if (isHtmlNode2(node)) {
1233
1328
  return /* @__PURE__ */ jsx14("pre", { className: "incremark-html-code", children: /* @__PURE__ */ jsx14("code", { children: node.value }) });
1234
1329
  }
1235
1330
  if (isContainerNode(node)) {
@@ -1247,21 +1342,21 @@ var IncremarkRenderer = ({
1247
1342
  {
1248
1343
  node,
1249
1344
  customCodeBlocks,
1345
+ codeBlockConfigs,
1250
1346
  blockStatus
1251
1347
  }
1252
1348
  );
1253
1349
  }
1254
- const mergedComponents = { ...defaultComponents, ...components };
1255
- const Component = mergedComponents[node.type] || DefaultDefault;
1350
+ const Component = getComponent(node.type, components);
1256
1351
  return /* @__PURE__ */ jsx14(Component, { node });
1257
1352
  };
1258
1353
 
1259
1354
  // src/components/IncremarkFootnotes.tsx
1260
- import React7 from "react";
1261
- import { jsx as jsx15, jsxs as jsxs7 } from "react/jsx-runtime";
1355
+ import React9 from "react";
1356
+ import { jsx as jsx15, jsxs as jsxs6 } from "react/jsx-runtime";
1262
1357
  var IncremarkFootnotes = () => {
1263
1358
  const { footnoteDefinitions, footnoteReferenceOrder } = useDefinitions();
1264
- const orderedFootnotes = React7.useMemo(() => {
1359
+ const orderedFootnotes = React9.useMemo(() => {
1265
1360
  return footnoteReferenceOrder.map((identifier) => ({
1266
1361
  identifier,
1267
1362
  definition: footnoteDefinitions[identifier]
@@ -1270,16 +1365,16 @@ var IncremarkFootnotes = () => {
1270
1365
  if (orderedFootnotes.length === 0) {
1271
1366
  return null;
1272
1367
  }
1273
- return /* @__PURE__ */ jsxs7("section", { className: "incremark-footnotes", children: [
1368
+ return /* @__PURE__ */ jsxs6("section", { className: "incremark-footnotes", children: [
1274
1369
  /* @__PURE__ */ jsx15("hr", { className: "incremark-footnotes-divider" }),
1275
- /* @__PURE__ */ jsx15("ol", { className: "incremark-footnotes-list", children: orderedFootnotes.map((item, index) => /* @__PURE__ */ jsxs7(
1370
+ /* @__PURE__ */ jsx15("ol", { className: "incremark-footnotes-list", children: orderedFootnotes.map((item, index) => /* @__PURE__ */ jsxs6(
1276
1371
  "li",
1277
1372
  {
1278
1373
  id: `fn-${item.identifier}`,
1279
1374
  className: "incremark-footnote-item",
1280
1375
  children: [
1281
- /* @__PURE__ */ jsxs7("div", { className: "incremark-footnote-content", children: [
1282
- /* @__PURE__ */ jsxs7("span", { className: "incremark-footnote-number", children: [
1376
+ /* @__PURE__ */ jsxs6("div", { className: "incremark-footnote-content", children: [
1377
+ /* @__PURE__ */ jsxs6("span", { className: "incremark-footnote-number", children: [
1283
1378
  index + 1,
1284
1379
  "."
1285
1380
  ] }),
@@ -1308,15 +1403,18 @@ var IncremarkContainerProvider = ({ children, definitions }) => {
1308
1403
  };
1309
1404
 
1310
1405
  // src/components/Incremark.tsx
1311
- import { jsx as jsx17, jsxs as jsxs8 } from "react/jsx-runtime";
1406
+ import { jsx as jsx17, jsxs as jsxs7 } from "react/jsx-runtime";
1312
1407
  var Incremark = (props) => {
1313
1408
  const {
1314
1409
  blocks: propsBlocks,
1410
+ isDisplayComplete: propsIsDisplayComplete = false,
1315
1411
  components,
1316
1412
  customContainers,
1317
1413
  customCodeBlocks,
1318
1414
  showBlockStatus = true,
1319
1415
  className = "",
1416
+ pendingClass = "incremark-pending",
1417
+ completedClass = "incremark-completed",
1320
1418
  incremark
1321
1419
  } = props;
1322
1420
  if (incremark) {
@@ -1330,12 +1428,14 @@ var Incremark = (props) => {
1330
1428
  customCodeBlocks,
1331
1429
  showBlockStatus,
1332
1430
  className,
1431
+ pendingClass,
1432
+ completedClass,
1333
1433
  isDisplayComplete: isDisplayComplete2
1334
1434
  }
1335
1435
  ) });
1336
1436
  }
1337
1437
  const blocks = propsBlocks || [];
1338
- const isDisplayComplete = blocks.length > 0 && blocks.every((b) => b.status === "completed");
1438
+ const isDisplayComplete = propsIsDisplayComplete;
1339
1439
  return /* @__PURE__ */ jsx17(
1340
1440
  IncremarkInternal,
1341
1441
  {
@@ -1345,6 +1445,8 @@ var Incremark = (props) => {
1345
1445
  customCodeBlocks,
1346
1446
  showBlockStatus,
1347
1447
  className,
1448
+ pendingClass,
1449
+ completedClass,
1348
1450
  isDisplayComplete
1349
1451
  }
1350
1452
  );
@@ -1354,11 +1456,14 @@ var IncremarkInternal = ({
1354
1456
  components,
1355
1457
  customContainers,
1356
1458
  customCodeBlocks,
1459
+ codeBlockConfigs,
1357
1460
  showBlockStatus,
1358
1461
  className,
1462
+ pendingClass,
1463
+ completedClass,
1359
1464
  isDisplayComplete
1360
1465
  }) => {
1361
- return /* @__PURE__ */ jsxs8("div", { className: `incremark ${className}`, children: [
1466
+ return /* @__PURE__ */ jsxs7("div", { className: `incremark ${className}`, children: [
1362
1467
  blocks.map((block) => {
1363
1468
  if (block.node.type === "definition" || block.node.type === "footnoteDefinition") {
1364
1469
  return null;
@@ -1366,7 +1471,7 @@ var IncremarkInternal = ({
1366
1471
  const isPending = block.status === "pending";
1367
1472
  const classes = [
1368
1473
  "incremark-block",
1369
- isPending ? "incremark-pending" : "incremark-completed",
1474
+ isPending ? pendingClass : completedClass,
1370
1475
  showBlockStatus && "incremark-show-status",
1371
1476
  block.isLastPending && "incremark-last-pending"
1372
1477
  ].filter(Boolean).join(" ");
@@ -1377,24 +1482,115 @@ var IncremarkInternal = ({
1377
1482
  components,
1378
1483
  customContainers,
1379
1484
  customCodeBlocks,
1485
+ codeBlockConfigs,
1380
1486
  blockStatus: block.status
1381
1487
  }
1382
- ) }, block.stableId);
1488
+ ) }, block.id);
1383
1489
  }),
1384
1490
  isDisplayComplete && /* @__PURE__ */ jsx17(IncremarkFootnotes, {})
1385
1491
  ] });
1386
1492
  };
1387
1493
 
1494
+ // src/components/IncremarkContent.tsx
1495
+ import { useEffect as useEffect6, useRef as useRef7, useMemo as useMemo3, useCallback as useCallback7 } from "react";
1496
+ import { jsx as jsx18 } from "react/jsx-runtime";
1497
+ var IncremarkContent = (props) => {
1498
+ const {
1499
+ stream,
1500
+ content,
1501
+ components,
1502
+ customContainers,
1503
+ customCodeBlocks,
1504
+ codeBlockConfigs,
1505
+ isFinished = false,
1506
+ incremarkOptions,
1507
+ showBlockStatus,
1508
+ pendingClass
1509
+ } = props;
1510
+ const initialOptionsRef = useRef7({
1511
+ gfm: true,
1512
+ htmlTree: true,
1513
+ containers: true,
1514
+ math: true,
1515
+ ...incremarkOptions
1516
+ });
1517
+ const { blocks, append, finalize, render, reset, isDisplayComplete, markdown, typewriter, _definitionsContextValue } = useIncremark(initialOptionsRef.current);
1518
+ useEffect6(() => {
1519
+ if (incremarkOptions?.typewriter) {
1520
+ typewriter.setOptions(incremarkOptions.typewriter);
1521
+ }
1522
+ }, [incremarkOptions?.typewriter, typewriter]);
1523
+ const isStreamMode = useMemo3(() => typeof stream === "function", [stream]);
1524
+ const prevContentRef = useRef7(void 0);
1525
+ const isStreamingRef = useRef7(false);
1526
+ const handleStreamInput = useCallback7(async () => {
1527
+ if (!stream || isStreamingRef.current) return;
1528
+ isStreamingRef.current = true;
1529
+ try {
1530
+ const streamGen = stream();
1531
+ for await (const chunk of streamGen) {
1532
+ append(chunk);
1533
+ }
1534
+ finalize();
1535
+ } catch (error) {
1536
+ console.error("Stream error: ", error);
1537
+ finalize();
1538
+ } finally {
1539
+ isStreamingRef.current = false;
1540
+ }
1541
+ }, [stream, append, finalize]);
1542
+ const handleContentInput = useCallback7((newContent, oldContent) => {
1543
+ if (!newContent) {
1544
+ if (oldContent) {
1545
+ reset();
1546
+ }
1547
+ return;
1548
+ }
1549
+ if (newContent?.startsWith(oldContent ?? "")) {
1550
+ const delta = newContent.slice((oldContent || "").length);
1551
+ append(delta);
1552
+ } else {
1553
+ render(newContent);
1554
+ }
1555
+ }, [append, render, reset]);
1556
+ useEffect6(() => {
1557
+ if (isStreamMode) {
1558
+ handleStreamInput();
1559
+ } else {
1560
+ handleContentInput(content, prevContentRef.current);
1561
+ }
1562
+ prevContentRef.current = content;
1563
+ }, [content, isStreamMode, handleStreamInput, handleContentInput]);
1564
+ useEffect6(() => {
1565
+ if (isFinished && content === markdown) {
1566
+ finalize();
1567
+ }
1568
+ }, [isFinished, content, markdown, finalize]);
1569
+ return /* @__PURE__ */ jsx18(IncremarkContainerProvider, { definitions: _definitionsContextValue, children: /* @__PURE__ */ jsx18(
1570
+ Incremark,
1571
+ {
1572
+ blocks,
1573
+ isDisplayComplete,
1574
+ showBlockStatus,
1575
+ pendingClass,
1576
+ components,
1577
+ customContainers,
1578
+ customCodeBlocks,
1579
+ codeBlockConfigs
1580
+ }
1581
+ ) });
1582
+ };
1583
+
1388
1584
  // src/components/AutoScrollContainer.tsx
1389
1585
  import {
1390
- useRef as useRef7,
1391
- useEffect as useEffect6,
1392
- useCallback as useCallback7,
1586
+ useRef as useRef8,
1587
+ useEffect as useEffect7,
1588
+ useCallback as useCallback8,
1393
1589
  useState as useState7,
1394
1590
  forwardRef,
1395
1591
  useImperativeHandle
1396
1592
  } from "react";
1397
- import { jsx as jsx18 } from "react/jsx-runtime";
1593
+ import { jsx as jsx19 } from "react/jsx-runtime";
1398
1594
  var AutoScrollContainer = forwardRef(
1399
1595
  ({
1400
1596
  children,
@@ -1404,17 +1600,17 @@ var AutoScrollContainer = forwardRef(
1404
1600
  style,
1405
1601
  className
1406
1602
  }, ref) => {
1407
- const containerRef = useRef7(null);
1603
+ const containerRef = useRef8(null);
1408
1604
  const [isUserScrolledUp, setIsUserScrolledUp] = useState7(false);
1409
- const lastScrollTopRef = useRef7(0);
1410
- const lastScrollHeightRef = useRef7(0);
1411
- const isNearBottom = useCallback7(() => {
1605
+ const lastScrollTopRef = useRef8(0);
1606
+ const lastScrollHeightRef = useRef8(0);
1607
+ const isNearBottom = useCallback8(() => {
1412
1608
  const container = containerRef.current;
1413
1609
  if (!container) return true;
1414
1610
  const { scrollTop, scrollHeight, clientHeight } = container;
1415
1611
  return scrollHeight - scrollTop - clientHeight <= threshold;
1416
1612
  }, [threshold]);
1417
- const scrollToBottom = useCallback7(
1613
+ const scrollToBottom = useCallback8(
1418
1614
  (force = false) => {
1419
1615
  const container = containerRef.current;
1420
1616
  if (!container) return;
@@ -1426,12 +1622,12 @@ var AutoScrollContainer = forwardRef(
1426
1622
  },
1427
1623
  [isUserScrolledUp, behavior]
1428
1624
  );
1429
- const hasScrollbar = useCallback7(() => {
1625
+ const hasScrollbar = useCallback8(() => {
1430
1626
  const container = containerRef.current;
1431
1627
  if (!container) return false;
1432
1628
  return container.scrollHeight > container.clientHeight;
1433
1629
  }, []);
1434
- const handleScroll = useCallback7(() => {
1630
+ const handleScroll = useCallback8(() => {
1435
1631
  const container = containerRef.current;
1436
1632
  if (!container) return;
1437
1633
  const { scrollTop, scrollHeight, clientHeight } = container;
@@ -1453,14 +1649,14 @@ var AutoScrollContainer = forwardRef(
1453
1649
  lastScrollTopRef.current = scrollTop;
1454
1650
  lastScrollHeightRef.current = scrollHeight;
1455
1651
  }, [isNearBottom]);
1456
- useEffect6(() => {
1652
+ useEffect7(() => {
1457
1653
  const container = containerRef.current;
1458
1654
  if (container) {
1459
1655
  lastScrollTopRef.current = container.scrollTop;
1460
1656
  lastScrollHeightRef.current = container.scrollHeight;
1461
1657
  }
1462
1658
  }, []);
1463
- useEffect6(() => {
1659
+ useEffect7(() => {
1464
1660
  const container = containerRef.current;
1465
1661
  if (!container || !enabled) return;
1466
1662
  const observer = new MutationObserver(() => {
@@ -1492,7 +1688,7 @@ var AutoScrollContainer = forwardRef(
1492
1688
  }),
1493
1689
  [scrollToBottom, isUserScrolledUp]
1494
1690
  );
1495
- return /* @__PURE__ */ jsx18(
1691
+ return /* @__PURE__ */ jsx19(
1496
1692
  "div",
1497
1693
  {
1498
1694
  ref: containerRef,
@@ -1511,21 +1707,21 @@ var AutoScrollContainer = forwardRef(
1511
1707
  AutoScrollContainer.displayName = "AutoScrollContainer";
1512
1708
 
1513
1709
  // src/ThemeProvider.tsx
1514
- import { useEffect as useEffect7, useRef as useRef8 } from "react";
1710
+ import { useEffect as useEffect8, useRef as useRef9 } from "react";
1515
1711
  import { applyTheme } from "@incremark/theme";
1516
- import { jsx as jsx19 } from "react/jsx-runtime";
1712
+ import { jsx as jsx20 } from "react/jsx-runtime";
1517
1713
  var ThemeProvider = ({
1518
1714
  theme,
1519
1715
  children,
1520
1716
  className = ""
1521
1717
  }) => {
1522
- const containerRef = useRef8(null);
1523
- useEffect7(() => {
1718
+ const containerRef = useRef9(null);
1719
+ useEffect8(() => {
1524
1720
  if (containerRef.current) {
1525
1721
  applyTheme(containerRef.current, theme);
1526
1722
  }
1527
1723
  }, [theme]);
1528
- return /* @__PURE__ */ jsx19("div", { ref: containerRef, className: `incremark-theme-provider ${className}`.trim(), children });
1724
+ return /* @__PURE__ */ jsx20("div", { ref: containerRef, className: `incremark-theme-provider ${className}`.trim(), children });
1529
1725
  };
1530
1726
 
1531
1727
  // src/index.ts
@@ -1557,6 +1753,7 @@ export {
1557
1753
  DefinitionsProvider,
1558
1754
  Incremark,
1559
1755
  IncremarkContainerProvider,
1756
+ IncremarkContent,
1560
1757
  IncremarkFootnotes,
1561
1758
  IncremarkHtmlElement,
1562
1759
  IncremarkInline,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@incremark/react",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "license": "MIT",
5
5
  "description": "Incremark React integration - Incremental Markdown parser for AI streaming",
6
6
  "type": "module",
@@ -21,13 +21,13 @@
21
21
  "mermaid": "^10.0.0 || ^11.0.0",
22
22
  "katex": "^0.16.0",
23
23
  "react": ">=18.0.0",
24
- "@incremark/core": "0.2.5"
24
+ "@incremark/core": "0.2.7"
25
25
  },
26
26
  "dependencies": {
27
27
  "shiki": "^3.20.0",
28
- "@incremark/theme": "0.2.5",
29
- "@incremark/shared": "0.2.5",
30
- "@incremark/devtools": "0.2.5"
28
+ "@incremark/devtools": "0.2.7",
29
+ "@incremark/theme": "0.2.7",
30
+ "@incremark/shared": "0.2.7"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/mdast": "^4.0.0",