@lobehub/ui 5.3.0 → 5.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/es/Accordion/Accordion.d.mts +2 -2
  2. package/es/Accordion/AccordionItem.d.mts +2 -2
  3. package/es/ActionIcon/ActionIcon.d.mts +2 -2
  4. package/es/Alert/Alert.d.mts +2 -2
  5. package/es/AutoComplete/Select.d.mts +2 -2
  6. package/es/Avatar/AvatarGroup/index.d.mts +2 -2
  7. package/es/Burger/Burger.d.mts +2 -2
  8. package/es/CodeDiff/CodeDiff.d.mts +2 -2
  9. package/es/CodeDiff/PatchDiff.d.mts +2 -2
  10. package/es/CodeEditor/CodeEditor.d.mts +2 -2
  11. package/es/Collapse/Collapse.d.mts +2 -2
  12. package/es/ConfigProvider/index.d.mts +2 -2
  13. package/es/CopyButton/CopyButton.d.mts +2 -2
  14. package/es/DatePicker/DatePicker.d.mts +2 -2
  15. package/es/DraggablePanel/components/DraggablePanelBody.d.mts +2 -2
  16. package/es/DraggablePanel/components/DraggablePanelContainer.d.mts +2 -2
  17. package/es/DraggablePanel/components/DraggablePanelFooter.d.mts +2 -2
  18. package/es/DraggablePanel/components/DraggablePanelHeader.d.mts +2 -2
  19. package/es/DraggableSideNav/DraggableSideNav.d.mts +2 -2
  20. package/es/Drawer/Drawer.d.mts +2 -2
  21. package/es/Dropdown/Dropdown.d.mts +2 -2
  22. package/es/EditableText/EditableText.d.mts +2 -2
  23. package/es/EditorSlashMenu/atoms.d.mts +13 -13
  24. package/es/EmojiPicker/EmojiPicker.d.mts +2 -2
  25. package/es/Flex/FlexBasic.d.mts +2 -2
  26. package/es/FontLoader/index.d.mts +2 -2
  27. package/es/Footer/Footer.d.mts +2 -2
  28. package/es/Form/components/FormGroup.d.mts +2 -2
  29. package/es/Form/components/FormItem.d.mts +2 -2
  30. package/es/Form/components/FormSubmitFooter.d.mts +2 -2
  31. package/es/FormModal/FormModal.d.mts +2 -2
  32. package/es/Freeze/Freeze.d.mts +2 -2
  33. package/es/GuideCard/GuideCard.d.mts +2 -2
  34. package/es/Header/Header.d.mts +2 -2
  35. package/es/Highlighter/Highlighter.d.mts +2 -2
  36. package/es/Highlighter/SyntaxHighlighter/index.d.mts +2 -2
  37. package/es/Hotkey/Hotkey.d.mts +2 -2
  38. package/es/HotkeyInput/HotkeyInput.d.mts +2 -2
  39. package/es/HotkeyInput/HotkeyInput.mjs +30 -8
  40. package/es/HotkeyInput/HotkeyInput.mjs.map +1 -1
  41. package/es/HotkeyInput/type.d.mts +3 -0
  42. package/es/Icon/Icon.d.mts +2 -2
  43. package/es/Icon/components/IconProvider.d.mts +3 -3
  44. package/es/Image/PreviewGroup.d.mts +2 -2
  45. package/es/ImageSelect/ImageSelect.d.mts +2 -2
  46. package/es/Input/Input.d.mts +2 -2
  47. package/es/Input/InputNumber.d.mts +2 -2
  48. package/es/Input/InputOPT.d.mts +2 -2
  49. package/es/Input/InputPassword.d.mts +2 -2
  50. package/es/Input/TextArea.d.mts +2 -2
  51. package/es/Layout/components/LayoutFooter.d.mts +2 -2
  52. package/es/Layout/components/LayoutHeader.d.mts +2 -2
  53. package/es/Layout/components/LayoutMain.d.mts +2 -2
  54. package/es/Layout/components/LayoutSidebar.d.mts +2 -2
  55. package/es/Layout/components/LayoutSidebarInner.d.mts +2 -2
  56. package/es/Layout/components/LayoutToc.d.mts +2 -2
  57. package/es/List/ListItem/index.d.mts +2 -2
  58. package/es/Markdown/Markdown.d.mts +2 -2
  59. package/es/Markdown/Markdown.mjs +2 -1
  60. package/es/Markdown/Markdown.mjs.map +1 -1
  61. package/es/Markdown/SyntaxMarkdown/StreamdownRender.mjs +56 -30
  62. package/es/Markdown/SyntaxMarkdown/StreamdownRender.mjs.map +1 -1
  63. package/es/Markdown/SyntaxMarkdown/style.mjs +2 -2
  64. package/es/Markdown/SyntaxMarkdown/style.mjs.map +1 -1
  65. package/es/Markdown/SyntaxMarkdown/useSmoothStreamContent.mjs +229 -0
  66. package/es/Markdown/SyntaxMarkdown/useSmoothStreamContent.mjs.map +1 -0
  67. package/es/Markdown/SyntaxMarkdown/useStreamQueue.mjs +8 -6
  68. package/es/Markdown/SyntaxMarkdown/useStreamQueue.mjs.map +1 -1
  69. package/es/Markdown/Typography.d.mts +2 -2
  70. package/es/Markdown/components/SearchResultCards/index.d.mts +2 -2
  71. package/es/Markdown/index.d.mts +2 -2
  72. package/es/Markdown/plugins/rehypeStreamAnimated.d.mts +2 -0
  73. package/es/Markdown/plugins/rehypeStreamAnimated.mjs +17 -5
  74. package/es/Markdown/plugins/rehypeStreamAnimated.mjs.map +1 -1
  75. package/es/Markdown/type.d.mts +3 -1
  76. package/es/MaskShadow/MaskShadow.d.mts +2 -2
  77. package/es/Menu/Menu.d.mts +2 -2
  78. package/es/Mermaid/Mermaid.d.mts +2 -2
  79. package/es/Mermaid/SyntaxMermaid/index.d.mts +2 -2
  80. package/es/Modal/Modal.d.mts +2 -2
  81. package/es/Modal/ModalProvider.d.mts +2 -2
  82. package/es/Modal/imperative.d.mts +2 -2
  83. package/es/MotionProvider/index.d.mts +2 -2
  84. package/es/Popover/ArrowIcon.d.mts +2 -2
  85. package/es/Popover/atoms.d.mts +9 -9
  86. package/es/Popover/context.d.mts +2 -2
  87. package/es/SearchBar/SearchBar.d.mts +2 -2
  88. package/es/Segmented/Segmented.d.mts +2 -2
  89. package/es/Select/Select.d.mts +2 -2
  90. package/es/SideNav/SideNav.d.mts +2 -2
  91. package/es/SliderWithInput/SliderWithInput.d.mts +2 -2
  92. package/es/SortableList/components/DragHandle.d.mts +2 -2
  93. package/es/SortableList/components/SortableItem.d.mts +2 -2
  94. package/es/ThemeProvider/ThemeProvider.d.mts +2 -2
  95. package/es/Toc/Toc.d.mts +2 -2
  96. package/es/Video/index.d.mts +2 -2
  97. package/es/awesome/AuroraBackground/AuroraBackground.d.mts +2 -2
  98. package/es/awesome/BottomGradientButton/BottomGradientButton.d.mts +2 -2
  99. package/es/awesome/Features/Features.d.mts +2 -2
  100. package/es/awesome/Giscus/Giscus.d.mts +2 -2
  101. package/es/awesome/GradientButton/GradientButton.d.mts +2 -2
  102. package/es/awesome/GridBackground/GridBackground.d.mts +2 -2
  103. package/es/awesome/GridBackground/GridShowcase.d.mts +2 -2
  104. package/es/awesome/Hero/Hero.d.mts +2 -2
  105. package/es/awesome/Spline/Spine.d.mts +2 -2
  106. package/es/awesome/Spotlight/Spotlight.d.mts +2 -2
  107. package/es/awesome/SpotlightCard/SpotlightCard.d.mts +2 -2
  108. package/es/awesome/TypewriterEffect/TypewriterEffect.d.mts +2 -2
  109. package/es/base-ui/Modal/atoms.d.mts +12 -12
  110. package/es/base-ui/Modal/context.d.mts +2 -2
  111. package/es/base-ui/Modal/imperative.d.mts +2 -2
  112. package/es/base-ui/Select/Select.d.mts +2 -2
  113. package/es/base-ui/Select/atoms.d.mts +19 -19
  114. package/es/base-ui/Switch/Switch.d.mts +2 -2
  115. package/es/base-ui/Switch/atoms.d.mts +4 -4
  116. package/es/base-ui/Toast/imperative.d.mts +2 -2
  117. package/es/brand/LobeChat/index.d.mts +2 -2
  118. package/es/brand/LobeHub/index.d.mts +2 -2
  119. package/es/brand/LogoThree/LogoSpline.d.mts +2 -2
  120. package/es/brand/LogoThree/index.d.mts +2 -2
  121. package/es/chat/BackBottom/BackBottom.d.mts +2 -2
  122. package/es/chat/ChatInputArea/components/ChatInputAreaInner.d.mts +2 -2
  123. package/es/chat/ChatItem/ChatItem.d.mts +2 -2
  124. package/es/chat/ChatList/ChatList.d.mts +2 -2
  125. package/es/chat/EditableMessage/EditableMessage.d.mts +2 -2
  126. package/es/chat/EditableMessageList/EditableMessageList.d.mts +2 -2
  127. package/es/chat/MessageInput/MessageInput.d.mts +2 -2
  128. package/es/chat/MessageModal/MessageModal.d.mts +2 -2
  129. package/es/color/ColorScales/index.d.mts +2 -2
  130. package/es/color/CssVar/index.d.mts +2 -2
  131. package/es/i18n/context.d.mts +2 -2
  132. package/es/i18n/resources/en/hotkey.d.mts +1 -0
  133. package/es/i18n/resources/en/hotkey.mjs +1 -0
  134. package/es/i18n/resources/en/hotkey.mjs.map +1 -1
  135. package/es/i18n/resources/zhCn/hotkey.d.mts +1 -0
  136. package/es/i18n/resources/zhCn/hotkey.mjs +1 -0
  137. package/es/i18n/resources/zhCn/hotkey.mjs.map +1 -1
  138. package/es/icons/lucideExtra/BotPromptIcon.d.mts +2 -2
  139. package/es/icons/lucideExtra/CreateBotIcon.d.mts +3 -3
  140. package/es/icons/lucideExtra/DiscordIcon.d.mts +2 -2
  141. package/es/icons/lucideExtra/GlobeOffIcon.d.mts +2 -2
  142. package/es/icons/lucideExtra/GroupBotIcon.d.mts +3 -3
  143. package/es/icons/lucideExtra/GroupBotSquareIcon.d.mts +3 -3
  144. package/es/icons/lucideExtra/LeftClickIcon.d.mts +3 -3
  145. package/es/icons/lucideExtra/LeftDoubleClickIcon.d.mts +3 -3
  146. package/es/icons/lucideExtra/McpIcon.d.mts +3 -3
  147. package/es/icons/lucideExtra/ProviderIcon.d.mts +3 -3
  148. package/es/icons/lucideExtra/RightClickIcon.d.mts +3 -3
  149. package/es/icons/lucideExtra/RightDoubleClickIcon.d.mts +3 -3
  150. package/es/icons/lucideExtra/ShapesUploadIcon.d.mts +3 -3
  151. package/es/icons/lucideExtra/SkillsIcon.d.mts +3 -3
  152. package/es/icons/lucideExtra/TreeDownRightIcon.d.mts +2 -2
  153. package/es/icons/lucideExtra/TreeUpDownRightIcon.d.mts +3 -3
  154. package/es/mdx/Mdx/index.d.mts +2 -2
  155. package/es/mobile/ChatHeader/ChatHeaderTitle.d.mts +2 -2
  156. package/es/mobile/ChatInputArea/components/ChatSendButton.d.mts +2 -2
  157. package/es/mobile/TabBar/TabBar.d.mts +2 -2
  158. package/es/storybook/StoryBook/index.d.mts +2 -2
  159. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"style.mjs","names":[],"sources":["../../../src/Markdown/SyntaxMarkdown/style.ts"],"sourcesContent":["import { createStaticStyles } from 'antd-style';\n\nimport { fadeIn } from '@/styles/animations';\n\nexport const styles = createStaticStyles(({ css }) => {\n return {\n animated: css`\n .stream-char {\n opacity: 0;\n\n animation-name: ${fadeIn};\n animation-duration: 150ms;\n animation-timing-function: ease-out;\n animation-fill-mode: forwards;\n }\n\n .stream-char-revealed {\n opacity: 1;\n animation: none;\n }\n\n .katex-display .katex-html span {\n mask: none !important;\n animation: none !important;\n }\n `,\n };\n});\n"],"mappings":";;;;AAIA,MAAa,SAAS,oBAAoB,EAAE,iBAAU;AACpD,QAAO,EACL,UAAU,KAAG;;;;0BAIS,OAAO;;;;;;;;;;;;;;;OAgB9B;EACD"}
1
+ {"version":3,"file":"style.mjs","names":[],"sources":["../../../src/Markdown/SyntaxMarkdown/style.ts"],"sourcesContent":["import { createStaticStyles } from 'antd-style';\n\nimport { fadeIn } from '@/styles/animations';\n\nexport const styles = createStaticStyles(({ css }) => {\n return {\n animated: css`\n .stream-char {\n opacity: 0;\n\n animation-name: ${fadeIn};\n animation-duration: 280ms;\n animation-timing-function: cubic-bezier(0.33, 0, 0.67, 1);\n animation-fill-mode: forwards;\n }\n\n .stream-char-revealed {\n opacity: 1;\n animation: none;\n }\n\n .katex-display .katex-html span {\n mask: none !important;\n animation: none !important;\n }\n `,\n };\n});\n"],"mappings":";;;;AAIA,MAAa,SAAS,oBAAoB,EAAE,iBAAU;AACpD,QAAO,EACL,UAAU,KAAG;;;;0BAIS,OAAO;;;;;;;;;;;;;;;OAgB9B;EACD"}
@@ -0,0 +1,229 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+
3
+ //#region src/Markdown/SyntaxMarkdown/useSmoothStreamContent.ts
4
+ const PRESET_CONFIG = {
5
+ balanced: {
6
+ activeInputWindowMs: 220,
7
+ defaultCps: 38,
8
+ emaAlpha: .2,
9
+ flushCps: 120,
10
+ largeAppendChars: 120,
11
+ maxActiveCps: 132,
12
+ maxCps: 72,
13
+ maxFlushCps: 280,
14
+ minCps: 18,
15
+ settleAfterMs: 360,
16
+ settleDrainMaxMs: 520,
17
+ settleDrainMinMs: 180,
18
+ targetBufferMs: 120
19
+ },
20
+ realtime: {
21
+ activeInputWindowMs: 140,
22
+ defaultCps: 50,
23
+ emaAlpha: .3,
24
+ flushCps: 170,
25
+ largeAppendChars: 180,
26
+ maxActiveCps: 180,
27
+ maxCps: 96,
28
+ maxFlushCps: 360,
29
+ minCps: 24,
30
+ settleAfterMs: 260,
31
+ settleDrainMaxMs: 360,
32
+ settleDrainMinMs: 140,
33
+ targetBufferMs: 40
34
+ },
35
+ silky: {
36
+ activeInputWindowMs: 320,
37
+ defaultCps: 28,
38
+ emaAlpha: .14,
39
+ flushCps: 96,
40
+ largeAppendChars: 100,
41
+ maxActiveCps: 102,
42
+ maxCps: 56,
43
+ maxFlushCps: 220,
44
+ minCps: 14,
45
+ settleAfterMs: 460,
46
+ settleDrainMaxMs: 680,
47
+ settleDrainMinMs: 240,
48
+ targetBufferMs: 170
49
+ }
50
+ };
51
+ const clamp = (value, min, max) => {
52
+ return Math.min(max, Math.max(min, value));
53
+ };
54
+ const countChars = (text) => {
55
+ return [...text].length;
56
+ };
57
+ const useSmoothStreamContent = (content, { enabled = true, preset = "balanced" } = {}) => {
58
+ const config = PRESET_CONFIG[preset];
59
+ const [displayedContent, setDisplayedContent] = useState(content);
60
+ const displayedContentRef = useRef(content);
61
+ const displayedCountRef = useRef(countChars(content));
62
+ const targetContentRef = useRef(content);
63
+ const targetCharsRef = useRef([...content]);
64
+ const targetCountRef = useRef(targetCharsRef.current.length);
65
+ const emaCpsRef = useRef(config.defaultCps);
66
+ const lastInputTsRef = useRef(0);
67
+ const lastInputCountRef = useRef(targetCountRef.current);
68
+ const chunkSizeEmaRef = useRef(1);
69
+ const arrivalCpsEmaRef = useRef(config.defaultCps);
70
+ const rafRef = useRef(null);
71
+ const lastFrameTsRef = useRef(null);
72
+ const stopFrameLoop = useCallback(() => {
73
+ if (rafRef.current !== null) {
74
+ cancelAnimationFrame(rafRef.current);
75
+ rafRef.current = null;
76
+ }
77
+ lastFrameTsRef.current = null;
78
+ }, []);
79
+ const syncImmediate = useCallback((nextContent) => {
80
+ stopFrameLoop();
81
+ const chars = [...nextContent];
82
+ const now = performance.now();
83
+ targetContentRef.current = nextContent;
84
+ targetCharsRef.current = chars;
85
+ targetCountRef.current = chars.length;
86
+ displayedContentRef.current = nextContent;
87
+ displayedCountRef.current = chars.length;
88
+ setDisplayedContent(nextContent);
89
+ emaCpsRef.current = config.defaultCps;
90
+ chunkSizeEmaRef.current = 1;
91
+ arrivalCpsEmaRef.current = config.defaultCps;
92
+ lastInputTsRef.current = now;
93
+ lastInputCountRef.current = chars.length;
94
+ }, [config.defaultCps, stopFrameLoop]);
95
+ const startFrameLoop = useCallback(() => {
96
+ if (rafRef.current !== null) return;
97
+ const tick = (ts) => {
98
+ if (lastFrameTsRef.current === null) {
99
+ lastFrameTsRef.current = ts;
100
+ rafRef.current = requestAnimationFrame(tick);
101
+ return;
102
+ }
103
+ const dtSeconds = Math.max(.001, Math.min((ts - lastFrameTsRef.current) / 1e3, .05));
104
+ lastFrameTsRef.current = ts;
105
+ const targetCount = targetCountRef.current;
106
+ const displayedCount = displayedCountRef.current;
107
+ const backlog = targetCount - displayedCount;
108
+ if (backlog <= 0) {
109
+ stopFrameLoop();
110
+ return;
111
+ }
112
+ const idleMs = performance.now() - lastInputTsRef.current;
113
+ const inputActive = idleMs <= config.activeInputWindowMs;
114
+ const settling = !inputActive && idleMs >= config.settleAfterMs;
115
+ const baseCps = clamp(emaCpsRef.current, config.minCps, config.maxCps);
116
+ const baseLagChars = Math.max(1, Math.round(baseCps * config.targetBufferMs / 1e3));
117
+ const lagUpperBound = Math.max(baseLagChars + 2, baseLagChars * 3);
118
+ const targetLagChars = inputActive ? Math.round(clamp(baseLagChars + chunkSizeEmaRef.current * .35, baseLagChars, lagUpperBound)) : 0;
119
+ const desiredDisplayed = Math.max(0, targetCount - targetLagChars);
120
+ let currentCps;
121
+ if (inputActive) {
122
+ const backlogPressure = targetLagChars > 0 ? backlog / targetLagChars : 1;
123
+ const chunkPressure = targetLagChars > 0 ? chunkSizeEmaRef.current / targetLagChars : 1;
124
+ const arrivalPressure = arrivalCpsEmaRef.current / Math.max(baseCps, 1);
125
+ const combinedPressure = clamp(backlogPressure * .6 + chunkPressure * .25 + arrivalPressure * .15, 1, 4.5);
126
+ const activeCap = clamp(config.maxActiveCps + chunkSizeEmaRef.current * 6, config.maxActiveCps, config.maxFlushCps);
127
+ currentCps = clamp(baseCps * combinedPressure, config.minCps, activeCap);
128
+ } else if (settling) {
129
+ const drainTargetMs = clamp(backlog * 8, config.settleDrainMinMs, config.settleDrainMaxMs);
130
+ currentCps = clamp(backlog * 1e3 / drainTargetMs, config.flushCps, config.maxFlushCps);
131
+ } else currentCps = clamp(Math.max(config.flushCps, baseCps * 1.8, arrivalCpsEmaRef.current * .8), config.flushCps, config.maxFlushCps);
132
+ const urgentBacklog = inputActive && targetLagChars > 0 && backlog > targetLagChars * 2.2;
133
+ const burstyInput = inputActive && chunkSizeEmaRef.current >= targetLagChars * .9;
134
+ const minRevealChars = inputActive ? urgentBacklog || burstyInput ? 2 : 1 : 2;
135
+ let revealChars = Math.max(minRevealChars, Math.round(currentCps * dtSeconds));
136
+ if (inputActive) {
137
+ const shortfall = desiredDisplayed - displayedCount;
138
+ if (shortfall <= 0) {
139
+ rafRef.current = requestAnimationFrame(tick);
140
+ return;
141
+ }
142
+ revealChars = Math.min(revealChars, shortfall, backlog);
143
+ } else revealChars = Math.min(revealChars, backlog);
144
+ const nextCount = displayedCount + revealChars;
145
+ const segment = targetCharsRef.current.slice(displayedCount, nextCount).join("");
146
+ if (segment) {
147
+ const nextDisplayed = displayedContentRef.current + segment;
148
+ displayedContentRef.current = nextDisplayed;
149
+ displayedCountRef.current = nextCount;
150
+ setDisplayedContent(nextDisplayed);
151
+ } else {
152
+ displayedContentRef.current = targetContentRef.current;
153
+ displayedCountRef.current = targetCount;
154
+ setDisplayedContent(targetContentRef.current);
155
+ }
156
+ rafRef.current = requestAnimationFrame(tick);
157
+ };
158
+ rafRef.current = requestAnimationFrame(tick);
159
+ }, [
160
+ config.activeInputWindowMs,
161
+ config.flushCps,
162
+ config.maxActiveCps,
163
+ config.maxCps,
164
+ config.maxFlushCps,
165
+ config.minCps,
166
+ config.settleAfterMs,
167
+ config.settleDrainMaxMs,
168
+ config.settleDrainMinMs,
169
+ config.targetBufferMs,
170
+ stopFrameLoop
171
+ ]);
172
+ useEffect(() => {
173
+ if (!enabled) {
174
+ syncImmediate(content);
175
+ return;
176
+ }
177
+ const prevTargetContent = targetContentRef.current;
178
+ if (content === prevTargetContent) return;
179
+ const now = performance.now();
180
+ if (!content.startsWith(prevTargetContent)) {
181
+ syncImmediate(content);
182
+ return;
183
+ }
184
+ const appendedChars = [...content.slice(prevTargetContent.length)];
185
+ const appendedCount = appendedChars.length;
186
+ if (appendedCount > config.largeAppendChars) {
187
+ syncImmediate(content);
188
+ return;
189
+ }
190
+ targetContentRef.current = content;
191
+ targetCharsRef.current = [...targetCharsRef.current, ...appendedChars];
192
+ targetCountRef.current += appendedCount;
193
+ const deltaChars = targetCountRef.current - lastInputCountRef.current;
194
+ const deltaMs = Math.max(1, now - lastInputTsRef.current);
195
+ if (deltaChars > 0) {
196
+ const instantCps = deltaChars * 1e3 / deltaMs;
197
+ const normalizedInstantCps = clamp(instantCps, config.minCps, config.maxFlushCps * 2);
198
+ const chunkEmaAlpha = .35;
199
+ chunkSizeEmaRef.current = chunkSizeEmaRef.current * (1 - chunkEmaAlpha) + appendedCount * chunkEmaAlpha;
200
+ arrivalCpsEmaRef.current = arrivalCpsEmaRef.current * (1 - chunkEmaAlpha) + normalizedInstantCps * chunkEmaAlpha;
201
+ const clampedCps = clamp(instantCps, config.minCps, config.maxActiveCps);
202
+ emaCpsRef.current = emaCpsRef.current * (1 - config.emaAlpha) + clampedCps * config.emaAlpha;
203
+ }
204
+ lastInputTsRef.current = now;
205
+ lastInputCountRef.current = targetCountRef.current;
206
+ startFrameLoop();
207
+ }, [
208
+ config.emaAlpha,
209
+ config.largeAppendChars,
210
+ config.maxActiveCps,
211
+ config.maxCps,
212
+ config.maxFlushCps,
213
+ config.minCps,
214
+ content,
215
+ enabled,
216
+ startFrameLoop,
217
+ syncImmediate
218
+ ]);
219
+ useEffect(() => {
220
+ return () => {
221
+ stopFrameLoop();
222
+ };
223
+ }, [stopFrameLoop]);
224
+ return displayedContent;
225
+ };
226
+
227
+ //#endregion
228
+ export { useSmoothStreamContent };
229
+ //# sourceMappingURL=useSmoothStreamContent.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSmoothStreamContent.mjs","names":["PRESET_CONFIG: Record<StreamSmoothingPreset, StreamSmoothingPresetConfig>","currentCps: number"],"sources":["../../../src/Markdown/SyntaxMarkdown/useSmoothStreamContent.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { type StreamSmoothingPreset } from '@/Markdown/type';\n\ninterface StreamSmoothingPresetConfig {\n activeInputWindowMs: number;\n defaultCps: number;\n emaAlpha: number;\n flushCps: number;\n largeAppendChars: number;\n maxActiveCps: number;\n maxCps: number;\n maxFlushCps: number;\n minCps: number;\n settleAfterMs: number;\n settleDrainMaxMs: number;\n settleDrainMinMs: number;\n targetBufferMs: number;\n}\n\nconst PRESET_CONFIG: Record<StreamSmoothingPreset, StreamSmoothingPresetConfig> = {\n balanced: {\n activeInputWindowMs: 220,\n defaultCps: 38,\n emaAlpha: 0.2,\n flushCps: 120,\n largeAppendChars: 120,\n maxActiveCps: 132,\n maxCps: 72,\n maxFlushCps: 280,\n minCps: 18,\n settleAfterMs: 360,\n settleDrainMaxMs: 520,\n settleDrainMinMs: 180,\n targetBufferMs: 120,\n },\n realtime: {\n activeInputWindowMs: 140,\n defaultCps: 50,\n emaAlpha: 0.3,\n flushCps: 170,\n largeAppendChars: 180,\n maxActiveCps: 180,\n maxCps: 96,\n maxFlushCps: 360,\n minCps: 24,\n settleAfterMs: 260,\n settleDrainMaxMs: 360,\n settleDrainMinMs: 140,\n targetBufferMs: 40,\n },\n silky: {\n activeInputWindowMs: 320,\n defaultCps: 28,\n emaAlpha: 0.14,\n flushCps: 96,\n largeAppendChars: 100,\n maxActiveCps: 102,\n maxCps: 56,\n maxFlushCps: 220,\n minCps: 14,\n settleAfterMs: 460,\n settleDrainMaxMs: 680,\n settleDrainMinMs: 240,\n targetBufferMs: 170,\n },\n};\n\nconst clamp = (value: number, min: number, max: number): number => {\n return Math.min(max, Math.max(min, value));\n};\n\nexport const countChars = (text: string): number => {\n return [...text].length;\n};\n\ninterface UseSmoothStreamContentOptions {\n enabled?: boolean;\n preset?: StreamSmoothingPreset;\n}\n\nexport const useSmoothStreamContent = (\n content: string,\n { enabled = true, preset = 'balanced' }: UseSmoothStreamContentOptions = {},\n): string => {\n const config = PRESET_CONFIG[preset];\n const [displayedContent, setDisplayedContent] = useState(content);\n\n const displayedContentRef = useRef(content);\n const displayedCountRef = useRef(countChars(content));\n\n const targetContentRef = useRef(content);\n const targetCharsRef = useRef([...content]);\n const targetCountRef = useRef(targetCharsRef.current.length);\n\n const emaCpsRef = useRef(config.defaultCps);\n const lastInputTsRef = useRef(0);\n const lastInputCountRef = useRef(targetCountRef.current);\n const chunkSizeEmaRef = useRef(1);\n const arrivalCpsEmaRef = useRef(config.defaultCps);\n\n const rafRef = useRef<number | null>(null);\n const lastFrameTsRef = useRef<number | null>(null);\n\n const stopFrameLoop = useCallback(() => {\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n lastFrameTsRef.current = null;\n }, []);\n\n const syncImmediate = useCallback(\n (nextContent: string) => {\n stopFrameLoop();\n\n const chars = [...nextContent];\n const now = performance.now();\n\n targetContentRef.current = nextContent;\n targetCharsRef.current = chars;\n targetCountRef.current = chars.length;\n\n displayedContentRef.current = nextContent;\n displayedCountRef.current = chars.length;\n setDisplayedContent(nextContent);\n\n emaCpsRef.current = config.defaultCps;\n chunkSizeEmaRef.current = 1;\n arrivalCpsEmaRef.current = config.defaultCps;\n lastInputTsRef.current = now;\n lastInputCountRef.current = chars.length;\n },\n [config.defaultCps, stopFrameLoop],\n );\n\n const startFrameLoop = useCallback(() => {\n if (rafRef.current !== null) return;\n\n const tick = (ts: number) => {\n if (lastFrameTsRef.current === null) {\n lastFrameTsRef.current = ts;\n rafRef.current = requestAnimationFrame(tick);\n return;\n }\n\n const dtSeconds = Math.max(0.001, Math.min((ts - lastFrameTsRef.current) / 1000, 0.05));\n lastFrameTsRef.current = ts;\n\n const targetCount = targetCountRef.current;\n const displayedCount = displayedCountRef.current;\n const backlog = targetCount - displayedCount;\n\n if (backlog <= 0) {\n stopFrameLoop();\n return;\n }\n\n const now = performance.now();\n const idleMs = now - lastInputTsRef.current;\n const inputActive = idleMs <= config.activeInputWindowMs;\n const settling = !inputActive && idleMs >= config.settleAfterMs;\n\n const baseCps = clamp(emaCpsRef.current, config.minCps, config.maxCps);\n const baseLagChars = Math.max(1, Math.round((baseCps * config.targetBufferMs) / 1000));\n const lagUpperBound = Math.max(baseLagChars + 2, baseLagChars * 3);\n const targetLagChars = inputActive\n ? Math.round(\n clamp(baseLagChars + chunkSizeEmaRef.current * 0.35, baseLagChars, lagUpperBound),\n )\n : 0;\n const desiredDisplayed = Math.max(0, targetCount - targetLagChars);\n\n let currentCps: number;\n if (inputActive) {\n const backlogPressure = targetLagChars > 0 ? backlog / targetLagChars : 1;\n const chunkPressure = targetLagChars > 0 ? chunkSizeEmaRef.current / targetLagChars : 1;\n const arrivalPressure = arrivalCpsEmaRef.current / Math.max(baseCps, 1);\n const combinedPressure = clamp(\n backlogPressure * 0.6 + chunkPressure * 0.25 + arrivalPressure * 0.15,\n 1,\n 4.5,\n );\n const activeCap = clamp(\n config.maxActiveCps + chunkSizeEmaRef.current * 6,\n config.maxActiveCps,\n config.maxFlushCps,\n );\n currentCps = clamp(baseCps * combinedPressure, config.minCps, activeCap);\n } else if (settling) {\n // If upstream likely ended, cap the remaining tail duration so\n // we do not keep replaying old backlog for seconds.\n const drainTargetMs = clamp(backlog * 8, config.settleDrainMinMs, config.settleDrainMaxMs);\n const settleCps = (backlog * 1000) / drainTargetMs;\n currentCps = clamp(settleCps, config.flushCps, config.maxFlushCps);\n } else {\n const idleFlushCps = Math.max(\n config.flushCps,\n baseCps * 1.8,\n arrivalCpsEmaRef.current * 0.8,\n );\n currentCps = clamp(idleFlushCps, config.flushCps, config.maxFlushCps);\n }\n\n const urgentBacklog = inputActive && targetLagChars > 0 && backlog > targetLagChars * 2.2;\n const burstyInput = inputActive && chunkSizeEmaRef.current >= targetLagChars * 0.9;\n const minRevealChars = inputActive ? (urgentBacklog || burstyInput ? 2 : 1) : 2;\n let revealChars = Math.max(minRevealChars, Math.round(currentCps * dtSeconds));\n\n if (inputActive) {\n const shortfall = desiredDisplayed - displayedCount;\n if (shortfall <= 0) {\n rafRef.current = requestAnimationFrame(tick);\n return;\n }\n revealChars = Math.min(revealChars, shortfall, backlog);\n } else {\n revealChars = Math.min(revealChars, backlog);\n }\n\n const nextCount = displayedCount + revealChars;\n const segment = targetCharsRef.current.slice(displayedCount, nextCount).join('');\n\n if (segment) {\n const nextDisplayed = displayedContentRef.current + segment;\n displayedContentRef.current = nextDisplayed;\n displayedCountRef.current = nextCount;\n setDisplayedContent(nextDisplayed);\n } else {\n displayedContentRef.current = targetContentRef.current;\n displayedCountRef.current = targetCount;\n setDisplayedContent(targetContentRef.current);\n }\n\n rafRef.current = requestAnimationFrame(tick);\n };\n\n rafRef.current = requestAnimationFrame(tick);\n }, [\n config.activeInputWindowMs,\n config.flushCps,\n config.maxActiveCps,\n config.maxCps,\n config.maxFlushCps,\n config.minCps,\n config.settleAfterMs,\n config.settleDrainMaxMs,\n config.settleDrainMinMs,\n config.targetBufferMs,\n stopFrameLoop,\n ]);\n\n useEffect(() => {\n if (!enabled) {\n syncImmediate(content);\n return;\n }\n\n const prevTargetContent = targetContentRef.current;\n if (content === prevTargetContent) return;\n\n const now = performance.now();\n const appendOnly = content.startsWith(prevTargetContent);\n\n if (!appendOnly) {\n syncImmediate(content);\n return;\n }\n\n const appended = content.slice(prevTargetContent.length);\n const appendedChars = [...appended];\n const appendedCount = appendedChars.length;\n\n if (appendedCount > config.largeAppendChars) {\n syncImmediate(content);\n return;\n }\n\n targetContentRef.current = content;\n targetCharsRef.current = [...targetCharsRef.current, ...appendedChars];\n targetCountRef.current += appendedCount;\n\n const deltaChars = targetCountRef.current - lastInputCountRef.current;\n const deltaMs = Math.max(1, now - lastInputTsRef.current);\n\n if (deltaChars > 0) {\n const instantCps = (deltaChars * 1000) / deltaMs;\n const normalizedInstantCps = clamp(instantCps, config.minCps, config.maxFlushCps * 2);\n const chunkEmaAlpha = 0.35;\n chunkSizeEmaRef.current =\n chunkSizeEmaRef.current * (1 - chunkEmaAlpha) + appendedCount * chunkEmaAlpha;\n arrivalCpsEmaRef.current =\n arrivalCpsEmaRef.current * (1 - chunkEmaAlpha) + normalizedInstantCps * chunkEmaAlpha;\n\n const clampedCps = clamp(instantCps, config.minCps, config.maxActiveCps);\n emaCpsRef.current = emaCpsRef.current * (1 - config.emaAlpha) + clampedCps * config.emaAlpha;\n }\n\n lastInputTsRef.current = now;\n lastInputCountRef.current = targetCountRef.current;\n\n startFrameLoop();\n }, [\n config.emaAlpha,\n config.largeAppendChars,\n config.maxActiveCps,\n config.maxCps,\n config.maxFlushCps,\n config.minCps,\n content,\n enabled,\n startFrameLoop,\n syncImmediate,\n ]);\n\n useEffect(() => {\n return () => {\n stopFrameLoop();\n };\n }, [stopFrameLoop]);\n\n return displayedContent;\n};\n"],"mappings":"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"}
@@ -1,10 +1,10 @@
1
1
  import { useCallback, useEffect, useRef, useState } from "react";
2
2
 
3
3
  //#region src/Markdown/SyntaxMarkdown/useStreamQueue.ts
4
- const BASE_DELAY = 20;
4
+ const BASE_DELAY = 18;
5
5
  const ACCELERATION_FACTOR = .3;
6
6
  const MAX_BLOCK_DURATION = 3e3;
7
- const FADE_DURATION = 150;
7
+ const FADE_DURATION = 280;
8
8
  function countChars(text) {
9
9
  return [...text].length;
10
10
  }
@@ -45,15 +45,17 @@ function useStreamQueue(blocks) {
45
45
  const queueLength = Math.max(0, tailIndex - effectiveRevealedCount - 1);
46
46
  const animatingIndex = effectiveRevealedCount < tailIndex ? effectiveRevealedCount : -1;
47
47
  const animatingCharCount = animatingIndex >= 0 ? countChars(blocks[animatingIndex]?.content ?? "") : 0;
48
+ const activeIndex = animatingIndex >= 0 ? animatingIndex : animatingIndex < 0 && tailIndex >= effectiveRevealedCount ? tailIndex : -1;
49
+ const activeCharCount = activeIndex >= 0 ? countChars(blocks[activeIndex]?.content ?? "") : 0;
48
50
  const frozenRef = useRef({
49
51
  delay: BASE_DELAY,
50
52
  index: -1
51
53
  });
52
- if (animatingIndex >= 0 && animatingIndex !== frozenRef.current.index) frozenRef.current = {
53
- delay: computeCharDelay(queueLength, animatingCharCount),
54
- index: animatingIndex
54
+ if (activeIndex >= 0 && activeIndex !== frozenRef.current.index) frozenRef.current = {
55
+ delay: computeCharDelay(queueLength, activeCharCount),
56
+ index: activeIndex
55
57
  };
56
- const charDelay = animatingIndex >= 0 ? frozenRef.current.delay : BASE_DELAY;
58
+ const charDelay = activeIndex >= 0 ? frozenRef.current.delay : BASE_DELAY;
57
59
  const onAnimationDone = useCallback(() => {
58
60
  setRevealedCount(effectiveRevealedCount + 1);
59
61
  }, [effectiveRevealedCount]);
@@ -1 +1 @@
1
- {"version":3,"file":"useStreamQueue.mjs","names":[],"sources":["../../../src/Markdown/SyntaxMarkdown/useStreamQueue.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\n\nexport interface BlockInfo {\n content: string;\n startOffset: number;\n}\n\nexport type BlockState = 'revealed' | 'animating' | 'streaming' | 'queued';\n\nconst BASE_DELAY = 20;\nconst ACCELERATION_FACTOR = 0.3;\nconst MAX_BLOCK_DURATION = 3000;\nconst FADE_DURATION = 150;\n\nfunction countChars(text: string): number {\n return [...text].length;\n}\n\nfunction computeCharDelay(queueLength: number, charCount: number): number {\n const acceleration = 1 + queueLength * ACCELERATION_FACTOR;\n let delay = BASE_DELAY / acceleration;\n delay = Math.min(delay, MAX_BLOCK_DURATION / Math.max(charCount, 1));\n return delay;\n}\n\nexport interface UseStreamQueueReturn {\n charDelay: number;\n getBlockState: (index: number) => BlockState;\n queueLength: number;\n}\n\nexport function useStreamQueue(blocks: BlockInfo[]): UseStreamQueueReturn {\n const [revealedCount, setRevealedCount] = useState(0);\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const prevBlocksLenRef = useRef(0);\n const minRevealedRef = useRef(0);\n\n // Synchronous auto-reveal during render.\n // When blocks grow, the previous tail (streaming block) is instantly\n // promoted to revealed — its chars are already visible via stream-mode\n // animation. This runs during render (not in effect) so there is NO\n // intermediate frame where the old streaming block enters 'animating'\n // state and gets stagger plugins that would restart its animations.\n if (blocks.length === 0 && prevBlocksLenRef.current !== 0) {\n minRevealedRef.current = 0;\n }\n if (blocks.length > prevBlocksLenRef.current && prevBlocksLenRef.current > 0) {\n const prevTail = prevBlocksLenRef.current - 1;\n minRevealedRef.current = Math.max(minRevealedRef.current, prevTail + 1);\n }\n prevBlocksLenRef.current = blocks.length;\n\n // State reset when stream restarts (blocks empty)\n useEffect(() => {\n if (blocks.length === 0) {\n setRevealedCount(0);\n minRevealedRef.current = 0;\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n }\n }, [blocks.length]);\n\n const effectiveRevealedCount = Math.max(revealedCount, minRevealedRef.current);\n const tailIndex = blocks.length - 1;\n\n const getBlockState = useCallback(\n (index: number): BlockState => {\n if (index < effectiveRevealedCount) return 'revealed';\n if (index === effectiveRevealedCount && index < tailIndex) return 'animating';\n if (index === effectiveRevealedCount && index === tailIndex) return 'streaming';\n return 'queued';\n },\n [effectiveRevealedCount, tailIndex],\n );\n\n const queueLength = Math.max(0, tailIndex - effectiveRevealedCount - 1);\n\n const animatingIndex = effectiveRevealedCount < tailIndex ? effectiveRevealedCount : -1;\n const animatingCharCount =\n animatingIndex >= 0 ? countChars(blocks[animatingIndex]?.content ?? '') : 0;\n\n // Freeze charDelay when a new block starts animating\n const frozenRef = useRef({ delay: BASE_DELAY, index: -1 });\n if (animatingIndex >= 0 && animatingIndex !== frozenRef.current.index) {\n frozenRef.current = {\n delay: computeCharDelay(queueLength, animatingCharCount),\n index: animatingIndex,\n };\n }\n const charDelay = animatingIndex >= 0 ? frozenRef.current.delay : BASE_DELAY;\n\n const onAnimationDone = useCallback(() => {\n setRevealedCount(effectiveRevealedCount + 1);\n }, [effectiveRevealedCount]);\n\n useEffect(() => {\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n\n if (animatingIndex < 0) return;\n\n const totalTime = Math.max(0, (animatingCharCount - 1) * charDelay) + FADE_DURATION;\n timerRef.current = setTimeout(onAnimationDone, totalTime);\n\n return () => {\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n };\n }, [animatingIndex, animatingCharCount, charDelay, onAnimationDone]);\n\n return { charDelay, getBlockState, queueLength };\n}\n"],"mappings":";;;AASA,MAAM,aAAa;AACnB,MAAM,sBAAsB;AAC5B,MAAM,qBAAqB;AAC3B,MAAM,gBAAgB;AAEtB,SAAS,WAAW,MAAsB;AACxC,QAAO,CAAC,GAAG,KAAK,CAAC;;AAGnB,SAAS,iBAAiB,aAAqB,WAA2B;CAExE,IAAI,QAAQ,cADS,IAAI,cAAc;AAEvC,SAAQ,KAAK,IAAI,OAAO,qBAAqB,KAAK,IAAI,WAAW,EAAE,CAAC;AACpE,QAAO;;AAST,SAAgB,eAAe,QAA2C;CACxE,MAAM,CAAC,eAAe,oBAAoB,SAAS,EAAE;CACrD,MAAM,WAAW,OAA6C,KAAK;CACnE,MAAM,mBAAmB,OAAO,EAAE;CAClC,MAAM,iBAAiB,OAAO,EAAE;AAQhC,KAAI,OAAO,WAAW,KAAK,iBAAiB,YAAY,EACtD,gBAAe,UAAU;AAE3B,KAAI,OAAO,SAAS,iBAAiB,WAAW,iBAAiB,UAAU,GAAG;EAC5E,MAAM,WAAW,iBAAiB,UAAU;AAC5C,iBAAe,UAAU,KAAK,IAAI,eAAe,SAAS,WAAW,EAAE;;AAEzE,kBAAiB,UAAU,OAAO;AAGlC,iBAAgB;AACd,MAAI,OAAO,WAAW,GAAG;AACvB,oBAAiB,EAAE;AACnB,kBAAe,UAAU;AACzB,OAAI,SAAS,SAAS;AACpB,iBAAa,SAAS,QAAQ;AAC9B,aAAS,UAAU;;;IAGtB,CAAC,OAAO,OAAO,CAAC;CAEnB,MAAM,yBAAyB,KAAK,IAAI,eAAe,eAAe,QAAQ;CAC9E,MAAM,YAAY,OAAO,SAAS;CAElC,MAAM,gBAAgB,aACnB,UAA8B;AAC7B,MAAI,QAAQ,uBAAwB,QAAO;AAC3C,MAAI,UAAU,0BAA0B,QAAQ,UAAW,QAAO;AAClE,MAAI,UAAU,0BAA0B,UAAU,UAAW,QAAO;AACpE,SAAO;IAET,CAAC,wBAAwB,UAAU,CACpC;CAED,MAAM,cAAc,KAAK,IAAI,GAAG,YAAY,yBAAyB,EAAE;CAEvE,MAAM,iBAAiB,yBAAyB,YAAY,yBAAyB;CACrF,MAAM,qBACJ,kBAAkB,IAAI,WAAW,OAAO,iBAAiB,WAAW,GAAG,GAAG;CAG5E,MAAM,YAAY,OAAO;EAAE,OAAO;EAAY,OAAO;EAAI,CAAC;AAC1D,KAAI,kBAAkB,KAAK,mBAAmB,UAAU,QAAQ,MAC9D,WAAU,UAAU;EAClB,OAAO,iBAAiB,aAAa,mBAAmB;EACxD,OAAO;EACR;CAEH,MAAM,YAAY,kBAAkB,IAAI,UAAU,QAAQ,QAAQ;CAElE,MAAM,kBAAkB,kBAAkB;AACxC,mBAAiB,yBAAyB,EAAE;IAC3C,CAAC,uBAAuB,CAAC;AAE5B,iBAAgB;AACd,MAAI,SAAS,SAAS;AACpB,gBAAa,SAAS,QAAQ;AAC9B,YAAS,UAAU;;AAGrB,MAAI,iBAAiB,EAAG;EAExB,MAAM,YAAY,KAAK,IAAI,IAAI,qBAAqB,KAAK,UAAU,GAAG;AACtE,WAAS,UAAU,WAAW,iBAAiB,UAAU;AAEzD,eAAa;AACX,OAAI,SAAS,SAAS;AACpB,iBAAa,SAAS,QAAQ;AAC9B,aAAS,UAAU;;;IAGtB;EAAC;EAAgB;EAAoB;EAAW;EAAgB,CAAC;AAEpE,QAAO;EAAE;EAAW;EAAe;EAAa"}
1
+ {"version":3,"file":"useStreamQueue.mjs","names":[],"sources":["../../../src/Markdown/SyntaxMarkdown/useStreamQueue.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\n\nexport interface BlockInfo {\n content: string;\n startOffset: number;\n}\n\nexport type BlockState = 'revealed' | 'animating' | 'streaming' | 'queued';\n\nconst BASE_DELAY = 18;\nconst ACCELERATION_FACTOR = 0.3;\nconst MAX_BLOCK_DURATION = 3000;\nconst FADE_DURATION = 280;\n\nfunction countChars(text: string): number {\n return [...text].length;\n}\n\nfunction computeCharDelay(queueLength: number, charCount: number): number {\n const acceleration = 1 + queueLength * ACCELERATION_FACTOR;\n let delay = BASE_DELAY / acceleration;\n delay = Math.min(delay, MAX_BLOCK_DURATION / Math.max(charCount, 1));\n return delay;\n}\n\nexport interface UseStreamQueueReturn {\n charDelay: number;\n getBlockState: (index: number) => BlockState;\n queueLength: number;\n}\n\nexport function useStreamQueue(blocks: BlockInfo[]): UseStreamQueueReturn {\n const [revealedCount, setRevealedCount] = useState(0);\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const prevBlocksLenRef = useRef(0);\n const minRevealedRef = useRef(0);\n\n // Synchronous auto-reveal during render.\n // When blocks grow, the previous tail (streaming block) is instantly\n // promoted to revealed — its chars are already visible via stream-mode\n // animation. This runs during render (not in effect) so there is NO\n // intermediate frame where the old streaming block enters 'animating'\n // state and gets stagger plugins that would restart its animations.\n if (blocks.length === 0 && prevBlocksLenRef.current !== 0) {\n minRevealedRef.current = 0;\n }\n if (blocks.length > prevBlocksLenRef.current && prevBlocksLenRef.current > 0) {\n const prevTail = prevBlocksLenRef.current - 1;\n minRevealedRef.current = Math.max(minRevealedRef.current, prevTail + 1);\n }\n prevBlocksLenRef.current = blocks.length;\n\n // State reset when stream restarts (blocks empty)\n useEffect(() => {\n if (blocks.length === 0) {\n setRevealedCount(0);\n minRevealedRef.current = 0;\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n }\n }, [blocks.length]);\n\n const effectiveRevealedCount = Math.max(revealedCount, minRevealedRef.current);\n const tailIndex = blocks.length - 1;\n\n const getBlockState = useCallback(\n (index: number): BlockState => {\n if (index < effectiveRevealedCount) return 'revealed';\n if (index === effectiveRevealedCount && index < tailIndex) return 'animating';\n if (index === effectiveRevealedCount && index === tailIndex) return 'streaming';\n return 'queued';\n },\n [effectiveRevealedCount, tailIndex],\n );\n\n const queueLength = Math.max(0, tailIndex - effectiveRevealedCount - 1);\n\n const animatingIndex = effectiveRevealedCount < tailIndex ? effectiveRevealedCount : -1;\n const animatingCharCount =\n animatingIndex >= 0 ? countChars(blocks[animatingIndex]?.content ?? '') : 0;\n\n const streamingIndex = animatingIndex < 0 && tailIndex >= effectiveRevealedCount ? tailIndex : -1;\n const activeIndex = animatingIndex >= 0 ? animatingIndex : streamingIndex;\n const activeCharCount = activeIndex >= 0 ? countChars(blocks[activeIndex]?.content ?? '') : 0;\n\n // Freeze charDelay when entering a new active block (animating or streaming)\n const frozenRef = useRef({ delay: BASE_DELAY, index: -1 });\n if (activeIndex >= 0 && activeIndex !== frozenRef.current.index) {\n frozenRef.current = {\n delay: computeCharDelay(queueLength, activeCharCount),\n index: activeIndex,\n };\n }\n const charDelay = activeIndex >= 0 ? frozenRef.current.delay : BASE_DELAY;\n\n const onAnimationDone = useCallback(() => {\n setRevealedCount(effectiveRevealedCount + 1);\n }, [effectiveRevealedCount]);\n\n useEffect(() => {\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n\n if (animatingIndex < 0) return;\n\n const totalTime = Math.max(0, (animatingCharCount - 1) * charDelay) + FADE_DURATION;\n timerRef.current = setTimeout(onAnimationDone, totalTime);\n\n return () => {\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n };\n }, [animatingIndex, animatingCharCount, charDelay, onAnimationDone]);\n\n return { charDelay, getBlockState, queueLength };\n}\n"],"mappings":";;;AASA,MAAM,aAAa;AACnB,MAAM,sBAAsB;AAC5B,MAAM,qBAAqB;AAC3B,MAAM,gBAAgB;AAEtB,SAAS,WAAW,MAAsB;AACxC,QAAO,CAAC,GAAG,KAAK,CAAC;;AAGnB,SAAS,iBAAiB,aAAqB,WAA2B;CAExE,IAAI,QAAQ,cADS,IAAI,cAAc;AAEvC,SAAQ,KAAK,IAAI,OAAO,qBAAqB,KAAK,IAAI,WAAW,EAAE,CAAC;AACpE,QAAO;;AAST,SAAgB,eAAe,QAA2C;CACxE,MAAM,CAAC,eAAe,oBAAoB,SAAS,EAAE;CACrD,MAAM,WAAW,OAA6C,KAAK;CACnE,MAAM,mBAAmB,OAAO,EAAE;CAClC,MAAM,iBAAiB,OAAO,EAAE;AAQhC,KAAI,OAAO,WAAW,KAAK,iBAAiB,YAAY,EACtD,gBAAe,UAAU;AAE3B,KAAI,OAAO,SAAS,iBAAiB,WAAW,iBAAiB,UAAU,GAAG;EAC5E,MAAM,WAAW,iBAAiB,UAAU;AAC5C,iBAAe,UAAU,KAAK,IAAI,eAAe,SAAS,WAAW,EAAE;;AAEzE,kBAAiB,UAAU,OAAO;AAGlC,iBAAgB;AACd,MAAI,OAAO,WAAW,GAAG;AACvB,oBAAiB,EAAE;AACnB,kBAAe,UAAU;AACzB,OAAI,SAAS,SAAS;AACpB,iBAAa,SAAS,QAAQ;AAC9B,aAAS,UAAU;;;IAGtB,CAAC,OAAO,OAAO,CAAC;CAEnB,MAAM,yBAAyB,KAAK,IAAI,eAAe,eAAe,QAAQ;CAC9E,MAAM,YAAY,OAAO,SAAS;CAElC,MAAM,gBAAgB,aACnB,UAA8B;AAC7B,MAAI,QAAQ,uBAAwB,QAAO;AAC3C,MAAI,UAAU,0BAA0B,QAAQ,UAAW,QAAO;AAClE,MAAI,UAAU,0BAA0B,UAAU,UAAW,QAAO;AACpE,SAAO;IAET,CAAC,wBAAwB,UAAU,CACpC;CAED,MAAM,cAAc,KAAK,IAAI,GAAG,YAAY,yBAAyB,EAAE;CAEvE,MAAM,iBAAiB,yBAAyB,YAAY,yBAAyB;CACrF,MAAM,qBACJ,kBAAkB,IAAI,WAAW,OAAO,iBAAiB,WAAW,GAAG,GAAG;CAG5E,MAAM,cAAc,kBAAkB,IAAI,iBADnB,iBAAiB,KAAK,aAAa,yBAAyB,YAAY;CAE/F,MAAM,kBAAkB,eAAe,IAAI,WAAW,OAAO,cAAc,WAAW,GAAG,GAAG;CAG5F,MAAM,YAAY,OAAO;EAAE,OAAO;EAAY,OAAO;EAAI,CAAC;AAC1D,KAAI,eAAe,KAAK,gBAAgB,UAAU,QAAQ,MACxD,WAAU,UAAU;EAClB,OAAO,iBAAiB,aAAa,gBAAgB;EACrD,OAAO;EACR;CAEH,MAAM,YAAY,eAAe,IAAI,UAAU,QAAQ,QAAQ;CAE/D,MAAM,kBAAkB,kBAAkB;AACxC,mBAAiB,yBAAyB,EAAE;IAC3C,CAAC,uBAAuB,CAAC;AAE5B,iBAAgB;AACd,MAAI,SAAS,SAAS;AACpB,gBAAa,SAAS,QAAQ;AAC9B,YAAS,UAAU;;AAGrB,MAAI,iBAAiB,EAAG;EAExB,MAAM,YAAY,KAAK,IAAI,IAAI,qBAAqB,KAAK,UAAU,GAAG;AACtE,WAAS,UAAU,WAAW,iBAAiB,UAAU;AAEzD,eAAa;AACX,OAAI,SAAS,SAAS;AACpB,iBAAa,SAAS,QAAQ;AAC9B,aAAS,UAAU;;;IAGtB;EAAC;EAAgB;EAAoB;EAAW;EAAgB,CAAC;AAEpE,QAAO;EAAE;EAAW;EAAe;EAAa"}
@@ -1,8 +1,8 @@
1
1
  import { TypographyProps } from "./type.mjs";
2
- import * as react57 from "react";
2
+ import * as react36 from "react";
3
3
 
4
4
  //#region src/Markdown/Typography.d.ts
5
- declare const Typography: react57.NamedExoticComponent<TypographyProps>;
5
+ declare const Typography: react36.NamedExoticComponent<TypographyProps>;
6
6
  //#endregion
7
7
  export { Typography };
8
8
  //# sourceMappingURL=Typography.d.mts.map
@@ -1,6 +1,6 @@
1
1
  import { FlexboxProps } from "../../../Flex/type.mjs";
2
2
  import "../../../Flex/index.mjs";
3
- import * as react30 from "react";
3
+ import * as react76 from "react";
4
4
  import { Ref } from "react";
5
5
 
6
6
  //#region src/Markdown/components/SearchResultCards/index.d.ts
@@ -14,7 +14,7 @@ interface SearchResultCardsProps extends FlexboxProps {
14
14
  dataSource: string[] | SearchResultItem[];
15
15
  ref?: Ref<HTMLDivElement>;
16
16
  }
17
- declare const SearchResultCards: react30.NamedExoticComponent<SearchResultCardsProps>;
17
+ declare const SearchResultCards: react76.NamedExoticComponent<SearchResultCardsProps>;
18
18
  //#endregion
19
19
  export { SearchResultCards, SearchResultCardsProps };
20
20
  //# sourceMappingURL=index.d.mts.map
@@ -1,4 +1,4 @@
1
- import { MarkdownProps, SyntaxMarkdownProps, TypographyProps } from "./type.mjs";
1
+ import { MarkdownProps, StreamSmoothingPreset, SyntaxMarkdownProps, TypographyProps } from "./type.mjs";
2
2
  import { Markdown } from "./Markdown.mjs";
3
3
  import { Typography } from "./Typography.mjs";
4
- export { MarkdownProps, SyntaxMarkdownProps, Typography, TypographyProps, Markdown as default };
4
+ export { MarkdownProps, StreamSmoothingPreset, SyntaxMarkdownProps, Typography, TypographyProps, Markdown as default };
@@ -4,7 +4,9 @@ import { Root } from "hast";
4
4
  interface StreamAnimatedOptions {
5
5
  baseCharCount?: number;
6
6
  charDelay?: number;
7
+ fadeDuration?: number;
7
8
  revealed?: boolean;
9
+ timelineElapsedMs?: number;
8
10
  }
9
11
  declare const rehypeStreamAnimated: (options?: StreamAnimatedOptions) => (tree: Root) => void;
10
12
  //#endregion
@@ -24,7 +24,8 @@ function hasClass(node, cls) {
24
24
  return false;
25
25
  }
26
26
  const rehypeStreamAnimated = (options = {}) => {
27
- const { charDelay = 20, baseCharCount = 0, revealed = false } = options;
27
+ const { charDelay = 20, fadeDuration = 150, baseCharCount = 0, revealed = false, timelineElapsedMs } = options;
28
+ const hasTimeline = typeof timelineElapsedMs === "number" && Number.isFinite(timelineElapsedMs);
28
29
  return (tree) => {
29
30
  let globalCharIndex = 0;
30
31
  const shouldSkip = (node) => {
@@ -33,11 +34,22 @@ const rehypeStreamAnimated = (options = {}) => {
33
34
  const wrapText = (node) => {
34
35
  const newChildren = [];
35
36
  for (const child of node.children) if (child.type === "text") for (const char of child.value) {
36
- const properties = { className: revealed ? "stream-char stream-char-revealed" : "stream-char" };
37
- if (!revealed) {
38
- const delay = Math.max(0, globalCharIndex - baseCharCount) * charDelay;
39
- if (delay > 0) properties.style = `animation-delay:${delay}ms`;
37
+ const relativeIndex = globalCharIndex - baseCharCount;
38
+ let className = "stream-char";
39
+ let delay;
40
+ if (revealed) className = "stream-char stream-char-revealed";
41
+ else if (hasTimeline) {
42
+ const progress = timelineElapsedMs - globalCharIndex * charDelay;
43
+ if (progress >= fadeDuration) className = "stream-char stream-char-revealed";
44
+ else delay = -progress;
45
+ } else if (relativeIndex >= 0) delay = relativeIndex * charDelay;
46
+ else {
47
+ const elapsed = -relativeIndex * charDelay;
48
+ if (elapsed >= fadeDuration) className = "stream-char stream-char-revealed";
49
+ else delay = -elapsed;
40
50
  }
51
+ const properties = { className };
52
+ if (delay !== void 0 && delay !== 0) properties.style = `animation-delay:${delay}ms`;
41
53
  newChildren.push({
42
54
  children: [{
43
55
  type: "text",
@@ -1 +1 @@
1
- {"version":3,"file":"rehypeStreamAnimated.mjs","names":["newChildren: ElementContent[]","properties: Record<string, any>"],"sources":["../../../src/Markdown/plugins/rehypeStreamAnimated.ts"],"sourcesContent":["import { type Element, type ElementContent, type Root } from 'hast';\nimport { type BuildVisitor } from 'unist-util-visit';\nimport { visit } from 'unist-util-visit';\n\nexport interface StreamAnimatedOptions {\n baseCharCount?: number;\n charDelay?: number;\n revealed?: boolean;\n}\n\nconst BLOCK_TAGS = new Set(['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li']);\nconst SKIP_TAGS = new Set(['pre', 'code', 'table', 'svg']);\n\nfunction hasClass(node: Element, cls: string): boolean {\n const cn = node.properties?.className;\n if (Array.isArray(cn)) return cn.some((c) => String(c).includes(cls));\n if (typeof cn === 'string') return cn.includes(cls);\n return false;\n}\n\nexport const rehypeStreamAnimated = (options: StreamAnimatedOptions = {}) => {\n const { charDelay = 20, baseCharCount = 0, revealed = false } = options;\n\n return (tree: Root) => {\n let globalCharIndex = 0;\n\n const shouldSkip = (node: Element): boolean => {\n return SKIP_TAGS.has(node.tagName) || hasClass(node, 'katex');\n };\n\n const wrapText = (node: Element) => {\n const newChildren: ElementContent[] = [];\n for (const child of node.children) {\n if (child.type === 'text') {\n for (const char of child.value) {\n const properties: Record<string, any> = {\n className: revealed ? 'stream-char stream-char-revealed' : 'stream-char',\n };\n if (!revealed) {\n const relativeIndex = Math.max(0, globalCharIndex - baseCharCount);\n const delay = relativeIndex * charDelay;\n if (delay > 0) {\n properties.style = `animation-delay:${delay}ms`;\n }\n }\n newChildren.push({\n children: [{ type: 'text', value: char }],\n properties,\n tagName: 'span',\n type: 'element',\n });\n globalCharIndex++;\n }\n } else if (child.type === 'element') {\n if (!shouldSkip(child)) {\n wrapText(child);\n }\n newChildren.push(child);\n } else {\n newChildren.push(child);\n }\n }\n node.children = newChildren;\n };\n\n visit(tree, 'element', ((node: Element) => {\n if (shouldSkip(node)) return 'skip';\n if (BLOCK_TAGS.has(node.tagName)) {\n wrapText(node);\n return 'skip';\n }\n }) as BuildVisitor<Root, 'element'>);\n };\n};\n"],"mappings":";;;AAUA,MAAM,aAAa,IAAI,IAAI;CAAC;CAAK;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAK,CAAC;AAC3E,MAAM,YAAY,IAAI,IAAI;CAAC;CAAO;CAAQ;CAAS;CAAM,CAAC;AAE1D,SAAS,SAAS,MAAe,KAAsB;CACrD,MAAM,KAAK,KAAK,YAAY;AAC5B,KAAI,MAAM,QAAQ,GAAG,CAAE,QAAO,GAAG,MAAM,MAAM,OAAO,EAAE,CAAC,SAAS,IAAI,CAAC;AACrE,KAAI,OAAO,OAAO,SAAU,QAAO,GAAG,SAAS,IAAI;AACnD,QAAO;;AAGT,MAAa,wBAAwB,UAAiC,EAAE,KAAK;CAC3E,MAAM,EAAE,YAAY,IAAI,gBAAgB,GAAG,WAAW,UAAU;AAEhE,SAAQ,SAAe;EACrB,IAAI,kBAAkB;EAEtB,MAAM,cAAc,SAA2B;AAC7C,UAAO,UAAU,IAAI,KAAK,QAAQ,IAAI,SAAS,MAAM,QAAQ;;EAG/D,MAAM,YAAY,SAAkB;GAClC,MAAMA,cAAgC,EAAE;AACxC,QAAK,MAAM,SAAS,KAAK,SACvB,KAAI,MAAM,SAAS,OACjB,MAAK,MAAM,QAAQ,MAAM,OAAO;IAC9B,MAAMC,aAAkC,EACtC,WAAW,WAAW,qCAAqC,eAC5D;AACD,QAAI,CAAC,UAAU;KAEb,MAAM,QADgB,KAAK,IAAI,GAAG,kBAAkB,cAAc,GACpC;AAC9B,SAAI,QAAQ,EACV,YAAW,QAAQ,mBAAmB,MAAM;;AAGhD,gBAAY,KAAK;KACf,UAAU,CAAC;MAAE,MAAM;MAAQ,OAAO;MAAM,CAAC;KACzC;KACA,SAAS;KACT,MAAM;KACP,CAAC;AACF;;YAEO,MAAM,SAAS,WAAW;AACnC,QAAI,CAAC,WAAW,MAAM,CACpB,UAAS,MAAM;AAEjB,gBAAY,KAAK,MAAM;SAEvB,aAAY,KAAK,MAAM;AAG3B,QAAK,WAAW;;AAGlB,QAAM,MAAM,aAAa,SAAkB;AACzC,OAAI,WAAW,KAAK,CAAE,QAAO;AAC7B,OAAI,WAAW,IAAI,KAAK,QAAQ,EAAE;AAChC,aAAS,KAAK;AACd,WAAO;;KAEyB"}
1
+ {"version":3,"file":"rehypeStreamAnimated.mjs","names":["newChildren: ElementContent[]","delay: number | undefined","properties: Record<string, any>"],"sources":["../../../src/Markdown/plugins/rehypeStreamAnimated.ts"],"sourcesContent":["import { type Element, type ElementContent, type Root } from 'hast';\nimport { type BuildVisitor } from 'unist-util-visit';\nimport { visit } from 'unist-util-visit';\n\nexport interface StreamAnimatedOptions {\n baseCharCount?: number;\n charDelay?: number;\n fadeDuration?: number;\n revealed?: boolean;\n timelineElapsedMs?: number;\n}\n\nconst BLOCK_TAGS = new Set(['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li']);\nconst SKIP_TAGS = new Set(['pre', 'code', 'table', 'svg']);\n\nfunction hasClass(node: Element, cls: string): boolean {\n const cn = node.properties?.className;\n if (Array.isArray(cn)) return cn.some((c) => String(c).includes(cls));\n if (typeof cn === 'string') return cn.includes(cls);\n return false;\n}\n\nexport const rehypeStreamAnimated = (options: StreamAnimatedOptions = {}) => {\n const {\n charDelay = 20,\n fadeDuration = 150,\n baseCharCount = 0,\n revealed = false,\n timelineElapsedMs,\n } = options;\n const hasTimeline = typeof timelineElapsedMs === 'number' && Number.isFinite(timelineElapsedMs);\n\n return (tree: Root) => {\n let globalCharIndex = 0;\n\n const shouldSkip = (node: Element): boolean => {\n return SKIP_TAGS.has(node.tagName) || hasClass(node, 'katex');\n };\n\n const wrapText = (node: Element) => {\n const newChildren: ElementContent[] = [];\n for (const child of node.children) {\n if (child.type === 'text') {\n for (const char of child.value) {\n const relativeIndex = globalCharIndex - baseCharCount;\n let className = 'stream-char';\n let delay: number | undefined;\n\n if (revealed) {\n className = 'stream-char stream-char-revealed';\n } else if (hasTimeline) {\n const progress = (timelineElapsedMs as number) - globalCharIndex * charDelay;\n if (progress >= fadeDuration) {\n className = 'stream-char stream-char-revealed';\n } else {\n // Positive delay means \"not started yet\", negative keeps\n // the current in-flight progress on rerender.\n delay = -progress;\n }\n } else if (relativeIndex >= 0) {\n // Newly appended chars start with staggered positive delay.\n delay = relativeIndex * charDelay;\n } else {\n // Previously started chars continue fading with negative delay\n // instead of being immediately switched to revealed.\n const elapsed = -relativeIndex * charDelay;\n if (elapsed >= fadeDuration) {\n className = 'stream-char stream-char-revealed';\n } else {\n delay = -elapsed;\n }\n }\n\n const properties: Record<string, any> = { className };\n if (delay !== undefined && delay !== 0) {\n properties.style = `animation-delay:${delay}ms`;\n }\n newChildren.push({\n children: [{ type: 'text', value: char }],\n properties,\n tagName: 'span',\n type: 'element',\n });\n globalCharIndex++;\n }\n } else if (child.type === 'element') {\n if (!shouldSkip(child)) {\n wrapText(child);\n }\n newChildren.push(child);\n } else {\n newChildren.push(child);\n }\n }\n node.children = newChildren;\n };\n\n visit(tree, 'element', ((node: Element) => {\n if (shouldSkip(node)) return 'skip';\n if (BLOCK_TAGS.has(node.tagName)) {\n wrapText(node);\n return 'skip';\n }\n }) as BuildVisitor<Root, 'element'>);\n };\n};\n"],"mappings":";;;AAYA,MAAM,aAAa,IAAI,IAAI;CAAC;CAAK;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAK,CAAC;AAC3E,MAAM,YAAY,IAAI,IAAI;CAAC;CAAO;CAAQ;CAAS;CAAM,CAAC;AAE1D,SAAS,SAAS,MAAe,KAAsB;CACrD,MAAM,KAAK,KAAK,YAAY;AAC5B,KAAI,MAAM,QAAQ,GAAG,CAAE,QAAO,GAAG,MAAM,MAAM,OAAO,EAAE,CAAC,SAAS,IAAI,CAAC;AACrE,KAAI,OAAO,OAAO,SAAU,QAAO,GAAG,SAAS,IAAI;AACnD,QAAO;;AAGT,MAAa,wBAAwB,UAAiC,EAAE,KAAK;CAC3E,MAAM,EACJ,YAAY,IACZ,eAAe,KACf,gBAAgB,GAChB,WAAW,OACX,sBACE;CACJ,MAAM,cAAc,OAAO,sBAAsB,YAAY,OAAO,SAAS,kBAAkB;AAE/F,SAAQ,SAAe;EACrB,IAAI,kBAAkB;EAEtB,MAAM,cAAc,SAA2B;AAC7C,UAAO,UAAU,IAAI,KAAK,QAAQ,IAAI,SAAS,MAAM,QAAQ;;EAG/D,MAAM,YAAY,SAAkB;GAClC,MAAMA,cAAgC,EAAE;AACxC,QAAK,MAAM,SAAS,KAAK,SACvB,KAAI,MAAM,SAAS,OACjB,MAAK,MAAM,QAAQ,MAAM,OAAO;IAC9B,MAAM,gBAAgB,kBAAkB;IACxC,IAAI,YAAY;IAChB,IAAIC;AAEJ,QAAI,SACF,aAAY;aACH,aAAa;KACtB,MAAM,WAAY,oBAA+B,kBAAkB;AACnE,SAAI,YAAY,aACd,aAAY;SAIZ,SAAQ,CAAC;eAEF,iBAAiB,EAE1B,SAAQ,gBAAgB;SACnB;KAGL,MAAM,UAAU,CAAC,gBAAgB;AACjC,SAAI,WAAW,aACb,aAAY;SAEZ,SAAQ,CAAC;;IAIb,MAAMC,aAAkC,EAAE,WAAW;AACrD,QAAI,UAAU,UAAa,UAAU,EACnC,YAAW,QAAQ,mBAAmB,MAAM;AAE9C,gBAAY,KAAK;KACf,UAAU,CAAC;MAAE,MAAM;MAAQ,OAAO;MAAM,CAAC;KACzC;KACA,SAAS;KACT,MAAM;KACP,CAAC;AACF;;YAEO,MAAM,SAAS,WAAW;AACnC,QAAI,CAAC,WAAW,MAAM,CACpB,UAAS,MAAM;AAEjB,gBAAY,KAAK,MAAM;SAEvB,aAAY,KAAK,MAAM;AAG3B,QAAK,WAAW;;AAGlB,QAAM,MAAM,aAAa,SAAkB;AACzC,OAAI,WAAW,KAAK,CAAE,QAAO;AAC7B,OAAI,WAAW,IAAI,KAAK,QAAQ,EAAE;AAChC,aAAS,KAAK;AACd,WAAO;;KAEyB"}
@@ -22,6 +22,7 @@ interface TypographyProps extends DivProps {
22
22
  marginMultiple?: number;
23
23
  ref?: Ref<HTMLDivElement>;
24
24
  }
25
+ type StreamSmoothingPreset = 'realtime' | 'balanced' | 'silky';
25
26
  interface SyntaxMarkdownProps {
26
27
  allowHtml?: boolean;
27
28
  allowHtmlList?: ElementType[];
@@ -49,6 +50,7 @@ interface SyntaxMarkdownProps {
49
50
  remarkPlugins?: Pluggable[];
50
51
  remarkPluginsAhead?: Pluggable[];
51
52
  showFootnotes?: boolean;
53
+ streamSmoothingPreset?: StreamSmoothingPreset;
52
54
  variant?: 'default' | 'chat';
53
55
  }
54
56
  interface MarkdownProps extends SyntaxMarkdownProps, Omit<TypographyProps, 'children'> {
@@ -62,5 +64,5 @@ interface MarkdownProps extends SyntaxMarkdownProps, Omit<TypographyProps, 'chil
62
64
  style?: CSSProperties;
63
65
  }
64
66
  //#endregion
65
- export { MarkdownProps, SyntaxMarkdownProps, TypographyProps };
67
+ export { MarkdownProps, StreamSmoothingPreset, SyntaxMarkdownProps, TypographyProps };
66
68
  //# sourceMappingURL=type.d.mts.map
@@ -1,8 +1,8 @@
1
1
  import { MaskShadowProps } from "./type.mjs";
2
- import * as react56 from "react";
2
+ import * as react22 from "react";
3
3
 
4
4
  //#region src/MaskShadow/MaskShadow.d.ts
5
- declare const MaskShadow: react56.NamedExoticComponent<MaskShadowProps>;
5
+ declare const MaskShadow: react22.NamedExoticComponent<MaskShadowProps>;
6
6
  //#endregion
7
7
  export { MaskShadow };
8
8
  //# sourceMappingURL=MaskShadow.d.mts.map
@@ -1,8 +1,8 @@
1
1
  import { MenuProps } from "./type.mjs";
2
- import * as react61 from "react";
2
+ import * as react79 from "react";
3
3
 
4
4
  //#region src/Menu/Menu.d.ts
5
- declare const Menu: react61.NamedExoticComponent<MenuProps<unknown>>;
5
+ declare const Menu: react79.NamedExoticComponent<MenuProps<unknown>>;
6
6
  //#endregion
7
7
  export { Menu };
8
8
  //# sourceMappingURL=Menu.d.mts.map
@@ -1,8 +1,8 @@
1
1
  import { MermaidProps } from "./type.mjs";
2
- import * as react60 from "react";
2
+ import * as react12 from "react";
3
3
 
4
4
  //#region src/Mermaid/Mermaid.d.ts
5
- declare const Mermaid: react60.NamedExoticComponent<MermaidProps>;
5
+ declare const Mermaid: react12.NamedExoticComponent<MermaidProps>;
6
6
  //#endregion
7
7
  export { Mermaid };
8
8
  //# sourceMappingURL=Mermaid.d.mts.map
@@ -1,8 +1,8 @@
1
1
  import { SyntaxMermaidProps } from "../type.mjs";
2
- import * as react59 from "react";
2
+ import * as react13 from "react";
3
3
 
4
4
  //#region src/Mermaid/SyntaxMermaid/index.d.ts
5
- declare const SyntaxMermaid: react59.NamedExoticComponent<SyntaxMermaidProps>;
5
+ declare const SyntaxMermaid: react13.NamedExoticComponent<SyntaxMermaidProps>;
6
6
  //#endregion
7
7
  export { SyntaxMermaid };
8
8
  //# sourceMappingURL=index.d.mts.map
@@ -1,8 +1,8 @@
1
1
  import { ModalProps } from "./type.mjs";
2
- import * as react49 from "react";
2
+ import * as react19 from "react";
3
3
 
4
4
  //#region src/Modal/Modal.d.ts
5
- declare const Modal: react49.NamedExoticComponent<ModalProps>;
5
+ declare const Modal: react19.NamedExoticComponent<ModalProps>;
6
6
  //#endregion
7
7
  export { Modal };
8
8
  //# sourceMappingURL=Modal.d.mts.map
@@ -1,9 +1,9 @@
1
1
  import { ModalContextValue } from "./type.mjs";
2
- import * as react50 from "react";
2
+ import * as react20 from "react";
3
3
  import { ReactNode } from "react";
4
4
 
5
5
  //#region src/Modal/ModalProvider.d.ts
6
- declare const ModalProvider: react50.NamedExoticComponent<{
6
+ declare const ModalProvider: react20.NamedExoticComponent<{
7
7
  children: ReactNode;
8
8
  value: ModalContextValue;
9
9
  }>;
@@ -1,5 +1,5 @@
1
1
  import { ImperativeModalProps, ModalInstance, RawModalComponent, RawModalComponentProps, RawModalInstance, RawModalKeyOptions, RawModalOptions } from "./type.mjs";
2
- import * as react_jsx_runtime11 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime6 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/Modal/imperative.d.ts
5
5
  type ModalHostProps = {
@@ -7,7 +7,7 @@ type ModalHostProps = {
7
7
  };
8
8
  declare const ModalHost: ({
9
9
  root
10
- }: ModalHostProps) => react_jsx_runtime11.JSX.Element | null;
10
+ }: ModalHostProps) => react_jsx_runtime6.JSX.Element | null;
11
11
  declare const createModal: (props: ImperativeModalProps) => ModalInstance;
12
12
  declare function createRawModal<P extends RawModalComponentProps>(component: RawModalComponent<P>, props: Omit<P, 'open' | 'onClose'>, options?: RawModalOptions): RawModalInstance<P>;
13
13
  declare function createRawModal<P, OpenKey extends keyof P, CloseKey extends keyof P>(component: RawModalComponent<P>, props: Omit<P, OpenKey | CloseKey>, options: RawModalKeyOptions<OpenKey, CloseKey>): RawModalInstance<P, OpenKey, CloseKey>;
@@ -1,4 +1,4 @@
1
- import * as react31 from "react";
1
+ import * as react77 from "react";
2
2
  import { Context, ReactNode } from "react";
3
3
  import * as motion_react0 from "motion/react";
4
4
  import * as m from "motion/react-m";
@@ -6,7 +6,7 @@ import * as m from "motion/react-m";
6
6
  //#region src/MotionProvider/index.d.ts
7
7
  type MotionComponentType = typeof motion_react0.motion | typeof m;
8
8
  declare const MotionComponent: Context<MotionComponentType>;
9
- declare const MotionProvider: react31.NamedExoticComponent<{
9
+ declare const MotionProvider: react77.NamedExoticComponent<{
10
10
  children: ReactNode;
11
11
  motion: MotionComponentType;
12
12
  }>;
@@ -1,7 +1,7 @@
1
- import * as react_jsx_runtime12 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
2
2
 
3
3
  //#region src/Popover/ArrowIcon.d.ts
4
- declare const PopoverArrowIcon: react_jsx_runtime12.JSX.Element;
4
+ declare const PopoverArrowIcon: react_jsx_runtime0.JSX.Element;
5
5
  //#endregion
6
6
  export { PopoverArrowIcon };
7
7
  //# sourceMappingURL=ArrowIcon.d.mts.map