@incremark/react 0.2.6 → 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.
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
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,10 +293,41 @@ 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;
300
329
  }
330
+
301
331
  /**
302
332
  * 代码块配置
303
333
  */
@@ -307,22 +337,22 @@ interface CodeBlockConfig {
307
337
  }
308
338
  interface IncremarkProps {
309
339
  /** 要渲染的块列表 */
310
- blocks?: BlockWithStableId[];
340
+ blocks?: RenderableBlock[];
311
341
  /** 内容是否完全显示完成(用于控制脚注等需要在内容完全显示后才出现的元素)
312
342
  * 如果传入了 incremark,则会自动使用 incremark.isDisplayComplete,此 prop 被忽略 */
313
343
  isDisplayComplete?: boolean;
314
344
  /** 自定义组件映射 */
315
- components?: Partial<Record<string, React.ComponentType<{
345
+ components?: Partial<Record<string, React$1.ComponentType<{
316
346
  node: any;
317
347
  }>>>;
318
348
  /** 自定义容器组件映射,key 为容器名称(如 'warning', 'info') */
319
- customContainers?: Record<string, React.ComponentType<{
349
+ customContainers?: Record<string, React$1.ComponentType<{
320
350
  name: string;
321
351
  options?: Record<string, any>;
322
- children?: React.ReactNode;
352
+ children?: React$1.ReactNode;
323
353
  }>>;
324
354
  /** 自定义代码块组件映射,key 为代码语言名称(如 'echart', 'mermaid') */
325
- customCodeBlocks?: Record<string, React.ComponentType<{
355
+ customCodeBlocks?: Record<string, React$1.ComponentType<{
326
356
  codeStr: string;
327
357
  lang?: string | undefined;
328
358
  completed?: boolean | undefined;
@@ -334,6 +364,10 @@ interface IncremarkProps {
334
364
  showBlockStatus?: boolean;
335
365
  /** 自定义类名 */
336
366
  className?: string;
367
+ /** 待处理块的样式类名 */
368
+ pendingClass?: string;
369
+ /** 已完成块的样式类名 */
370
+ completedClass?: string;
337
371
  /** 可选:useIncremark 返回的对象(用于自动提供 context) */
338
372
  incremark?: UseIncremarkReturn;
339
373
  }
@@ -351,7 +385,26 @@ interface IncremarkProps {
351
385
  * }
352
386
  * ```
353
387
  */
354
- 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>;
355
408
 
356
409
  /**
357
410
  * 容器节点类型定义
@@ -366,15 +419,15 @@ interface ContainerNode {
366
419
 
367
420
  interface IncremarkRendererProps {
368
421
  node: RootContent | ContainerNode;
369
- components?: Partial<Record<string, React.ComponentType<{
422
+ components?: Partial<Record<string, React$1.ComponentType<{
370
423
  node: any;
371
424
  }>>>;
372
- customContainers?: Record<string, React.ComponentType<{
425
+ customContainers?: Record<string, React$1.ComponentType<{
373
426
  name: string;
374
427
  options?: Record<string, any>;
375
428
  children?: ReactNode;
376
429
  }>>;
377
- customCodeBlocks?: Record<string, React.ComponentType<{
430
+ customCodeBlocks?: Record<string, React$1.ComponentType<{
378
431
  codeStr: string;
379
432
  lang?: string;
380
433
  completed?: boolean;
@@ -388,11 +441,11 @@ interface IncremarkRendererProps {
388
441
  /**
389
442
  * 渲染单个 AST 节点
390
443
  */
391
- declare const IncremarkRenderer: React.FC<IncremarkRendererProps>;
444
+ declare const IncremarkRenderer: React$1.FC<IncremarkRendererProps>;
392
445
 
393
446
  interface AutoScrollContainerProps {
394
447
  /** 子元素 */
395
- children: React.ReactNode;
448
+ children: React$1.ReactNode;
396
449
  /** 是否启用自动滚动 */
397
450
  enabled?: boolean;
398
451
  /** 触发自动滚动的底部阈值(像素) */
@@ -400,7 +453,7 @@ interface AutoScrollContainerProps {
400
453
  /** 滚动行为 */
401
454
  behavior?: ScrollBehavior;
402
455
  /** 容器样式 */
403
- style?: React.CSSProperties;
456
+ style?: React$1.CSSProperties;
404
457
  /** 容器类名 */
405
458
  className?: string;
406
459
  }
@@ -430,7 +483,7 @@ interface AutoScrollContainerRef {
430
483
  * scrollRef.current?.scrollToBottom()
431
484
  * ```
432
485
  */
433
- declare const AutoScrollContainer: React.ForwardRefExoticComponent<AutoScrollContainerProps & React.RefAttributes<AutoScrollContainerRef>>;
486
+ declare const AutoScrollContainer: React$1.ForwardRefExoticComponent<AutoScrollContainerProps & React$1.RefAttributes<AutoScrollContainerRef>>;
434
487
 
435
488
  interface IncremarkInlineProps {
436
489
  nodes: PhrasingContent[];
@@ -450,7 +503,7 @@ interface IncremarkInlineProps {
450
503
  *
451
504
  * 注意:此组件与 Vue 版本保持完全一致的逻辑和结构
452
505
  */
453
- declare const IncremarkInline: React.FC<IncremarkInlineProps>;
506
+ declare const IncremarkInline: React$1.FC<IncremarkInlineProps>;
454
507
 
455
508
  /**
456
509
  * HtmlElementNode 类型定义(与 @incremark/core 中的定义一致)
@@ -474,7 +527,7 @@ interface IncremarkHtmlElementProps {
474
527
  *
475
528
  * 渲染结构化的 HTML 元素节点
476
529
  */
477
- declare const IncremarkHtmlElement: React.FC<IncremarkHtmlElementProps>;
530
+ declare const IncremarkHtmlElement: React$1.FC<IncremarkHtmlElementProps>;
478
531
 
479
532
  /**
480
533
  * 脚注列表组件
@@ -501,7 +554,7 @@ declare const IncremarkHtmlElement: React.FC<IncremarkHtmlElementProps>;
501
554
  * <IncremarkFootnotes />
502
555
  * ```
503
556
  */
504
- declare const IncremarkFootnotes: React.FC;
557
+ declare const IncremarkFootnotes: React$1.FC;
505
558
 
506
559
  /**
507
560
  * @file IncremarkContainerProvider - Incremark 容器级 Provider
@@ -533,7 +586,7 @@ interface IncremarkContainerProviderProps {
533
586
  * @param props - 组件参数
534
587
  * @returns ReactElement
535
588
  */
536
- declare const IncremarkContainerProvider: React.FC<IncremarkContainerProviderProps>;
589
+ declare const IncremarkContainerProvider: React$1.FC<IncremarkContainerProviderProps>;
537
590
 
538
591
  interface ThemeProviderProps {
539
592
  /** 主题配置,可以是:
@@ -565,6 +618,6 @@ interface ThemeProviderProps {
565
618
  * </ThemeProvider>
566
619
  * ```
567
620
  */
568
- declare const ThemeProvider: React.FC<ThemeProviderProps>;
621
+ declare const ThemeProvider: React$1.FC<ThemeProviderProps>;
569
622
 
570
- 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,
@@ -452,8 +441,8 @@ function useDevTools(incremark, options = {}) {
452
441
  devtoolsRef.current = devtools;
453
442
  incremark.parser.setOnChange((state) => {
454
443
  const blocks = [
455
- ...state.completedBlocks.map((b) => ({ ...b, stableId: b.id })),
456
- ...state.pendingBlocks.map((b, i) => ({ ...b, stableId: `pending-${i}` }))
444
+ ...state.completedBlocks,
445
+ ...state.pendingBlocks
457
446
  ];
458
447
  devtools.update({
459
448
  blocks,
@@ -844,7 +833,135 @@ var IncremarkParagraph = ({ node }) => {
844
833
  };
845
834
 
846
835
  // src/components/IncremarkCode.tsx
847
- 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
848
965
  import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
849
966
  var IncremarkCode = ({
850
967
  node,
@@ -858,19 +975,16 @@ var IncremarkCode = ({
858
975
  }) => {
859
976
  const [copied, setCopied] = useState5(false);
860
977
  const [highlightedHtml, setHighlightedHtml] = useState5("");
861
- const [isHighlighting, setIsHighlighting] = useState5(false);
862
978
  const [mermaidSvg, setMermaidSvg] = useState5("");
863
979
  const [mermaidLoading, setMermaidLoading] = useState5(false);
864
980
  const [mermaidViewMode, setMermaidViewMode] = useState5("preview");
865
981
  const mermaidRef = useRef5(null);
866
- const highlighterRef = useRef5(null);
867
982
  const mermaidTimerRef = useRef5(null);
868
- const loadedLanguagesRef = useRef5(/* @__PURE__ */ new Set());
869
- const loadedThemesRef = useRef5(/* @__PURE__ */ new Set());
870
983
  const language = node.lang || "text";
871
984
  const code = node.value;
872
985
  const isMermaid = language === "mermaid";
873
- const CustomCodeBlock = React4.useMemo(() => {
986
+ const { isHighlighting, highlight } = useShiki(theme);
987
+ const CustomCodeBlock = React5.useMemo(() => {
874
988
  const component = customCodeBlocks?.[language];
875
989
  if (!component) return null;
876
990
  const config = codeBlockConfigs?.[language];
@@ -902,7 +1016,8 @@ var IncremarkCode = ({
902
1016
  mermaidRef.current.initialize({
903
1017
  startOnLoad: false,
904
1018
  theme: "dark",
905
- securityLevel: "loose"
1019
+ securityLevel: "loose",
1020
+ suppressErrorRendering: true
906
1021
  });
907
1022
  }
908
1023
  const mermaid = mermaidRef.current;
@@ -925,7 +1040,7 @@ var IncremarkCode = ({
925
1040
  doRenderMermaid();
926
1041
  }, mermaidDelay);
927
1042
  }, [isMermaid, code, mermaidDelay, doRenderMermaid]);
928
- const highlight = useCallback5(async () => {
1043
+ const doHighlight = useCallback5(async () => {
929
1044
  if (isMermaid) {
930
1045
  scheduleRenderMermaid();
931
1046
  return;
@@ -934,46 +1049,16 @@ var IncremarkCode = ({
934
1049
  setHighlightedHtml("");
935
1050
  return;
936
1051
  }
937
- setIsHighlighting(true);
938
1052
  try {
939
- if (!highlighterRef.current) {
940
- const { createHighlighter } = await import("shiki");
941
- highlighterRef.current = await createHighlighter({
942
- themes: [theme],
943
- langs: []
944
- });
945
- loadedThemesRef.current.add(theme);
946
- }
947
- const highlighter = highlighterRef.current;
948
- const lang = language;
949
- if (!loadedLanguagesRef.current.has(lang) && lang !== "text") {
950
- try {
951
- await highlighter.loadLanguage(lang);
952
- loadedLanguagesRef.current.add(lang);
953
- } catch {
954
- }
955
- }
956
- if (!loadedThemesRef.current.has(theme)) {
957
- try {
958
- await highlighter.loadTheme(theme);
959
- loadedThemesRef.current.add(theme);
960
- } catch {
961
- }
962
- }
963
- const html = highlighter.codeToHtml(code, {
964
- lang: loadedLanguagesRef.current.has(lang) ? lang : "text",
965
- theme: loadedThemesRef.current.has(theme) ? theme : fallbackTheme
966
- });
1053
+ const html = await highlight(code, language, fallbackTheme);
967
1054
  setHighlightedHtml(html);
968
1055
  } catch {
969
1056
  setHighlightedHtml("");
970
- } finally {
971
- setIsHighlighting(false);
972
1057
  }
973
- }, [code, language, theme, fallbackTheme, disableHighlight, isMermaid, scheduleRenderMermaid]);
1058
+ }, [code, language, fallbackTheme, disableHighlight, isMermaid, highlight, scheduleRenderMermaid]);
974
1059
  useEffect4(() => {
975
- highlight();
976
- }, [highlight]);
1060
+ doHighlight();
1061
+ }, [doHighlight]);
977
1062
  useEffect4(() => {
978
1063
  return () => {
979
1064
  if (mermaidTimerRef.current) {
@@ -1024,7 +1109,7 @@ var IncremarkCode = ({
1024
1109
  };
1025
1110
 
1026
1111
  // src/components/IncremarkList.tsx
1027
- import React5 from "react";
1112
+ import React6 from "react";
1028
1113
  import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
1029
1114
  function getItemInlineContent(item) {
1030
1115
  const firstChild = item.children[0];
@@ -1044,7 +1129,7 @@ function getItemBlockChildren(item) {
1044
1129
  var IncremarkList = ({ node }) => {
1045
1130
  const Tag = node.ordered ? "ol" : "ul";
1046
1131
  const isTaskList = node.children?.some((item) => item.checked !== null && item.checked !== void 0);
1047
- 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) => {
1048
1133
  const isTaskItem = item.checked !== null && item.checked !== void 0;
1049
1134
  const inlineContent = getItemInlineContent(item);
1050
1135
  const blockChildren = getItemBlockChildren(item);
@@ -1064,16 +1149,16 @@ var IncremarkList = ({ node }) => {
1064
1149
  }
1065
1150
  return /* @__PURE__ */ jsxs3("li", { className: "incremark-list-item", children: [
1066
1151
  /* @__PURE__ */ jsx7(IncremarkInline, { nodes: inlineContent }),
1067
- blockChildren.map((child, childIndex) => /* @__PURE__ */ jsx7(React5.Fragment, { children: /* @__PURE__ */ jsx7(IncremarkRenderer, { node: child }) }, childIndex))
1152
+ blockChildren.map((child, childIndex) => /* @__PURE__ */ jsx7(React6.Fragment, { children: /* @__PURE__ */ jsx7(IncremarkRenderer, { node: child }) }, childIndex))
1068
1153
  ] }, index);
1069
1154
  }) });
1070
1155
  };
1071
1156
 
1072
1157
  // src/components/IncremarkBlockquote.tsx
1073
- import React6 from "react";
1158
+ import React7 from "react";
1074
1159
  import { jsx as jsx8 } from "react/jsx-runtime";
1075
1160
  var IncremarkBlockquote = ({ node }) => {
1076
- return /* @__PURE__ */ jsx8("blockquote", { className: "incremark-blockquote", children: node.children.map((child, index) => /* @__PURE__ */ jsx8(React6.Fragment, { children: /* @__PURE__ */ jsx8(IncremarkRenderer, { node: child }) }, index)) });
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)) });
1077
1162
  };
1078
1163
 
1079
1164
  // src/components/IncremarkTable.tsx
@@ -1267,11 +1352,11 @@ var IncremarkRenderer = ({
1267
1352
  };
1268
1353
 
1269
1354
  // src/components/IncremarkFootnotes.tsx
1270
- import React8 from "react";
1355
+ import React9 from "react";
1271
1356
  import { jsx as jsx15, jsxs as jsxs6 } from "react/jsx-runtime";
1272
1357
  var IncremarkFootnotes = () => {
1273
1358
  const { footnoteDefinitions, footnoteReferenceOrder } = useDefinitions();
1274
- const orderedFootnotes = React8.useMemo(() => {
1359
+ const orderedFootnotes = React9.useMemo(() => {
1275
1360
  return footnoteReferenceOrder.map((identifier) => ({
1276
1361
  identifier,
1277
1362
  definition: footnoteDefinitions[identifier]
@@ -1328,6 +1413,8 @@ var Incremark = (props) => {
1328
1413
  customCodeBlocks,
1329
1414
  showBlockStatus = true,
1330
1415
  className = "",
1416
+ pendingClass = "incremark-pending",
1417
+ completedClass = "incremark-completed",
1331
1418
  incremark
1332
1419
  } = props;
1333
1420
  if (incremark) {
@@ -1341,6 +1428,8 @@ var Incremark = (props) => {
1341
1428
  customCodeBlocks,
1342
1429
  showBlockStatus,
1343
1430
  className,
1431
+ pendingClass,
1432
+ completedClass,
1344
1433
  isDisplayComplete: isDisplayComplete2
1345
1434
  }
1346
1435
  ) });
@@ -1356,6 +1445,8 @@ var Incremark = (props) => {
1356
1445
  customCodeBlocks,
1357
1446
  showBlockStatus,
1358
1447
  className,
1448
+ pendingClass,
1449
+ completedClass,
1359
1450
  isDisplayComplete
1360
1451
  }
1361
1452
  );
@@ -1368,6 +1459,8 @@ var IncremarkInternal = ({
1368
1459
  codeBlockConfigs,
1369
1460
  showBlockStatus,
1370
1461
  className,
1462
+ pendingClass,
1463
+ completedClass,
1371
1464
  isDisplayComplete
1372
1465
  }) => {
1373
1466
  return /* @__PURE__ */ jsxs7("div", { className: `incremark ${className}`, children: [
@@ -1378,7 +1471,7 @@ var IncremarkInternal = ({
1378
1471
  const isPending = block.status === "pending";
1379
1472
  const classes = [
1380
1473
  "incremark-block",
1381
- isPending ? "incremark-pending" : "incremark-completed",
1474
+ isPending ? pendingClass : completedClass,
1382
1475
  showBlockStatus && "incremark-show-status",
1383
1476
  block.isLastPending && "incremark-last-pending"
1384
1477
  ].filter(Boolean).join(" ");
@@ -1392,22 +1485,112 @@ var IncremarkInternal = ({
1392
1485
  codeBlockConfigs,
1393
1486
  blockStatus: block.status
1394
1487
  }
1395
- ) }, block.stableId);
1488
+ ) }, block.id);
1396
1489
  }),
1397
1490
  isDisplayComplete && /* @__PURE__ */ jsx17(IncremarkFootnotes, {})
1398
1491
  ] });
1399
1492
  };
1400
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
+
1401
1584
  // src/components/AutoScrollContainer.tsx
1402
1585
  import {
1403
- useRef as useRef7,
1404
- useEffect as useEffect6,
1405
- useCallback as useCallback7,
1586
+ useRef as useRef8,
1587
+ useEffect as useEffect7,
1588
+ useCallback as useCallback8,
1406
1589
  useState as useState7,
1407
1590
  forwardRef,
1408
1591
  useImperativeHandle
1409
1592
  } from "react";
1410
- import { jsx as jsx18 } from "react/jsx-runtime";
1593
+ import { jsx as jsx19 } from "react/jsx-runtime";
1411
1594
  var AutoScrollContainer = forwardRef(
1412
1595
  ({
1413
1596
  children,
@@ -1417,17 +1600,17 @@ var AutoScrollContainer = forwardRef(
1417
1600
  style,
1418
1601
  className
1419
1602
  }, ref) => {
1420
- const containerRef = useRef7(null);
1603
+ const containerRef = useRef8(null);
1421
1604
  const [isUserScrolledUp, setIsUserScrolledUp] = useState7(false);
1422
- const lastScrollTopRef = useRef7(0);
1423
- const lastScrollHeightRef = useRef7(0);
1424
- const isNearBottom = useCallback7(() => {
1605
+ const lastScrollTopRef = useRef8(0);
1606
+ const lastScrollHeightRef = useRef8(0);
1607
+ const isNearBottom = useCallback8(() => {
1425
1608
  const container = containerRef.current;
1426
1609
  if (!container) return true;
1427
1610
  const { scrollTop, scrollHeight, clientHeight } = container;
1428
1611
  return scrollHeight - scrollTop - clientHeight <= threshold;
1429
1612
  }, [threshold]);
1430
- const scrollToBottom = useCallback7(
1613
+ const scrollToBottom = useCallback8(
1431
1614
  (force = false) => {
1432
1615
  const container = containerRef.current;
1433
1616
  if (!container) return;
@@ -1439,12 +1622,12 @@ var AutoScrollContainer = forwardRef(
1439
1622
  },
1440
1623
  [isUserScrolledUp, behavior]
1441
1624
  );
1442
- const hasScrollbar = useCallback7(() => {
1625
+ const hasScrollbar = useCallback8(() => {
1443
1626
  const container = containerRef.current;
1444
1627
  if (!container) return false;
1445
1628
  return container.scrollHeight > container.clientHeight;
1446
1629
  }, []);
1447
- const handleScroll = useCallback7(() => {
1630
+ const handleScroll = useCallback8(() => {
1448
1631
  const container = containerRef.current;
1449
1632
  if (!container) return;
1450
1633
  const { scrollTop, scrollHeight, clientHeight } = container;
@@ -1466,14 +1649,14 @@ var AutoScrollContainer = forwardRef(
1466
1649
  lastScrollTopRef.current = scrollTop;
1467
1650
  lastScrollHeightRef.current = scrollHeight;
1468
1651
  }, [isNearBottom]);
1469
- useEffect6(() => {
1652
+ useEffect7(() => {
1470
1653
  const container = containerRef.current;
1471
1654
  if (container) {
1472
1655
  lastScrollTopRef.current = container.scrollTop;
1473
1656
  lastScrollHeightRef.current = container.scrollHeight;
1474
1657
  }
1475
1658
  }, []);
1476
- useEffect6(() => {
1659
+ useEffect7(() => {
1477
1660
  const container = containerRef.current;
1478
1661
  if (!container || !enabled) return;
1479
1662
  const observer = new MutationObserver(() => {
@@ -1505,7 +1688,7 @@ var AutoScrollContainer = forwardRef(
1505
1688
  }),
1506
1689
  [scrollToBottom, isUserScrolledUp]
1507
1690
  );
1508
- return /* @__PURE__ */ jsx18(
1691
+ return /* @__PURE__ */ jsx19(
1509
1692
  "div",
1510
1693
  {
1511
1694
  ref: containerRef,
@@ -1524,21 +1707,21 @@ var AutoScrollContainer = forwardRef(
1524
1707
  AutoScrollContainer.displayName = "AutoScrollContainer";
1525
1708
 
1526
1709
  // src/ThemeProvider.tsx
1527
- import { useEffect as useEffect7, useRef as useRef8 } from "react";
1710
+ import { useEffect as useEffect8, useRef as useRef9 } from "react";
1528
1711
  import { applyTheme } from "@incremark/theme";
1529
- import { jsx as jsx19 } from "react/jsx-runtime";
1712
+ import { jsx as jsx20 } from "react/jsx-runtime";
1530
1713
  var ThemeProvider = ({
1531
1714
  theme,
1532
1715
  children,
1533
1716
  className = ""
1534
1717
  }) => {
1535
- const containerRef = useRef8(null);
1536
- useEffect7(() => {
1718
+ const containerRef = useRef9(null);
1719
+ useEffect8(() => {
1537
1720
  if (containerRef.current) {
1538
1721
  applyTheme(containerRef.current, theme);
1539
1722
  }
1540
1723
  }, [theme]);
1541
- 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 });
1542
1725
  };
1543
1726
 
1544
1727
  // src/index.ts
@@ -1570,6 +1753,7 @@ export {
1570
1753
  DefinitionsProvider,
1571
1754
  Incremark,
1572
1755
  IncremarkContainerProvider,
1756
+ IncremarkContent,
1573
1757
  IncremarkFootnotes,
1574
1758
  IncremarkHtmlElement,
1575
1759
  IncremarkInline,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@incremark/react",
3
- "version": "0.2.6",
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.6"
24
+ "@incremark/core": "0.2.7"
25
25
  },
26
26
  "dependencies": {
27
27
  "shiki": "^3.20.0",
28
- "@incremark/shared": "0.2.6",
29
- "@incremark/theme": "0.2.6",
30
- "@incremark/devtools": "0.2.6"
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",