@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.
- package/es/Accordion/Accordion.d.mts +2 -2
- package/es/Accordion/AccordionItem.d.mts +2 -2
- package/es/ActionIcon/ActionIcon.d.mts +2 -2
- package/es/Alert/Alert.d.mts +2 -2
- package/es/AutoComplete/Select.d.mts +2 -2
- package/es/Avatar/AvatarGroup/index.d.mts +2 -2
- package/es/Burger/Burger.d.mts +2 -2
- package/es/CodeDiff/CodeDiff.d.mts +2 -2
- package/es/CodeDiff/PatchDiff.d.mts +2 -2
- package/es/CodeEditor/CodeEditor.d.mts +2 -2
- package/es/Collapse/Collapse.d.mts +2 -2
- package/es/ConfigProvider/index.d.mts +2 -2
- package/es/CopyButton/CopyButton.d.mts +2 -2
- package/es/DatePicker/DatePicker.d.mts +2 -2
- package/es/DraggablePanel/components/DraggablePanelBody.d.mts +2 -2
- package/es/DraggablePanel/components/DraggablePanelContainer.d.mts +2 -2
- package/es/DraggablePanel/components/DraggablePanelFooter.d.mts +2 -2
- package/es/DraggablePanel/components/DraggablePanelHeader.d.mts +2 -2
- package/es/DraggableSideNav/DraggableSideNav.d.mts +2 -2
- package/es/Drawer/Drawer.d.mts +2 -2
- package/es/Dropdown/Dropdown.d.mts +2 -2
- package/es/EditableText/EditableText.d.mts +2 -2
- package/es/EditorSlashMenu/atoms.d.mts +13 -13
- package/es/EmojiPicker/EmojiPicker.d.mts +2 -2
- package/es/Flex/FlexBasic.d.mts +2 -2
- package/es/FontLoader/index.d.mts +2 -2
- package/es/Footer/Footer.d.mts +2 -2
- package/es/Form/components/FormGroup.d.mts +2 -2
- package/es/Form/components/FormItem.d.mts +2 -2
- package/es/Form/components/FormSubmitFooter.d.mts +2 -2
- package/es/FormModal/FormModal.d.mts +2 -2
- package/es/Freeze/Freeze.d.mts +2 -2
- package/es/GuideCard/GuideCard.d.mts +2 -2
- package/es/Header/Header.d.mts +2 -2
- package/es/Highlighter/Highlighter.d.mts +2 -2
- package/es/Highlighter/SyntaxHighlighter/index.d.mts +2 -2
- package/es/Hotkey/Hotkey.d.mts +2 -2
- package/es/HotkeyInput/HotkeyInput.d.mts +2 -2
- package/es/HotkeyInput/HotkeyInput.mjs +30 -8
- package/es/HotkeyInput/HotkeyInput.mjs.map +1 -1
- package/es/HotkeyInput/type.d.mts +3 -0
- package/es/Icon/Icon.d.mts +2 -2
- package/es/Icon/components/IconProvider.d.mts +3 -3
- package/es/Image/PreviewGroup.d.mts +2 -2
- package/es/ImageSelect/ImageSelect.d.mts +2 -2
- package/es/Input/Input.d.mts +2 -2
- package/es/Input/InputNumber.d.mts +2 -2
- package/es/Input/InputOPT.d.mts +2 -2
- package/es/Input/InputPassword.d.mts +2 -2
- package/es/Input/TextArea.d.mts +2 -2
- package/es/Layout/components/LayoutFooter.d.mts +2 -2
- package/es/Layout/components/LayoutHeader.d.mts +2 -2
- package/es/Layout/components/LayoutMain.d.mts +2 -2
- package/es/Layout/components/LayoutSidebar.d.mts +2 -2
- package/es/Layout/components/LayoutSidebarInner.d.mts +2 -2
- package/es/Layout/components/LayoutToc.d.mts +2 -2
- package/es/List/ListItem/index.d.mts +2 -2
- package/es/Markdown/Markdown.d.mts +2 -2
- package/es/Markdown/Markdown.mjs +2 -1
- package/es/Markdown/Markdown.mjs.map +1 -1
- package/es/Markdown/SyntaxMarkdown/StreamdownRender.mjs +56 -30
- package/es/Markdown/SyntaxMarkdown/StreamdownRender.mjs.map +1 -1
- package/es/Markdown/SyntaxMarkdown/style.mjs +2 -2
- package/es/Markdown/SyntaxMarkdown/style.mjs.map +1 -1
- package/es/Markdown/SyntaxMarkdown/useSmoothStreamContent.mjs +229 -0
- package/es/Markdown/SyntaxMarkdown/useSmoothStreamContent.mjs.map +1 -0
- package/es/Markdown/SyntaxMarkdown/useStreamQueue.mjs +8 -6
- package/es/Markdown/SyntaxMarkdown/useStreamQueue.mjs.map +1 -1
- package/es/Markdown/Typography.d.mts +2 -2
- package/es/Markdown/components/SearchResultCards/index.d.mts +2 -2
- package/es/Markdown/index.d.mts +2 -2
- package/es/Markdown/plugins/rehypeStreamAnimated.d.mts +2 -0
- package/es/Markdown/plugins/rehypeStreamAnimated.mjs +17 -5
- package/es/Markdown/plugins/rehypeStreamAnimated.mjs.map +1 -1
- package/es/Markdown/type.d.mts +3 -1
- package/es/MaskShadow/MaskShadow.d.mts +2 -2
- package/es/Menu/Menu.d.mts +2 -2
- package/es/Mermaid/Mermaid.d.mts +2 -2
- package/es/Mermaid/SyntaxMermaid/index.d.mts +2 -2
- package/es/Modal/Modal.d.mts +2 -2
- package/es/Modal/ModalProvider.d.mts +2 -2
- package/es/Modal/imperative.d.mts +2 -2
- package/es/MotionProvider/index.d.mts +2 -2
- package/es/Popover/ArrowIcon.d.mts +2 -2
- package/es/Popover/atoms.d.mts +9 -9
- package/es/Popover/context.d.mts +2 -2
- package/es/SearchBar/SearchBar.d.mts +2 -2
- package/es/Segmented/Segmented.d.mts +2 -2
- package/es/Select/Select.d.mts +2 -2
- package/es/SideNav/SideNav.d.mts +2 -2
- package/es/SliderWithInput/SliderWithInput.d.mts +2 -2
- package/es/SortableList/components/DragHandle.d.mts +2 -2
- package/es/SortableList/components/SortableItem.d.mts +2 -2
- package/es/ThemeProvider/ThemeProvider.d.mts +2 -2
- package/es/Toc/Toc.d.mts +2 -2
- package/es/Video/index.d.mts +2 -2
- package/es/awesome/AuroraBackground/AuroraBackground.d.mts +2 -2
- package/es/awesome/BottomGradientButton/BottomGradientButton.d.mts +2 -2
- package/es/awesome/Features/Features.d.mts +2 -2
- package/es/awesome/Giscus/Giscus.d.mts +2 -2
- package/es/awesome/GradientButton/GradientButton.d.mts +2 -2
- package/es/awesome/GridBackground/GridBackground.d.mts +2 -2
- package/es/awesome/GridBackground/GridShowcase.d.mts +2 -2
- package/es/awesome/Hero/Hero.d.mts +2 -2
- package/es/awesome/Spline/Spine.d.mts +2 -2
- package/es/awesome/Spotlight/Spotlight.d.mts +2 -2
- package/es/awesome/SpotlightCard/SpotlightCard.d.mts +2 -2
- package/es/awesome/TypewriterEffect/TypewriterEffect.d.mts +2 -2
- package/es/base-ui/Modal/atoms.d.mts +12 -12
- package/es/base-ui/Modal/context.d.mts +2 -2
- package/es/base-ui/Modal/imperative.d.mts +2 -2
- package/es/base-ui/Select/Select.d.mts +2 -2
- package/es/base-ui/Select/atoms.d.mts +19 -19
- package/es/base-ui/Switch/Switch.d.mts +2 -2
- package/es/base-ui/Switch/atoms.d.mts +4 -4
- package/es/base-ui/Toast/imperative.d.mts +2 -2
- package/es/brand/LobeChat/index.d.mts +2 -2
- package/es/brand/LobeHub/index.d.mts +2 -2
- package/es/brand/LogoThree/LogoSpline.d.mts +2 -2
- package/es/brand/LogoThree/index.d.mts +2 -2
- package/es/chat/BackBottom/BackBottom.d.mts +2 -2
- package/es/chat/ChatInputArea/components/ChatInputAreaInner.d.mts +2 -2
- package/es/chat/ChatItem/ChatItem.d.mts +2 -2
- package/es/chat/ChatList/ChatList.d.mts +2 -2
- package/es/chat/EditableMessage/EditableMessage.d.mts +2 -2
- package/es/chat/EditableMessageList/EditableMessageList.d.mts +2 -2
- package/es/chat/MessageInput/MessageInput.d.mts +2 -2
- package/es/chat/MessageModal/MessageModal.d.mts +2 -2
- package/es/color/ColorScales/index.d.mts +2 -2
- package/es/color/CssVar/index.d.mts +2 -2
- package/es/i18n/context.d.mts +2 -2
- package/es/i18n/resources/en/hotkey.d.mts +1 -0
- package/es/i18n/resources/en/hotkey.mjs +1 -0
- package/es/i18n/resources/en/hotkey.mjs.map +1 -1
- package/es/i18n/resources/zhCn/hotkey.d.mts +1 -0
- package/es/i18n/resources/zhCn/hotkey.mjs +1 -0
- package/es/i18n/resources/zhCn/hotkey.mjs.map +1 -1
- package/es/icons/lucideExtra/BotPromptIcon.d.mts +2 -2
- package/es/icons/lucideExtra/CreateBotIcon.d.mts +3 -3
- package/es/icons/lucideExtra/DiscordIcon.d.mts +2 -2
- package/es/icons/lucideExtra/GlobeOffIcon.d.mts +2 -2
- package/es/icons/lucideExtra/GroupBotIcon.d.mts +3 -3
- package/es/icons/lucideExtra/GroupBotSquareIcon.d.mts +3 -3
- package/es/icons/lucideExtra/LeftClickIcon.d.mts +3 -3
- package/es/icons/lucideExtra/LeftDoubleClickIcon.d.mts +3 -3
- package/es/icons/lucideExtra/McpIcon.d.mts +3 -3
- package/es/icons/lucideExtra/ProviderIcon.d.mts +3 -3
- package/es/icons/lucideExtra/RightClickIcon.d.mts +3 -3
- package/es/icons/lucideExtra/RightDoubleClickIcon.d.mts +3 -3
- package/es/icons/lucideExtra/ShapesUploadIcon.d.mts +3 -3
- package/es/icons/lucideExtra/SkillsIcon.d.mts +3 -3
- package/es/icons/lucideExtra/TreeDownRightIcon.d.mts +2 -2
- package/es/icons/lucideExtra/TreeUpDownRightIcon.d.mts +3 -3
- package/es/mdx/Mdx/index.d.mts +2 -2
- package/es/mobile/ChatHeader/ChatHeaderTitle.d.mts +2 -2
- package/es/mobile/ChatInputArea/components/ChatSendButton.d.mts +2 -2
- package/es/mobile/TabBar/TabBar.d.mts +2 -2
- package/es/storybook/StoryBook/index.d.mts +2 -2
- package/package.json +1 -1
|
@@ -12,11 +12,11 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
12
12
|
import { cx, useThemeMode } from "antd-style";
|
|
13
13
|
import useMergeState from "use-merge-value";
|
|
14
14
|
import { isEqual } from "es-toolkit/compat";
|
|
15
|
-
import { Undo2Icon } from "lucide-react";
|
|
15
|
+
import { Undo2Icon, XIcon } from "lucide-react";
|
|
16
16
|
import { useHotkeys, useRecordHotkeys } from "react-hotkeys-hook";
|
|
17
17
|
|
|
18
18
|
//#region src/HotkeyInput/HotkeyInput.tsx
|
|
19
|
-
const HotkeyInput = memo(({ value = "", defaultValue = "", resetValue = "", onChange, onConflict, placeholder, disabled, shadow, allowReset = true, style, className, hotkeyConflicts = [], variant, texts, isApple, onBlur, onReset, onFocus }) => {
|
|
19
|
+
const HotkeyInput = memo(({ value = "", defaultValue = "", resetValue = "", onChange, onClear, onConflict, placeholder, disabled, shadow, allowClear, allowReset = true, style, className, hotkeyConflicts = [], variant, texts, isApple, onBlur, onReset, onFocus }) => {
|
|
20
20
|
const [isFocused, setIsFocused] = useState(false);
|
|
21
21
|
const [hasConflict, setHasConflict] = useState(false);
|
|
22
22
|
const [hasInvalidCombination, setHasInvalidCombination] = useState(false);
|
|
@@ -106,6 +106,17 @@ const HotkeyInput = memo(({ value = "", defaultValue = "", resetValue = "", onCh
|
|
|
106
106
|
stop();
|
|
107
107
|
onBlur?.(e);
|
|
108
108
|
};
|
|
109
|
+
const handleClear = (e) => {
|
|
110
|
+
e.preventDefault();
|
|
111
|
+
e.stopPropagation();
|
|
112
|
+
setHotkeyValue?.("");
|
|
113
|
+
resetKeys();
|
|
114
|
+
setHasConflict(false);
|
|
115
|
+
setHasInvalidCombination(false);
|
|
116
|
+
setIsFocused(false);
|
|
117
|
+
stop();
|
|
118
|
+
onClear?.(hotkeyValue);
|
|
119
|
+
};
|
|
109
120
|
const handleReset = (e) => {
|
|
110
121
|
e.preventDefault();
|
|
111
122
|
e.stopPropagation();
|
|
@@ -125,6 +136,7 @@ const HotkeyInput = memo(({ value = "", defaultValue = "", resetValue = "", onCh
|
|
|
125
136
|
};
|
|
126
137
|
const placeholderText = placeholder ?? t("hotkey.placeholder");
|
|
127
138
|
const resetTitle = texts?.reset ?? t("hotkey.reset");
|
|
139
|
+
const clearTitle = texts?.clear ?? t("hotkey.clear");
|
|
128
140
|
const conflictText = texts?.conflicts ?? t("hotkey.conflict");
|
|
129
141
|
const invalidText = texts?.invalidCombination ?? t("hotkey.invalidCombination");
|
|
130
142
|
return /* @__PURE__ */ jsxs(FlexBasic_default, {
|
|
@@ -167,12 +179,22 @@ const HotkeyInput = memo(({ value = "", defaultValue = "", resetValue = "", onCh
|
|
|
167
179
|
onBlur: handleBlur,
|
|
168
180
|
onFocus: handleFocus
|
|
169
181
|
}),
|
|
170
|
-
!isFocused &&
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
182
|
+
!isFocused && hotkeyValue && !disabled && (allowReset || allowClear) && /* @__PURE__ */ jsxs(FlexBasic_default, {
|
|
183
|
+
horizontal: true,
|
|
184
|
+
gap: 4,
|
|
185
|
+
children: [allowReset && hotkeyValue !== resetValue && /* @__PURE__ */ jsx(ActionIcon_default, {
|
|
186
|
+
icon: Undo2Icon,
|
|
187
|
+
size: "small",
|
|
188
|
+
title: resetTitle,
|
|
189
|
+
variant: "filled",
|
|
190
|
+
onClick: handleReset
|
|
191
|
+
}), allowClear && /* @__PURE__ */ jsx(ActionIcon_default, {
|
|
192
|
+
icon: XIcon,
|
|
193
|
+
size: "small",
|
|
194
|
+
title: clearTitle,
|
|
195
|
+
variant: "filled",
|
|
196
|
+
onClick: handleClear
|
|
197
|
+
})]
|
|
176
198
|
})
|
|
177
199
|
]
|
|
178
200
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HotkeyInput.mjs","names":["hotkeyMessages","useControlledState","modifiers: string[]","normalKeys: string[]","normalizedKey: any","Flexbox","Hotkey","ActionIcon"],"sources":["../../src/HotkeyInput/HotkeyInput.tsx"],"sourcesContent":["'use client';\n\nimport { type InputRef } from 'antd';\nimport { cx, useThemeMode } from 'antd-style';\nimport { isEqual } from 'es-toolkit/compat';\nimport { Undo2Icon } from 'lucide-react';\nimport {\n type FocusEvent,\n memo,\n type MouseEvent,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { useHotkeys, useRecordHotkeys } from 'react-hotkeys-hook';\nimport useControlledState from 'use-merge-value';\n\nimport ActionIcon from '@/ActionIcon';\nimport { Flexbox } from '@/Flex';\nimport Hotkey from '@/Hotkey';\nimport { checkIsAppleDevice, NORMATIVE_MODIFIER, splitKeysByPlus } from '@/Hotkey/utils';\nimport hotkeyMessages from '@/i18n/resources/en/hotkey';\nimport { useTranslation } from '@/i18n/useTranslation';\n\nimport { styles, variants } from './style';\nimport { type HotkeyInputProps } from './type';\n\nconst HotkeyInput = memo<HotkeyInputProps>(\n ({\n value = '',\n defaultValue = '',\n resetValue = '',\n onChange,\n onConflict,\n placeholder,\n disabled,\n shadow,\n allowReset = true,\n style,\n className,\n hotkeyConflicts = [],\n variant,\n texts,\n isApple,\n onBlur,\n onReset,\n onFocus,\n }) => {\n const [isFocused, setIsFocused] = useState(false);\n const [hasConflict, setHasConflict] = useState(false);\n const [hasInvalidCombination, setHasInvalidCombination] = useState(false);\n const inputRef = useRef<InputRef>(null);\n const { isDarkMode } = useThemeMode();\n const { t } = useTranslation(hotkeyMessages);\n const isAppleDevice = useMemo(() => checkIsAppleDevice(isApple), [isApple]);\n const [hotkeyValue, setHotkeyValue] = useControlledState(defaultValue, {\n defaultValue,\n onChange,\n value,\n });\n\n // 使用 useRecordHotkeys 处理快捷键录入\n const [recordedKeys, { start, stop, isRecording, resetKeys }] = useRecordHotkeys();\n\n useHotkeys(\n '*',\n () => {\n inputRef.current?.blur();\n },\n {\n enableOnContentEditable: true,\n enableOnFormTags: true,\n enabled: isRecording && !disabled,\n keydown: false,\n keyup: true,\n preventDefault: true,\n },\n );\n\n // 处理按键,保证格式正确:修饰键在前,最多一个非修饰键在后\n const formatKeys = useCallback((keysSet: Set<string>) => {\n const modifiers: string[] = [];\n const normalKeys: string[] = [];\n\n for (const key of keysSet) {\n // 处理不同表示的修饰键\n const normalizedKey: any = key.toLowerCase();\n if (NORMATIVE_MODIFIER.includes(normalizedKey)) {\n // 统一修饰键表示\n if (\n (!isAppleDevice && normalizedKey === 'ctrl') ||\n (isAppleDevice && normalizedKey === 'meta')\n ) {\n if (!modifiers.includes('mod')) modifiers.push('mod');\n } else if (!modifiers.includes(normalizedKey)) {\n modifiers.push(normalizedKey);\n }\n } else {\n normalKeys.push(key);\n }\n }\n\n // 至少需要一个修饰键\n if (modifiers.length === 0 && normalKeys.length > 0) {\n return { isValid: false, keys: [] };\n }\n\n // 只允许一个非修饰键,如果有多个,只保留最后一个\n const finalKey = normalKeys.length > 0 ? [normalKeys.at(-1)] : [];\n const shortcuts = [modifiers, finalKey];\n\n return {\n // 组合必须包含至少一个按键\n isValid: shortcuts.every((k) => k.length > 0),\n keys: shortcuts.flat(),\n };\n }, []);\n\n // 获取格式化后的按键字符串\n const { isValid, keys } = formatKeys(recordedKeys);\n const keysString = keys.join('+');\n\n // 检查快捷键冲突\n const checkHotkeyConflict = useCallback(\n (newHotkey: string): boolean => {\n return hotkeyConflicts\n .filter((conflictKey) => conflictKey !== resetValue)\n .some((conflictKey) => {\n const newKeys = splitKeysByPlus(newHotkey);\n const conflictKeys = splitKeysByPlus(conflictKey);\n return isEqual(newKeys, conflictKeys);\n });\n },\n [hotkeyConflicts],\n );\n\n // 当按键组合完成时处理结果\n useEffect(() => {\n if (recordedKeys.size > 0 && !isRecording) {\n if (!isValid) {\n setHasInvalidCombination(true);\n setHasConflict(false);\n return;\n }\n\n setHasInvalidCombination(false);\n const newKeysString = keysString;\n\n // 检查冲突\n const conflict = checkHotkeyConflict(newKeysString);\n if (conflict) {\n setHasConflict(true);\n onConflict?.(newKeysString);\n } else {\n setHasConflict(false);\n setHotkeyValue?.(newKeysString);\n }\n }\n }, [\n recordedKeys,\n isRecording,\n isValid,\n keysString,\n checkHotkeyConflict,\n setHotkeyValue,\n onConflict,\n ]);\n\n // 处理输入框焦点\n const handleFocus = (e: FocusEvent<HTMLInputElement>) => {\n if (disabled) return;\n setIsFocused(true);\n setHasConflict(false);\n setHasInvalidCombination(false);\n start(); // 开始记录\n onFocus?.(e);\n };\n\n const handleBlur = (e: FocusEvent<HTMLInputElement>) => {\n setIsFocused(false);\n stop(); // 停止记录\n onBlur?.(e);\n };\n\n // 重置功能\n const handleReset = (e: MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n setHotkeyValue?.(resetValue);\n resetKeys();\n setHasConflict(false);\n setHasInvalidCombination(false);\n setIsFocused(false);\n stop(); // 停止记录\n onReset?.(hotkeyValue, resetValue);\n };\n\n const handleClick = (e: MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n if (disabled || isFocused) return;\n inputRef.current?.focus();\n };\n\n const placeholderText = placeholder ?? t('hotkey.placeholder');\n const resetTitle = texts?.reset ?? t('hotkey.reset');\n const conflictText = texts?.conflicts ?? t('hotkey.conflict');\n const invalidText = texts?.invalidCombination ?? t('hotkey.invalidCombination');\n\n return (\n <Flexbox\n className={className}\n gap={8}\n style={{\n position: 'relative',\n ...style,\n }}\n >\n <Flexbox\n horizontal\n align={'center'}\n justify={'space-between'}\n className={cx(\n variants({\n disabled,\n error: hasConflict || hasInvalidCombination,\n focused: isFocused,\n shadow,\n variant: variant || (isDarkMode ? 'filled' : 'outlined'),\n }),\n )}\n onClick={handleClick}\n >\n <div style={{ pointerEvents: 'none' }}>\n {isRecording ? (\n <span className={styles.placeholder}>\n {keys.length > 0 ? <Hotkey keys={keysString} /> : placeholderText}\n </span>\n ) : hotkeyValue ? (\n <Hotkey keys={hotkeyValue} />\n ) : (\n <span className={styles.placeholder}>{placeholderText}</span>\n )}\n </div>\n\n {/* 隐藏的输入框,用于接收焦点 */}\n <input\n readOnly\n className={styles.hiddenInput}\n disabled={disabled}\n ref={inputRef as any}\n style={{ pointerEvents: 'none' }}\n onBlur={handleBlur}\n onFocus={handleFocus}\n />\n\n {!isFocused && allowReset && hotkeyValue && hotkeyValue !== resetValue && !disabled && (\n <ActionIcon\n icon={Undo2Icon}\n size={'small'}\n title={resetTitle}\n variant={'filled'}\n onClick={handleReset}\n />\n )}\n </Flexbox>\n {hasConflict && <div className={styles.errorText}>{conflictText}</div>}\n {hasInvalidCombination && <div className={styles.errorText}>{invalidText}</div>}\n </Flexbox>\n );\n },\n);\n\nHotkeyInput.displayName = 'HotkeyInput';\n\nexport default HotkeyInput;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HotkeyInput.mjs","names":["hotkeyMessages","useControlledState","modifiers: string[]","normalKeys: string[]","normalizedKey: any","Flexbox","Hotkey","ActionIcon"],"sources":["../../src/HotkeyInput/HotkeyInput.tsx"],"sourcesContent":["'use client';\n\nimport { type InputRef } from 'antd';\nimport { cx, useThemeMode } from 'antd-style';\nimport { isEqual } from 'es-toolkit/compat';\nimport { Undo2Icon, XIcon } from 'lucide-react';\nimport {\n type FocusEvent,\n memo,\n type MouseEvent,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { useHotkeys, useRecordHotkeys } from 'react-hotkeys-hook';\nimport useControlledState from 'use-merge-value';\n\nimport ActionIcon from '@/ActionIcon';\nimport { Flexbox } from '@/Flex';\nimport Hotkey from '@/Hotkey';\nimport { checkIsAppleDevice, NORMATIVE_MODIFIER, splitKeysByPlus } from '@/Hotkey/utils';\nimport hotkeyMessages from '@/i18n/resources/en/hotkey';\nimport { useTranslation } from '@/i18n/useTranslation';\n\nimport { styles, variants } from './style';\nimport { type HotkeyInputProps } from './type';\n\nconst HotkeyInput = memo<HotkeyInputProps>(\n ({\n value = '',\n defaultValue = '',\n resetValue = '',\n onChange,\n onClear,\n onConflict,\n placeholder,\n disabled,\n shadow,\n allowClear,\n allowReset = true,\n style,\n className,\n hotkeyConflicts = [],\n variant,\n texts,\n isApple,\n onBlur,\n onReset,\n onFocus,\n }) => {\n const [isFocused, setIsFocused] = useState(false);\n const [hasConflict, setHasConflict] = useState(false);\n const [hasInvalidCombination, setHasInvalidCombination] = useState(false);\n const inputRef = useRef<InputRef>(null);\n const { isDarkMode } = useThemeMode();\n const { t } = useTranslation(hotkeyMessages);\n const isAppleDevice = useMemo(() => checkIsAppleDevice(isApple), [isApple]);\n const [hotkeyValue, setHotkeyValue] = useControlledState(defaultValue, {\n defaultValue,\n onChange,\n value,\n });\n\n // 使用 useRecordHotkeys 处理快捷键录入\n const [recordedKeys, { start, stop, isRecording, resetKeys }] = useRecordHotkeys();\n\n useHotkeys(\n '*',\n () => {\n inputRef.current?.blur();\n },\n {\n enableOnContentEditable: true,\n enableOnFormTags: true,\n enabled: isRecording && !disabled,\n keydown: false,\n keyup: true,\n preventDefault: true,\n },\n );\n\n // 处理按键,保证格式正确:修饰键在前,最多一个非修饰键在后\n const formatKeys = useCallback((keysSet: Set<string>) => {\n const modifiers: string[] = [];\n const normalKeys: string[] = [];\n\n for (const key of keysSet) {\n // 处理不同表示的修饰键\n const normalizedKey: any = key.toLowerCase();\n if (NORMATIVE_MODIFIER.includes(normalizedKey)) {\n // 统一修饰键表示\n if (\n (!isAppleDevice && normalizedKey === 'ctrl') ||\n (isAppleDevice && normalizedKey === 'meta')\n ) {\n if (!modifiers.includes('mod')) modifiers.push('mod');\n } else if (!modifiers.includes(normalizedKey)) {\n modifiers.push(normalizedKey);\n }\n } else {\n normalKeys.push(key);\n }\n }\n\n // 至少需要一个修饰键\n if (modifiers.length === 0 && normalKeys.length > 0) {\n return { isValid: false, keys: [] };\n }\n\n // 只允许一个非修饰键,如果有多个,只保留最后一个\n const finalKey = normalKeys.length > 0 ? [normalKeys.at(-1)] : [];\n const shortcuts = [modifiers, finalKey];\n\n return {\n // 组合必须包含至少一个按键\n isValid: shortcuts.every((k) => k.length > 0),\n keys: shortcuts.flat(),\n };\n }, []);\n\n // 获取格式化后的按键字符串\n const { isValid, keys } = formatKeys(recordedKeys);\n const keysString = keys.join('+');\n\n // 检查快捷键冲突\n const checkHotkeyConflict = useCallback(\n (newHotkey: string): boolean => {\n return hotkeyConflicts\n .filter((conflictKey) => conflictKey !== resetValue)\n .some((conflictKey) => {\n const newKeys = splitKeysByPlus(newHotkey);\n const conflictKeys = splitKeysByPlus(conflictKey);\n return isEqual(newKeys, conflictKeys);\n });\n },\n [hotkeyConflicts],\n );\n\n // 当按键组合完成时处理结果\n useEffect(() => {\n if (recordedKeys.size > 0 && !isRecording) {\n if (!isValid) {\n setHasInvalidCombination(true);\n setHasConflict(false);\n return;\n }\n\n setHasInvalidCombination(false);\n const newKeysString = keysString;\n\n // 检查冲突\n const conflict = checkHotkeyConflict(newKeysString);\n if (conflict) {\n setHasConflict(true);\n onConflict?.(newKeysString);\n } else {\n setHasConflict(false);\n setHotkeyValue?.(newKeysString);\n }\n }\n }, [\n recordedKeys,\n isRecording,\n isValid,\n keysString,\n checkHotkeyConflict,\n setHotkeyValue,\n onConflict,\n ]);\n\n // 处理输入框焦点\n const handleFocus = (e: FocusEvent<HTMLInputElement>) => {\n if (disabled) return;\n setIsFocused(true);\n setHasConflict(false);\n setHasInvalidCombination(false);\n start(); // 开始记录\n onFocus?.(e);\n };\n\n const handleBlur = (e: FocusEvent<HTMLInputElement>) => {\n setIsFocused(false);\n stop(); // 停止记录\n onBlur?.(e);\n };\n\n const handleClear = (e: MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n setHotkeyValue?.('');\n resetKeys();\n setHasConflict(false);\n setHasInvalidCombination(false);\n setIsFocused(false);\n stop();\n onClear?.(hotkeyValue);\n };\n\n // 重置功能\n const handleReset = (e: MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n setHotkeyValue?.(resetValue);\n resetKeys();\n setHasConflict(false);\n setHasInvalidCombination(false);\n setIsFocused(false);\n stop(); // 停止记录\n onReset?.(hotkeyValue, resetValue);\n };\n\n const handleClick = (e: MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n if (disabled || isFocused) return;\n inputRef.current?.focus();\n };\n\n const placeholderText = placeholder ?? t('hotkey.placeholder');\n const resetTitle = texts?.reset ?? t('hotkey.reset');\n const clearTitle = texts?.clear ?? t('hotkey.clear');\n const conflictText = texts?.conflicts ?? t('hotkey.conflict');\n const invalidText = texts?.invalidCombination ?? t('hotkey.invalidCombination');\n\n return (\n <Flexbox\n className={className}\n gap={8}\n style={{\n position: 'relative',\n ...style,\n }}\n >\n <Flexbox\n horizontal\n align={'center'}\n justify={'space-between'}\n className={cx(\n variants({\n disabled,\n error: hasConflict || hasInvalidCombination,\n focused: isFocused,\n shadow,\n variant: variant || (isDarkMode ? 'filled' : 'outlined'),\n }),\n )}\n onClick={handleClick}\n >\n <div style={{ pointerEvents: 'none' }}>\n {isRecording ? (\n <span className={styles.placeholder}>\n {keys.length > 0 ? <Hotkey keys={keysString} /> : placeholderText}\n </span>\n ) : hotkeyValue ? (\n <Hotkey keys={hotkeyValue} />\n ) : (\n <span className={styles.placeholder}>{placeholderText}</span>\n )}\n </div>\n\n {/* 隐藏的输入框,用于接收焦点 */}\n <input\n readOnly\n className={styles.hiddenInput}\n disabled={disabled}\n ref={inputRef as any}\n style={{ pointerEvents: 'none' }}\n onBlur={handleBlur}\n onFocus={handleFocus}\n />\n\n {!isFocused && hotkeyValue && !disabled && (allowReset || allowClear) && (\n <Flexbox horizontal gap={4}>\n {allowReset && hotkeyValue !== resetValue && (\n <ActionIcon\n icon={Undo2Icon}\n size={'small'}\n title={resetTitle}\n variant={'filled'}\n onClick={handleReset}\n />\n )}\n {allowClear && (\n <ActionIcon\n icon={XIcon}\n size={'small'}\n title={clearTitle}\n variant={'filled'}\n onClick={handleClear}\n />\n )}\n </Flexbox>\n )}\n </Flexbox>\n {hasConflict && <div className={styles.errorText}>{conflictText}</div>}\n {hasInvalidCombination && <div className={styles.errorText}>{invalidText}</div>}\n </Flexbox>\n );\n },\n);\n\nHotkeyInput.displayName = 'HotkeyInput';\n\nexport default HotkeyInput;\n"],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ import { InputProps } from "antd";
|
|
|
3
3
|
|
|
4
4
|
//#region src/HotkeyInput/type.d.ts
|
|
5
5
|
interface HotkeyInputProps {
|
|
6
|
+
allowClear?: boolean;
|
|
6
7
|
allowReset?: boolean;
|
|
7
8
|
className?: string;
|
|
8
9
|
defaultValue?: string;
|
|
@@ -11,6 +12,7 @@ interface HotkeyInputProps {
|
|
|
11
12
|
isApple?: boolean;
|
|
12
13
|
onBlur?: InputProps['onBlur'];
|
|
13
14
|
onChange?: (value: string) => void;
|
|
15
|
+
onClear?: (currentValue: string) => void;
|
|
14
16
|
onConflict?: (conflictKey: string) => void;
|
|
15
17
|
onFocus?: InputProps['onFocus'];
|
|
16
18
|
onReset?: (currentValue: string, resetValue: string) => void;
|
|
@@ -19,6 +21,7 @@ interface HotkeyInputProps {
|
|
|
19
21
|
shadow?: boolean;
|
|
20
22
|
style?: CSSProperties;
|
|
21
23
|
texts?: {
|
|
24
|
+
clear?: string;
|
|
22
25
|
conflicts?: string;
|
|
23
26
|
invalidCombination?: string;
|
|
24
27
|
reset?: string;
|
package/es/Icon/Icon.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { IconProps } from "./type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react25 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/Icon/Icon.d.ts
|
|
5
|
-
declare const Icon:
|
|
5
|
+
declare const Icon: react25.NamedExoticComponent<IconProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { Icon };
|
|
8
8
|
//# sourceMappingURL=Icon.d.mts.map
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { IconProps } from "../type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react23 from "react";
|
|
3
3
|
import { ReactNode } from "react";
|
|
4
4
|
|
|
5
5
|
//#region src/Icon/components/IconProvider.d.ts
|
|
6
6
|
type IconContentConfig = Omit<IconProps, 'icon' | 'ref'>;
|
|
7
|
-
declare const IconContext:
|
|
8
|
-
declare const IconProvider:
|
|
7
|
+
declare const IconContext: react23.Context<IconContentConfig>;
|
|
8
|
+
declare const IconProvider: react23.NamedExoticComponent<{
|
|
9
9
|
children: ReactNode;
|
|
10
10
|
config?: IconContentConfig;
|
|
11
11
|
}>;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { PreviewGroupProps } from "./type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react35 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/Image/PreviewGroup.d.ts
|
|
5
|
-
declare const PreviewGroup:
|
|
5
|
+
declare const PreviewGroup: react35.NamedExoticComponent<PreviewGroupProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { PreviewGroup };
|
|
8
8
|
//# sourceMappingURL=PreviewGroup.d.mts.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ImageSelectProps } from "./type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react118 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/ImageSelect/ImageSelect.d.ts
|
|
5
|
-
declare const ImageSelect:
|
|
5
|
+
declare const ImageSelect: react118.NamedExoticComponent<ImageSelectProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { ImageSelect };
|
|
8
8
|
//# sourceMappingURL=ImageSelect.d.mts.map
|
package/es/Input/Input.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { InputProps } from "./type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react153 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/Input/Input.d.ts
|
|
5
|
-
declare const Input:
|
|
5
|
+
declare const Input: react153.NamedExoticComponent<InputProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { Input };
|
|
8
8
|
//# sourceMappingURL=Input.d.mts.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { InputNumberProps } from "./type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react154 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/Input/InputNumber.d.ts
|
|
5
|
-
declare const InputNumber:
|
|
5
|
+
declare const InputNumber: react154.NamedExoticComponent<InputNumberProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { InputNumber };
|
|
8
8
|
//# sourceMappingURL=InputNumber.d.mts.map
|
package/es/Input/InputOPT.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { InputOPTProps } from "./type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react155 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/Input/InputOPT.d.ts
|
|
5
|
-
declare const InputOPT:
|
|
5
|
+
declare const InputOPT: react155.NamedExoticComponent<InputOPTProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { InputOPT };
|
|
8
8
|
//# sourceMappingURL=InputOPT.d.mts.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { InputPasswordProps } from "./type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react156 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/Input/InputPassword.d.ts
|
|
5
|
-
declare const InputPassword:
|
|
5
|
+
declare const InputPassword: react156.NamedExoticComponent<InputPasswordProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { InputPassword };
|
|
8
8
|
//# sourceMappingURL=InputPassword.d.mts.map
|
package/es/Input/TextArea.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { TextAreaProps } from "./type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react157 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/Input/TextArea.d.ts
|
|
5
|
-
declare const TextArea:
|
|
5
|
+
declare const TextArea: react157.NamedExoticComponent<TextAreaProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { TextArea };
|
|
8
8
|
//# sourceMappingURL=TextArea.d.mts.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { DivProps } from "../../types/index.mjs";
|
|
2
2
|
import "../../index.mjs";
|
|
3
|
-
import * as
|
|
3
|
+
import * as react28 from "react";
|
|
4
4
|
|
|
5
5
|
//#region src/Layout/components/LayoutFooter.d.ts
|
|
6
|
-
declare const LayoutFooter:
|
|
6
|
+
declare const LayoutFooter: react28.NamedExoticComponent<DivProps>;
|
|
7
7
|
//#endregion
|
|
8
8
|
export { LayoutFooter };
|
|
9
9
|
//# sourceMappingURL=LayoutFooter.d.mts.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { LayoutHeaderProps } from "../type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react29 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/Layout/components/LayoutHeader.d.ts
|
|
5
|
-
declare const LayoutHeader:
|
|
5
|
+
declare const LayoutHeader: react29.NamedExoticComponent<LayoutHeaderProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { LayoutHeader };
|
|
8
8
|
//# sourceMappingURL=LayoutHeader.d.mts.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { DivProps } from "../../types/index.mjs";
|
|
2
2
|
import "../../index.mjs";
|
|
3
|
-
import * as
|
|
3
|
+
import * as react30 from "react";
|
|
4
4
|
|
|
5
5
|
//#region src/Layout/components/LayoutMain.d.ts
|
|
6
|
-
declare const LayoutMain:
|
|
6
|
+
declare const LayoutMain: react30.NamedExoticComponent<DivProps>;
|
|
7
7
|
//#endregion
|
|
8
8
|
export { LayoutMain };
|
|
9
9
|
//# sourceMappingURL=LayoutMain.d.mts.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { LayoutSidebarProps } from "../type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react31 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/Layout/components/LayoutSidebar.d.ts
|
|
5
|
-
declare const LayoutSidebar:
|
|
5
|
+
declare const LayoutSidebar: react31.NamedExoticComponent<LayoutSidebarProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { LayoutSidebar };
|
|
8
8
|
//# sourceMappingURL=LayoutSidebar.d.mts.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { LayoutSidebarInnerProps } from "../type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react32 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/Layout/components/LayoutSidebarInner.d.ts
|
|
5
|
-
declare const LayoutSidebarInner:
|
|
5
|
+
declare const LayoutSidebarInner: react32.NamedExoticComponent<LayoutSidebarInnerProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { LayoutSidebarInner };
|
|
8
8
|
//# sourceMappingURL=LayoutSidebarInner.d.mts.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { LayoutTocProps } from "../type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react33 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/Layout/components/LayoutToc.d.ts
|
|
5
|
-
declare const LayoutToc:
|
|
5
|
+
declare const LayoutToc: react33.NamedExoticComponent<LayoutTocProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { LayoutToc };
|
|
8
8
|
//# sourceMappingURL=LayoutToc.d.mts.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ListItemProps } from "../type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react27 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/List/ListItem/index.d.ts
|
|
5
|
-
declare const ListItem:
|
|
5
|
+
declare const ListItem: react27.NamedExoticComponent<ListItemProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { ListItem };
|
|
8
8
|
//# sourceMappingURL=index.d.mts.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { MarkdownProps } from "./type.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react37 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/Markdown/Markdown.d.ts
|
|
5
|
-
declare const Markdown:
|
|
5
|
+
declare const Markdown: react37.NamedExoticComponent<MarkdownProps>;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { Markdown };
|
|
8
8
|
//# sourceMappingURL=Markdown.d.mts.map
|
package/es/Markdown/Markdown.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import { cx } from "antd-style";
|
|
|
13
13
|
|
|
14
14
|
//#region src/Markdown/Markdown.tsx
|
|
15
15
|
const Markdown = memo((props) => {
|
|
16
|
-
const { ref, children = "", className, style, fullFeaturedCodeBlock, onDoubleClick, animated, enableLatex = true, enableMermaid = true, enableImageGallery, enableCustomFootnotes, enableGithubAlert, enableStream = true, componentProps, rehypePluginsAhead, allowHtml, borderRadius, fontSize = props.variant === "chat" ? 14 : void 0, headerMultiple = props.variant === "chat" ? .25 : void 0, marginMultiple = props.variant === "chat" ? 1 : void 0, variant = "default", reactMarkdownProps, lineHeight = props.variant === "chat" ? 1.6 : void 0, rehypePlugins, remarkPlugins, remarkPluginsAhead, components = {}, customRender, showFootnotes = true, citations, ...rest } = props;
|
|
16
|
+
const { ref, children = "", className, style, fullFeaturedCodeBlock, onDoubleClick, animated, enableLatex = true, enableMermaid = true, enableImageGallery, enableCustomFootnotes, enableGithubAlert, enableStream = true, componentProps, rehypePluginsAhead, allowHtml, borderRadius, fontSize = props.variant === "chat" ? 14 : void 0, headerMultiple = props.variant === "chat" ? .25 : void 0, marginMultiple = props.variant === "chat" ? 1 : void 0, variant = "default", reactMarkdownProps, lineHeight = props.variant === "chat" ? 1.6 : void 0, rehypePlugins, remarkPlugins, remarkPluginsAhead, components = {}, customRender, showFootnotes = true, streamSmoothingPreset, citations, ...rest } = props;
|
|
17
17
|
const delayedAnimated = useDelayedAnimated(animated);
|
|
18
18
|
const Render = useCallback(({ enableStream: enableStream$1, children: children$1, reactMarkdownProps: reactMarkdownProps$1 }) => {
|
|
19
19
|
const defaultDOM = /* @__PURE__ */ jsx(enableStream$1 ? StreamdownRender_default : MarkdownRender_default, {
|
|
@@ -55,6 +55,7 @@ const Markdown = memo((props) => {
|
|
|
55
55
|
remarkPlugins,
|
|
56
56
|
remarkPluginsAhead,
|
|
57
57
|
showFootnotes,
|
|
58
|
+
streamSmoothingPreset,
|
|
58
59
|
variant,
|
|
59
60
|
children: /* @__PURE__ */ jsx(Render, {
|
|
60
61
|
enableStream: enableStream && delayedAnimated,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Markdown.mjs","names":["enableStream","StreamdownRender","MarkdownRender","reactMarkdownProps","children","PreviewGroup","Typography"],"sources":["../../src/Markdown/Markdown.tsx"],"sourcesContent":["'use client';\n\nimport { cx } from 'antd-style';\nimport { memo, useCallback } from 'react';\n\nimport { PreviewGroup } from '@/Image';\n\nimport { MarkdownProvider } from './components/MarkdownProvider';\nimport { useDelayedAnimated } from './components/useDelayedAnimated';\nimport { variants } from './style';\nimport { MarkdownRender, StreamdownRender } from './SyntaxMarkdown';\nimport { type MarkdownProps } from './type';\nimport Typography from './Typography';\n\nconst Markdown = memo<MarkdownProps>((props) => {\n const {\n ref,\n children = '',\n className,\n style,\n fullFeaturedCodeBlock,\n onDoubleClick,\n animated,\n enableLatex = true,\n enableMermaid = true,\n enableImageGallery,\n enableCustomFootnotes,\n enableGithubAlert,\n enableStream = true,\n componentProps,\n rehypePluginsAhead,\n allowHtml,\n borderRadius,\n fontSize = props.variant === 'chat' ? 14 : undefined,\n headerMultiple = props.variant === 'chat' ? 0.25 : undefined,\n marginMultiple = props.variant === 'chat' ? 1 : undefined,\n variant = 'default',\n reactMarkdownProps,\n lineHeight = props.variant === 'chat' ? 1.6 : undefined,\n rehypePlugins,\n remarkPlugins,\n remarkPluginsAhead,\n components = {},\n customRender,\n showFootnotes = true,\n citations,\n ...rest\n } = props;\n\n const delayedAnimated = useDelayedAnimated(animated);\n\n const Render = useCallback(\n ({\n enableStream,\n children,\n reactMarkdownProps,\n }: Pick<MarkdownProps, 'children' | 'enableStream' | 'reactMarkdownProps'>) => {\n const DefaultRender = enableStream ? StreamdownRender : MarkdownRender;\n const defaultDOM = <DefaultRender {...reactMarkdownProps}>{children}</DefaultRender>;\n return customRender ? customRender(defaultDOM, { text: children }) : defaultDOM;\n },\n [customRender],\n );\n\n return (\n <PreviewGroup enable={enableImageGallery}>\n <Typography\n borderRadius={borderRadius}\n className={cx(variants({ enableLatex, variant }), className)}\n data-code-type=\"markdown\"\n fontSize={fontSize}\n headerMultiple={headerMultiple}\n lineHeight={lineHeight}\n marginMultiple={marginMultiple}\n ref={ref}\n style={style}\n onDoubleClick={onDoubleClick}\n {...rest}\n >\n <MarkdownProvider\n allowHtml={allowHtml}\n animated={delayedAnimated}\n citations={citations}\n componentProps={componentProps}\n components={components}\n enableCustomFootnotes={enableCustomFootnotes}\n enableGithubAlert={enableGithubAlert}\n enableLatex={enableLatex}\n enableMermaid={enableMermaid}\n fullFeaturedCodeBlock={fullFeaturedCodeBlock}\n rehypePlugins={rehypePlugins}\n rehypePluginsAhead={rehypePluginsAhead}\n remarkPlugins={remarkPlugins}\n remarkPluginsAhead={remarkPluginsAhead}\n showFootnotes={showFootnotes}\n variant={variant}\n >\n <Render\n enableStream={enableStream && delayedAnimated}\n reactMarkdownProps={reactMarkdownProps}\n >\n {children}\n </Render>\n </MarkdownProvider>\n </Typography>\n </PreviewGroup>\n );\n});\n\nMarkdown.displayName = 'Markdown';\n\nexport default Markdown;\n"],"mappings":";;;;;;;;;;;;;;AAcA,MAAM,WAAW,MAAqB,UAAU;CAC9C,MAAM,EACJ,KACA,WAAW,IACX,WACA,OACA,uBACA,eACA,UACA,cAAc,MACd,gBAAgB,MAChB,oBACA,uBACA,mBACA,eAAe,MACf,gBACA,oBACA,WACA,cACA,WAAW,MAAM,YAAY,SAAS,KAAK,QAC3C,iBAAiB,MAAM,YAAY,SAAS,MAAO,QACnD,iBAAiB,MAAM,YAAY,SAAS,IAAI,QAChD,UAAU,WACV,oBACA,aAAa,MAAM,YAAY,SAAS,MAAM,QAC9C,eACA,eACA,oBACA,aAAa,EAAE,EACf,cACA,gBAAgB,MAChB,WACA,GAAG,SACD;CAEJ,MAAM,kBAAkB,mBAAmB,SAAS;CAEpD,MAAM,SAAS,aACZ,EACC,8BACA,sBACA,+CAC6E;EAE7E,MAAM,aAAa,oBADGA,iBAAeC,2BAAmBC;GACtB,GAAIC;aAAqBC;IAAyB;AACpF,SAAO,eAAe,aAAa,YAAY,EAAE,MAAMA,YAAU,CAAC,GAAG;IAEvE,CAAC,aAAa,CACf;AAED,QACE,oBAACC;EAAa,QAAQ;YACpB,oBAACC;GACe;GACd,WAAW,GAAG,SAAS;IAAE;IAAa;IAAS,CAAC,EAAE,UAAU;GAC5D,kBAAe;GACL;GACM;GACJ;GACI;GACX;GACE;GACQ;GACf,GAAI;aAEJ,oBAAC;IACY;IACX,UAAU;IACC;IACK;IACJ;IACW;IACJ;IACN;IACE;IACQ;IACR;IACK;IACL;IACK;IACL;
|
|
1
|
+
{"version":3,"file":"Markdown.mjs","names":["enableStream","StreamdownRender","MarkdownRender","reactMarkdownProps","children","PreviewGroup","Typography"],"sources":["../../src/Markdown/Markdown.tsx"],"sourcesContent":["'use client';\n\nimport { cx } from 'antd-style';\nimport { memo, useCallback } from 'react';\n\nimport { PreviewGroup } from '@/Image';\n\nimport { MarkdownProvider } from './components/MarkdownProvider';\nimport { useDelayedAnimated } from './components/useDelayedAnimated';\nimport { variants } from './style';\nimport { MarkdownRender, StreamdownRender } from './SyntaxMarkdown';\nimport { type MarkdownProps } from './type';\nimport Typography from './Typography';\n\nconst Markdown = memo<MarkdownProps>((props) => {\n const {\n ref,\n children = '',\n className,\n style,\n fullFeaturedCodeBlock,\n onDoubleClick,\n animated,\n enableLatex = true,\n enableMermaid = true,\n enableImageGallery,\n enableCustomFootnotes,\n enableGithubAlert,\n enableStream = true,\n componentProps,\n rehypePluginsAhead,\n allowHtml,\n borderRadius,\n fontSize = props.variant === 'chat' ? 14 : undefined,\n headerMultiple = props.variant === 'chat' ? 0.25 : undefined,\n marginMultiple = props.variant === 'chat' ? 1 : undefined,\n variant = 'default',\n reactMarkdownProps,\n lineHeight = props.variant === 'chat' ? 1.6 : undefined,\n rehypePlugins,\n remarkPlugins,\n remarkPluginsAhead,\n components = {},\n customRender,\n showFootnotes = true,\n streamSmoothingPreset,\n citations,\n ...rest\n } = props;\n\n const delayedAnimated = useDelayedAnimated(animated);\n\n const Render = useCallback(\n ({\n enableStream,\n children,\n reactMarkdownProps,\n }: Pick<MarkdownProps, 'children' | 'enableStream' | 'reactMarkdownProps'>) => {\n const DefaultRender = enableStream ? StreamdownRender : MarkdownRender;\n const defaultDOM = <DefaultRender {...reactMarkdownProps}>{children}</DefaultRender>;\n return customRender ? customRender(defaultDOM, { text: children }) : defaultDOM;\n },\n [customRender],\n );\n\n return (\n <PreviewGroup enable={enableImageGallery}>\n <Typography\n borderRadius={borderRadius}\n className={cx(variants({ enableLatex, variant }), className)}\n data-code-type=\"markdown\"\n fontSize={fontSize}\n headerMultiple={headerMultiple}\n lineHeight={lineHeight}\n marginMultiple={marginMultiple}\n ref={ref}\n style={style}\n onDoubleClick={onDoubleClick}\n {...rest}\n >\n <MarkdownProvider\n allowHtml={allowHtml}\n animated={delayedAnimated}\n citations={citations}\n componentProps={componentProps}\n components={components}\n enableCustomFootnotes={enableCustomFootnotes}\n enableGithubAlert={enableGithubAlert}\n enableLatex={enableLatex}\n enableMermaid={enableMermaid}\n fullFeaturedCodeBlock={fullFeaturedCodeBlock}\n rehypePlugins={rehypePlugins}\n rehypePluginsAhead={rehypePluginsAhead}\n remarkPlugins={remarkPlugins}\n remarkPluginsAhead={remarkPluginsAhead}\n showFootnotes={showFootnotes}\n streamSmoothingPreset={streamSmoothingPreset}\n variant={variant}\n >\n <Render\n enableStream={enableStream && delayedAnimated}\n reactMarkdownProps={reactMarkdownProps}\n >\n {children}\n </Render>\n </MarkdownProvider>\n </Typography>\n </PreviewGroup>\n );\n});\n\nMarkdown.displayName = 'Markdown';\n\nexport default Markdown;\n"],"mappings":";;;;;;;;;;;;;;AAcA,MAAM,WAAW,MAAqB,UAAU;CAC9C,MAAM,EACJ,KACA,WAAW,IACX,WACA,OACA,uBACA,eACA,UACA,cAAc,MACd,gBAAgB,MAChB,oBACA,uBACA,mBACA,eAAe,MACf,gBACA,oBACA,WACA,cACA,WAAW,MAAM,YAAY,SAAS,KAAK,QAC3C,iBAAiB,MAAM,YAAY,SAAS,MAAO,QACnD,iBAAiB,MAAM,YAAY,SAAS,IAAI,QAChD,UAAU,WACV,oBACA,aAAa,MAAM,YAAY,SAAS,MAAM,QAC9C,eACA,eACA,oBACA,aAAa,EAAE,EACf,cACA,gBAAgB,MAChB,uBACA,WACA,GAAG,SACD;CAEJ,MAAM,kBAAkB,mBAAmB,SAAS;CAEpD,MAAM,SAAS,aACZ,EACC,8BACA,sBACA,+CAC6E;EAE7E,MAAM,aAAa,oBADGA,iBAAeC,2BAAmBC;GACtB,GAAIC;aAAqBC;IAAyB;AACpF,SAAO,eAAe,aAAa,YAAY,EAAE,MAAMA,YAAU,CAAC,GAAG;IAEvE,CAAC,aAAa,CACf;AAED,QACE,oBAACC;EAAa,QAAQ;YACpB,oBAACC;GACe;GACd,WAAW,GAAG,SAAS;IAAE;IAAa;IAAS,CAAC,EAAE,UAAU;GAC5D,kBAAe;GACL;GACM;GACJ;GACI;GACX;GACE;GACQ;GACf,GAAI;aAEJ,oBAAC;IACY;IACX,UAAU;IACC;IACK;IACJ;IACW;IACJ;IACN;IACE;IACQ;IACR;IACK;IACL;IACK;IACL;IACQ;IACd;cAET,oBAAC;KACC,cAAc,gBAAgB;KACV;KAEnB;MACM;KACQ;IACR;GACA;EAEjB;AAEF,SAAS,cAAc;AAEvB,uBAAe"}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { useMarkdownContext } from "../components/MarkdownProvider.mjs";
|
|
3
4
|
import { useMarkdownComponents } from "../../hooks/useMarkdown/useMarkdownComponents.mjs";
|
|
4
5
|
import { useMarkdownContent } from "../../hooks/useMarkdown/useMarkdownContent.mjs";
|
|
5
6
|
import { useMarkdownRehypePlugins } from "../../hooks/useMarkdown/useMarkdownRehypePlugins.mjs";
|
|
6
7
|
import { useMarkdownRemarkPlugins } from "../../hooks/useMarkdown/useMarkdownRemarkPlugins.mjs";
|
|
7
8
|
import { rehypeStreamAnimated } from "../plugins/rehypeStreamAnimated.mjs";
|
|
8
9
|
import { styles } from "./style.mjs";
|
|
10
|
+
import { useSmoothStreamContent } from "./useSmoothStreamContent.mjs";
|
|
9
11
|
import { useStreamQueue } from "./useStreamQueue.mjs";
|
|
10
12
|
import { createElement, memo, useEffect, useId, useMemo, useRef } from "react";
|
|
11
13
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -14,7 +16,7 @@ import { marked } from "marked";
|
|
|
14
16
|
import remend from "remend";
|
|
15
17
|
|
|
16
18
|
//#region src/Markdown/SyntaxMarkdown/StreamdownRender.tsx
|
|
17
|
-
const
|
|
19
|
+
const STREAM_FADE_DURATION = 280;
|
|
18
20
|
function countChars(text) {
|
|
19
21
|
return [...text].length;
|
|
20
22
|
}
|
|
@@ -61,14 +63,16 @@ const StreamdownBlock = memo(({ children, ...rest }) => {
|
|
|
61
63
|
}, (prevProps, nextProps) => prevProps.children === nextProps.children && prevProps.components === nextProps.components && isSamePlugins(prevProps.rehypePlugins, nextProps.rehypePlugins) && isSamePlugins(prevProps.remarkPlugins, nextProps.remarkPlugins));
|
|
62
64
|
StreamdownBlock.displayName = "StreamdownBlock";
|
|
63
65
|
const StreamdownRender = memo(({ children, ...rest }) => {
|
|
66
|
+
const { streamSmoothingPreset = "balanced" } = useMarkdownContext();
|
|
64
67
|
const escapedContent = useMarkdownContent(children || "");
|
|
65
68
|
const components = useMarkdownComponents();
|
|
66
69
|
const baseRehypePlugins = useStablePlugins(useMarkdownRehypePlugins());
|
|
67
70
|
const remarkPlugins = useStablePlugins(useMarkdownRemarkPlugins());
|
|
68
71
|
const generatedId = useId();
|
|
72
|
+
const smoothedContent = useSmoothStreamContent(typeof escapedContent === "string" ? escapedContent : "", { preset: streamSmoothingPreset });
|
|
69
73
|
const processedContent = useMemo(() => {
|
|
70
|
-
return remend(
|
|
71
|
-
}, [
|
|
74
|
+
return remend(smoothedContent);
|
|
75
|
+
}, [smoothedContent]);
|
|
72
76
|
const blocks = useMemo(() => {
|
|
73
77
|
const tokens = marked.lexer(processedContent);
|
|
74
78
|
let offset = 0;
|
|
@@ -82,41 +86,63 @@ const StreamdownRender = memo(({ children, ...rest }) => {
|
|
|
82
86
|
});
|
|
83
87
|
}, [processedContent]);
|
|
84
88
|
const { getBlockState, charDelay } = useStreamQueue(blocks);
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
89
|
+
const prevBlockCharCountRef = useRef(/* @__PURE__ */ new Map());
|
|
90
|
+
const blockTimelineRef = useRef(/* @__PURE__ */ new Map());
|
|
91
|
+
const lastRenderTsRef = useRef(null);
|
|
92
|
+
const renderTs = typeof performance === "undefined" ? Date.now() : performance.now();
|
|
93
|
+
const frameDt = lastRenderTsRef.current === null ? 0 : Math.max(0, Math.min(renderTs - lastRenderTsRef.current, 120));
|
|
94
|
+
const timelineForRender = useMemo(() => {
|
|
95
|
+
const next = /* @__PURE__ */ new Map();
|
|
96
|
+
const prevTimeline = blockTimelineRef.current;
|
|
97
|
+
const prevCharCounts = prevBlockCharCountRef.current;
|
|
98
|
+
for (const block of blocks) {
|
|
99
|
+
const blockCharCount = countChars(block.content);
|
|
100
|
+
const prevCharCount = prevCharCounts.get(block.startOffset) ?? 0;
|
|
101
|
+
const prevElapsed = prevTimeline.get(block.startOffset);
|
|
102
|
+
const latestCharStart = Math.max(0, (blockCharCount - 1) * charDelay);
|
|
103
|
+
if (prevElapsed === void 0 || blockCharCount < prevCharCount) {
|
|
104
|
+
next.set(block.startOffset, latestCharStart);
|
|
105
|
+
continue;
|
|
99
106
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
prevStreamOffsetRef.current = -1;
|
|
107
|
+
const elapsedByTime = prevElapsed + frameDt;
|
|
108
|
+
const minElapsed = Math.max(0, latestCharStart - charDelay * 2);
|
|
109
|
+
next.set(block.startOffset, Math.max(elapsedByTime, minElapsed));
|
|
104
110
|
}
|
|
105
|
-
|
|
111
|
+
return next;
|
|
112
|
+
}, [
|
|
113
|
+
blocks,
|
|
114
|
+
charDelay,
|
|
115
|
+
frameDt
|
|
116
|
+
]);
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
const nextCharCount = /* @__PURE__ */ new Map();
|
|
119
|
+
for (const block of blocks) nextCharCount.set(block.startOffset, countChars(block.content));
|
|
120
|
+
prevBlockCharCountRef.current = nextCharCount;
|
|
121
|
+
blockTimelineRef.current = timelineForRender;
|
|
122
|
+
lastRenderTsRef.current = typeof performance === "undefined" ? Date.now() : performance.now();
|
|
123
|
+
}, [blocks, timelineForRender]);
|
|
106
124
|
return /* @__PURE__ */ jsx("div", {
|
|
107
125
|
className: styles.animated,
|
|
108
126
|
children: blocks.map((block, index) => {
|
|
109
127
|
const state = getBlockState(index);
|
|
110
128
|
if (state === "queued") return null;
|
|
129
|
+
const timelineElapsedMs = timelineForRender.get(block.startOffset) ?? 0;
|
|
111
130
|
let plugins;
|
|
112
|
-
if (state === "streaming") {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
131
|
+
if (state === "streaming") plugins = [...baseRehypePlugins, [rehypeStreamAnimated, {
|
|
132
|
+
charDelay,
|
|
133
|
+
fadeDuration: STREAM_FADE_DURATION,
|
|
134
|
+
timelineElapsedMs
|
|
135
|
+
}]];
|
|
136
|
+
else if (state === "animating") plugins = [...baseRehypePlugins, [rehypeStreamAnimated, {
|
|
137
|
+
charDelay,
|
|
138
|
+
fadeDuration: STREAM_FADE_DURATION,
|
|
139
|
+
timelineElapsedMs
|
|
140
|
+
}]];
|
|
141
|
+
else plugins = [...baseRehypePlugins, [rehypeStreamAnimated, {
|
|
142
|
+
charDelay,
|
|
143
|
+
fadeDuration: STREAM_FADE_DURATION,
|
|
144
|
+
timelineElapsedMs
|
|
145
|
+
}]];
|
|
120
146
|
return /* @__PURE__ */ createElement(StreamdownBlock, {
|
|
121
147
|
...rest,
|
|
122
148
|
components,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StreamdownRender.mjs","names":["blocks: BlockInfo[]","staggerPlugins: Pluggable[]","revealedPlugins: Pluggable[]","plugins: Pluggable[]"],"sources":["../../../src/Markdown/SyntaxMarkdown/StreamdownRender.tsx"],"sourcesContent":["'use client';\n\nimport { marked } from 'marked';\nimport { memo, useEffect, useId, useMemo, useRef } from 'react';\nimport Markdown, { type Options } from 'react-markdown';\nimport remend from 'remend';\nimport type { Pluggable, PluggableList } from 'unified';\n\nimport {\n useMarkdownComponents,\n useMarkdownContent,\n useMarkdownRehypePlugins,\n useMarkdownRemarkPlugins,\n} from '@/hooks/useMarkdown';\nimport { rehypeStreamAnimated } from '@/Markdown/plugins/rehypeStreamAnimated';\n\nimport { styles } from './style';\nimport { type BlockInfo, useStreamQueue } from './useStreamQueue';\n\nconst STREAM_CHAR_DELAY = 15;\n\nfunction countChars(text: string): number {\n return [...text].length;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null;\n\nconst isDeepEqualValue = (a: unknown, b: unknown): boolean => {\n if (a === b) return true;\n\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b)) return false;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!isDeepEqualValue(a[i], b[i])) return false;\n }\n return true;\n }\n\n if (!isRecord(a) || !isRecord(b)) return false;\n\n const keysA = Object.keys(a);\n const keysB = Object.keys(b);\n if (keysA.length !== keysB.length) return false;\n\n for (const key of keysA) {\n if (!isDeepEqualValue(a[key], b[key])) return false;\n }\n\n return true;\n};\n\nconst isSamePlugin = (prevPlugin: Pluggable, nextPlugin: Pluggable): boolean => {\n const prevTuple = Array.isArray(prevPlugin) ? prevPlugin : [prevPlugin];\n const nextTuple = Array.isArray(nextPlugin) ? nextPlugin : [nextPlugin];\n\n if (prevTuple.length !== nextTuple.length) return false;\n if (prevTuple[0] !== nextTuple[0]) return false;\n\n return isDeepEqualValue(prevTuple.slice(1), nextTuple.slice(1));\n};\n\nconst isSamePlugins = (\n prevPlugins?: PluggableList | null,\n nextPlugins?: PluggableList | null,\n): boolean => {\n if (prevPlugins === nextPlugins) return true;\n if (!prevPlugins || !nextPlugins) return !prevPlugins && !nextPlugins;\n if (prevPlugins.length !== nextPlugins.length) return false;\n\n for (let i = 0; i < prevPlugins.length; i++) {\n if (!isSamePlugin(prevPlugins[i], nextPlugins[i])) return false;\n }\n\n return true;\n};\n\nconst useStablePlugins = (plugins: PluggableList): PluggableList => {\n const stableRef = useRef<PluggableList>(plugins);\n\n if (!isSamePlugins(stableRef.current, plugins)) {\n stableRef.current = plugins;\n }\n\n return stableRef.current;\n};\n\nconst StreamdownBlock = memo<Options>(\n ({ children, ...rest }) => {\n return <Markdown {...rest}>{children}</Markdown>;\n },\n (prevProps, nextProps) =>\n prevProps.children === nextProps.children &&\n prevProps.components === nextProps.components &&\n isSamePlugins(prevProps.rehypePlugins, nextProps.rehypePlugins) &&\n isSamePlugins(prevProps.remarkPlugins, nextProps.remarkPlugins),\n);\n\nStreamdownBlock.displayName = 'StreamdownBlock';\n\nexport const StreamdownRender = memo<Options>(({ children, ...rest }) => {\n const escapedContent = useMarkdownContent(children || '');\n const components = useMarkdownComponents();\n const baseRehypePlugins = useStablePlugins(useMarkdownRehypePlugins());\n const remarkPlugins = useStablePlugins(useMarkdownRemarkPlugins());\n const generatedId = useId();\n\n const processedContent = useMemo(() => {\n const content = typeof escapedContent === 'string' ? escapedContent : '';\n return remend(content);\n }, [escapedContent]);\n\n const blocks: BlockInfo[] = useMemo(() => {\n const tokens = marked.lexer(processedContent);\n let offset = 0;\n return tokens.map((token) => {\n const block = { content: token.raw, startOffset: offset };\n offset += token.raw.length;\n return block;\n });\n }, [processedContent]);\n\n const { getBlockState, charDelay } = useStreamQueue(blocks);\n\n const staggerPlugins: Pluggable[] = useMemo(\n () => [...baseRehypePlugins, [rehypeStreamAnimated, { baseCharCount: 0, charDelay }]],\n [baseRehypePlugins, charDelay],\n );\n\n const revealedPlugins: Pluggable[] = useMemo(\n () => [...baseRehypePlugins, [rehypeStreamAnimated, { revealed: true }]],\n [baseRehypePlugins],\n );\n\n // prevCharCount tracks the PREVIOUS render's streaming char count.\n // Updated in useEffect (after render) to avoid stale reads during\n // synchronous re-renders (e.g. useLayoutEffect auto-reveal).\n const prevCharCountRef = useRef(0);\n const prevStreamOffsetRef = useRef(-1);\n\n useEffect(() => {\n const tailIdx = blocks.length - 1;\n if (tailIdx >= 0) {\n const tail = blocks[tailIdx];\n if (tail.startOffset !== prevStreamOffsetRef.current) {\n prevStreamOffsetRef.current = tail.startOffset;\n prevCharCountRef.current = 0;\n }\n prevCharCountRef.current = countChars(tail.content);\n } else {\n prevCharCountRef.current = 0;\n prevStreamOffsetRef.current = -1;\n }\n }, [blocks]);\n\n return (\n <div className={styles.animated}>\n {blocks.map((block, index) => {\n const state = getBlockState(index);\n if (state === 'queued') return null;\n\n let plugins: Pluggable[];\n if (state === 'streaming') {\n const baseCharCount =\n block.startOffset === prevStreamOffsetRef.current ? prevCharCountRef.current : 0;\n plugins = [\n ...baseRehypePlugins,\n [rehypeStreamAnimated, { baseCharCount, charDelay: STREAM_CHAR_DELAY }],\n ];\n } else if (state === 'animating') {\n plugins = staggerPlugins;\n } else {\n plugins = revealedPlugins;\n }\n\n return (\n <StreamdownBlock\n {...rest}\n components={components}\n key={`${generatedId}-${block.startOffset}`}\n rehypePlugins={plugins}\n remarkPlugins={remarkPlugins}\n >\n {block.content}\n </StreamdownBlock>\n );\n })}\n </div>\n );\n});\n\nStreamdownRender.displayName = 'StreamdownRender';\n\nexport default StreamdownRender;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"StreamdownRender.mjs","names":["blocks: BlockInfo[]","plugins: Pluggable[]"],"sources":["../../../src/Markdown/SyntaxMarkdown/StreamdownRender.tsx"],"sourcesContent":["'use client';\n\nimport { marked } from 'marked';\nimport { memo, useEffect, useId, useMemo, useRef } from 'react';\nimport Markdown, { type Options } from 'react-markdown';\nimport remend from 'remend';\nimport type { Pluggable, PluggableList } from 'unified';\n\nimport {\n useMarkdownComponents,\n useMarkdownContent,\n useMarkdownRehypePlugins,\n useMarkdownRemarkPlugins,\n} from '@/hooks/useMarkdown';\nimport { useMarkdownContext } from '@/Markdown/components/MarkdownProvider';\nimport { rehypeStreamAnimated } from '@/Markdown/plugins/rehypeStreamAnimated';\n\nimport { styles } from './style';\nimport { useSmoothStreamContent } from './useSmoothStreamContent';\nimport { type BlockInfo, useStreamQueue } from './useStreamQueue';\n\nconst STREAM_FADE_DURATION = 280;\n\nfunction countChars(text: string): number {\n return [...text].length;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null;\n\nconst isDeepEqualValue = (a: unknown, b: unknown): boolean => {\n if (a === b) return true;\n\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b)) return false;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!isDeepEqualValue(a[i], b[i])) return false;\n }\n return true;\n }\n\n if (!isRecord(a) || !isRecord(b)) return false;\n\n const keysA = Object.keys(a);\n const keysB = Object.keys(b);\n if (keysA.length !== keysB.length) return false;\n\n for (const key of keysA) {\n if (!isDeepEqualValue(a[key], b[key])) return false;\n }\n\n return true;\n};\n\nconst isSamePlugin = (prevPlugin: Pluggable, nextPlugin: Pluggable): boolean => {\n const prevTuple = Array.isArray(prevPlugin) ? prevPlugin : [prevPlugin];\n const nextTuple = Array.isArray(nextPlugin) ? nextPlugin : [nextPlugin];\n\n if (prevTuple.length !== nextTuple.length) return false;\n if (prevTuple[0] !== nextTuple[0]) return false;\n\n return isDeepEqualValue(prevTuple.slice(1), nextTuple.slice(1));\n};\n\nconst isSamePlugins = (\n prevPlugins?: PluggableList | null,\n nextPlugins?: PluggableList | null,\n): boolean => {\n if (prevPlugins === nextPlugins) return true;\n if (!prevPlugins || !nextPlugins) return !prevPlugins && !nextPlugins;\n if (prevPlugins.length !== nextPlugins.length) return false;\n\n for (let i = 0; i < prevPlugins.length; i++) {\n if (!isSamePlugin(prevPlugins[i], nextPlugins[i])) return false;\n }\n\n return true;\n};\n\nconst useStablePlugins = (plugins: PluggableList): PluggableList => {\n const stableRef = useRef<PluggableList>(plugins);\n\n if (!isSamePlugins(stableRef.current, plugins)) {\n stableRef.current = plugins;\n }\n\n return stableRef.current;\n};\n\nconst StreamdownBlock = memo<Options>(\n ({ children, ...rest }) => {\n return <Markdown {...rest}>{children}</Markdown>;\n },\n (prevProps, nextProps) =>\n prevProps.children === nextProps.children &&\n prevProps.components === nextProps.components &&\n isSamePlugins(prevProps.rehypePlugins, nextProps.rehypePlugins) &&\n isSamePlugins(prevProps.remarkPlugins, nextProps.remarkPlugins),\n);\n\nStreamdownBlock.displayName = 'StreamdownBlock';\n\nexport const StreamdownRender = memo<Options>(({ children, ...rest }) => {\n const { streamSmoothingPreset = 'balanced' } = useMarkdownContext();\n const escapedContent = useMarkdownContent(children || '');\n const components = useMarkdownComponents();\n const baseRehypePlugins = useStablePlugins(useMarkdownRehypePlugins());\n const remarkPlugins = useStablePlugins(useMarkdownRemarkPlugins());\n const generatedId = useId();\n const smoothedContent = useSmoothStreamContent(\n typeof escapedContent === 'string' ? escapedContent : '',\n { preset: streamSmoothingPreset },\n );\n\n const processedContent = useMemo(() => {\n return remend(smoothedContent);\n }, [smoothedContent]);\n\n const blocks: BlockInfo[] = useMemo(() => {\n const tokens = marked.lexer(processedContent);\n let offset = 0;\n return tokens.map((token) => {\n const block = { content: token.raw, startOffset: offset };\n offset += token.raw.length;\n return block;\n });\n }, [processedContent]);\n\n const { getBlockState, charDelay } = useStreamQueue(blocks);\n const prevBlockCharCountRef = useRef<Map<number, number>>(new Map());\n const blockTimelineRef = useRef<Map<number, number>>(new Map());\n const lastRenderTsRef = useRef<number | null>(null);\n\n const renderTs = typeof performance === 'undefined' ? Date.now() : performance.now();\n const frameDt =\n lastRenderTsRef.current === null\n ? 0\n : Math.max(0, Math.min(renderTs - lastRenderTsRef.current, 120));\n\n const timelineForRender = useMemo(() => {\n const next = new Map<number, number>();\n const prevTimeline = blockTimelineRef.current;\n const prevCharCounts = prevBlockCharCountRef.current;\n\n for (const block of blocks) {\n const blockCharCount = countChars(block.content);\n const prevCharCount = prevCharCounts.get(block.startOffset) ?? 0;\n const prevElapsed = prevTimeline.get(block.startOffset);\n const latestCharStart = Math.max(0, (blockCharCount - 1) * charDelay);\n\n if (prevElapsed === undefined || blockCharCount < prevCharCount) {\n next.set(block.startOffset, latestCharStart);\n continue;\n }\n\n const elapsedByTime = prevElapsed + frameDt;\n // Avoid huge hidden backlog when stream updates in bursts.\n const minElapsed = Math.max(0, latestCharStart - charDelay * 2);\n next.set(block.startOffset, Math.max(elapsedByTime, minElapsed));\n }\n\n return next;\n }, [blocks, charDelay, frameDt]);\n\n useEffect(() => {\n const nextCharCount = new Map<number, number>();\n for (const block of blocks) {\n nextCharCount.set(block.startOffset, countChars(block.content));\n }\n prevBlockCharCountRef.current = nextCharCount;\n blockTimelineRef.current = timelineForRender;\n lastRenderTsRef.current = typeof performance === 'undefined' ? Date.now() : performance.now();\n }, [blocks, timelineForRender]);\n\n return (\n <div className={styles.animated}>\n {blocks.map((block, index) => {\n const state = getBlockState(index);\n if (state === 'queued') return null;\n const timelineElapsedMs = timelineForRender.get(block.startOffset) ?? 0;\n\n let plugins: Pluggable[];\n if (state === 'streaming') {\n plugins = [\n ...baseRehypePlugins,\n [\n rehypeStreamAnimated,\n { charDelay, fadeDuration: STREAM_FADE_DURATION, timelineElapsedMs },\n ],\n ];\n } else if (state === 'animating') {\n // Continue from previously rendered progress instead of restarting\n // or force-switching to fully revealed.\n plugins = [\n ...baseRehypePlugins,\n [\n rehypeStreamAnimated,\n { charDelay, fadeDuration: STREAM_FADE_DURATION, timelineElapsedMs },\n ],\n ];\n } else {\n // Keep fade continuity for just-finished chars; avoid instant class\n // switch to stream-char-revealed that would cancel in-flight fades.\n plugins = [\n ...baseRehypePlugins,\n [\n rehypeStreamAnimated,\n { charDelay, fadeDuration: STREAM_FADE_DURATION, timelineElapsedMs },\n ],\n ];\n }\n\n return (\n <StreamdownBlock\n {...rest}\n components={components}\n key={`${generatedId}-${block.startOffset}`}\n rehypePlugins={plugins}\n remarkPlugins={remarkPlugins}\n >\n {block.content}\n </StreamdownBlock>\n );\n })}\n </div>\n );\n});\n\nStreamdownRender.displayName = 'StreamdownRender';\n\nexport default StreamdownRender;\n"],"mappings":"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"}
|
|
@@ -8,8 +8,8 @@ const styles = createStaticStyles(({ css: css$1 }) => {
|
|
|
8
8
|
opacity: 0;
|
|
9
9
|
|
|
10
10
|
animation-name: ${fadeIn};
|
|
11
|
-
animation-duration:
|
|
12
|
-
animation-timing-function:
|
|
11
|
+
animation-duration: 280ms;
|
|
12
|
+
animation-timing-function: cubic-bezier(0.33, 0, 0.67, 1);
|
|
13
13
|
animation-fill-mode: forwards;
|
|
14
14
|
}
|
|
15
15
|
|