@lobehub/ui 5.24.0 → 5.25.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/A/index.mjs +2 -1
- package/es/A/index.mjs.map +1 -1
- package/es/Accordion/AccordionItem.mjs +0 -1
- package/es/Accordion/AccordionItem.mjs.map +1 -1
- package/es/ActionIcon/components/utils.mjs +7 -9
- package/es/ActionIcon/components/utils.mjs.map +1 -1
- package/es/Checkbox/CheckboxGroup.mjs +4 -2
- package/es/Checkbox/CheckboxGroup.mjs.map +1 -1
- package/es/CodeDiff/CodeDiff.mjs +20 -18
- package/es/CodeDiff/CodeDiff.mjs.map +1 -1
- package/es/CodeDiff/PatchDiff.mjs +18 -16
- package/es/CodeDiff/PatchDiff.mjs.map +1 -1
- package/es/CopyButton/CopyButton.mjs +2 -1
- package/es/CopyButton/CopyButton.mjs.map +1 -1
- package/es/Highlighter/LangSelect.mjs +41 -40
- package/es/Highlighter/LangSelect.mjs.map +1 -1
- package/es/Highlighter/SyntaxHighlighter/StaticRenderer.mjs +2 -1
- package/es/Highlighter/SyntaxHighlighter/StaticRenderer.mjs.map +1 -1
- package/es/HotkeyInput/HotkeyInput.mjs +3 -1
- package/es/HotkeyInput/HotkeyInput.mjs.map +1 -1
- package/es/HtmlPreview/const.mjs +1 -1
- package/es/HtmlPreview/const.mjs.map +1 -1
- package/es/Icon/components/utils.mjs +7 -9
- package/es/Icon/components/utils.mjs.map +1 -1
- package/es/Image/components/Toolbar.mjs +2 -1
- package/es/Image/components/Toolbar.mjs.map +1 -1
- package/es/Img/index.mjs +2 -1
- package/es/Img/index.mjs.map +1 -1
- package/es/Markdown/SyntaxMarkdown/StreamdownRender.mjs +2 -1
- package/es/Markdown/SyntaxMarkdown/StreamdownRender.mjs.map +1 -1
- package/es/Markdown/SyntaxMarkdown/useSmoothStreamContent.mjs +2 -2
- package/es/Markdown/SyntaxMarkdown/useSmoothStreamContent.mjs.map +1 -1
- package/es/Modal/RawModalStackItem.mjs +6 -5
- package/es/Modal/RawModalStackItem.mjs.map +1 -1
- package/es/Modal/imperative.mjs +4 -2
- package/es/Modal/imperative.mjs.map +1 -1
- package/es/NeuralNetworkLoading/NeuralNetworkLoading.mjs +1 -1
- package/es/NeuralNetworkLoading/NeuralNetworkLoading.mjs.map +1 -1
- package/es/ScrollShadow/ScrollShadow.mjs +13 -12
- package/es/ScrollShadow/ScrollShadow.mjs.map +1 -1
- package/es/Skeleton/SkeletonButton.mjs +2 -1
- package/es/Skeleton/SkeletonButton.mjs.map +1 -1
- package/es/SortableList/SortableList.mjs +5 -1
- package/es/SortableList/SortableList.mjs.map +1 -1
- package/es/Text/Text.mjs +22 -21
- package/es/Text/Text.mjs.map +1 -1
- package/es/ThemeProvider/ThemeProvider.mjs +3 -2
- package/es/ThemeProvider/ThemeProvider.mjs.map +1 -1
- package/es/awesome/GradientButton/GradientButton.mjs +1 -3
- package/es/awesome/GradientButton/GradientButton.mjs.map +1 -1
- package/es/awesome/Spotlight/Spotlight.mjs +2 -1
- package/es/awesome/Spotlight/Spotlight.mjs.map +1 -1
- package/es/base-ui/Button/Button.mjs +13 -11
- package/es/base-ui/Button/Button.mjs.map +1 -1
- package/es/base-ui/Button/SplitButton.mjs +2 -1
- package/es/base-ui/Button/SplitButton.mjs.map +1 -1
- package/es/base-ui/ContextMenu/ContextMenuTrigger.mjs +2 -1
- package/es/base-ui/ContextMenu/ContextMenuTrigger.mjs.map +1 -1
- package/es/base-ui/ContextMenu/renderItems.mjs +2 -1
- package/es/base-ui/ContextMenu/renderItems.mjs.map +1 -1
- package/es/base-ui/Drawer/Drawer.d.mts +6 -0
- package/es/base-ui/Drawer/Drawer.mjs +152 -0
- package/es/base-ui/Drawer/Drawer.mjs.map +1 -0
- package/es/base-ui/Drawer/DrawerLayerContext.mjs +10 -0
- package/es/base-ui/Drawer/DrawerLayerContext.mjs.map +1 -0
- package/es/base-ui/Drawer/DrawerPushContext.mjs +27 -0
- package/es/base-ui/Drawer/DrawerPushContext.mjs.map +1 -0
- package/es/base-ui/Drawer/atoms.d.mts +58 -0
- package/es/base-ui/Drawer/atoms.mjs +262 -0
- package/es/base-ui/Drawer/atoms.mjs.map +1 -0
- package/es/base-ui/Drawer/constants.d.mts +17 -0
- package/es/base-ui/Drawer/constants.mjs +72 -0
- package/es/base-ui/Drawer/constants.mjs.map +1 -0
- package/es/base-ui/Drawer/index.d.mts +5 -0
- package/es/base-ui/Drawer/style.mjs +295 -0
- package/es/base-ui/Drawer/style.mjs.map +1 -0
- package/es/base-ui/Drawer/type.d.mts +65 -0
- package/es/base-ui/DropdownMenu/DropdownMenu.mjs +2 -1
- package/es/base-ui/DropdownMenu/DropdownMenu.mjs.map +1 -1
- package/es/base-ui/DropdownMenu/atoms.mjs +2 -1
- package/es/base-ui/DropdownMenu/atoms.mjs.map +1 -1
- package/es/base-ui/DropdownMenu/renderItems.mjs +2 -1
- package/es/base-ui/DropdownMenu/renderItems.mjs.map +1 -1
- package/es/base-ui/FloatingSheet/FloatingSheet.mjs +4 -1
- package/es/base-ui/FloatingSheet/FloatingSheet.mjs.map +1 -1
- package/es/base-ui/Input/style.d.mts +1 -1
- package/es/base-ui/Modal/atoms.mjs +7 -5
- package/es/base-ui/Modal/atoms.mjs.map +1 -1
- package/es/base-ui/Modal/imperative.mjs +2 -1
- package/es/base-ui/Modal/imperative.mjs.map +1 -1
- package/es/base-ui/Popover/atoms.mjs +2 -1
- package/es/base-ui/Popover/atoms.mjs.map +1 -1
- package/es/base-ui/Popover/context.mjs +4 -2
- package/es/base-ui/Popover/context.mjs.map +1 -1
- package/es/base-ui/Select/atoms.mjs +2 -1
- package/es/base-ui/Select/atoms.mjs.map +1 -1
- package/es/base-ui/index.d.mts +6 -1
- package/es/base-ui/index.mjs +4 -1
- package/es/brand/LobeChat/index.mjs +0 -1
- package/es/brand/LobeChat/index.mjs.map +1 -1
- package/es/brand/LobeHub/index.mjs +0 -1
- package/es/brand/LobeHub/index.mjs.map +1 -1
- package/es/brand/Logo3d/index.mjs +2 -1
- package/es/brand/Logo3d/index.mjs.map +1 -1
- package/es/chat/ChatItem/components/Actions.mjs +5 -4
- package/es/chat/ChatItem/components/Actions.mjs.map +1 -1
- package/es/chat/ChatList/ChatList.mjs +2 -1
- package/es/chat/ChatList/ChatList.mjs.map +1 -1
- package/es/color/ColorScales/ScaleRow.mjs +0 -2
- package/es/color/ColorScales/ScaleRow.mjs.map +1 -1
- package/es/hooks/useMermaid.mjs +2 -1
- package/es/hooks/useMermaid.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -17,15 +17,13 @@ const calcSize = (iconSize) => {
|
|
|
17
17
|
size = 14;
|
|
18
18
|
strokeWidth = 2;
|
|
19
19
|
break;
|
|
20
|
-
default:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
break;
|
|
20
|
+
default: if (iconSize) {
|
|
21
|
+
size = iconSize?.size || 24;
|
|
22
|
+
strokeWidth = iconSize?.strokeWidth || 2;
|
|
23
|
+
} else {
|
|
24
|
+
size = "1em";
|
|
25
|
+
strokeWidth = 2;
|
|
26
|
+
}
|
|
29
27
|
}
|
|
30
28
|
return {
|
|
31
29
|
size,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.mjs","names":[],"sources":["../../../src/Icon/components/utils.ts"],"sourcesContent":["import { isNumber } from 'es-toolkit/compat';\n\nimport type { IconSize } from '../type';\n\nexport const calcSize = (iconSize?: IconSize) => {\n if (isNumber(iconSize)) {\n return { size: iconSize };\n }\n\n let size: number | string;\n let strokeWidth: number | string;\n\n switch (iconSize) {\n case 'large': {\n size = 24;\n strokeWidth = 2;\n break;\n }\n case 'middle': {\n size = 20;\n strokeWidth = 2;\n break;\n }\n case 'small': {\n size = 14;\n strokeWidth = 2;\n break;\n }\n default: {\n if (iconSize) {\n size = iconSize?.size || 24;\n strokeWidth = iconSize?.strokeWidth || 2;\n } else {\n size = '1em';\n strokeWidth = 2;\n }\n break;\n }\n }\n return { size, strokeWidth };\n};\n"],"mappings":";;AAIA,MAAa,YAAY,aAAwB;CAC/C,IAAI,SAAS,QAAQ,GACnB,OAAO,EAAE,MAAM,SAAS;CAG1B,IAAI;CACJ,IAAI;CAEJ,QAAQ,UAAR;EACE,KAAK;GACH,OAAO;GACP,cAAc;GACd;EAEF,KAAK;GACH,OAAO;GACP,cAAc;GACd;EAEF,KAAK;GACH,OAAO;GACP,cAAc;GACd;EAEF
|
|
1
|
+
{"version":3,"file":"utils.mjs","names":[],"sources":["../../../src/Icon/components/utils.ts"],"sourcesContent":["import { isNumber } from 'es-toolkit/compat';\n\nimport type { IconSize } from '../type';\n\nexport const calcSize = (iconSize?: IconSize) => {\n if (isNumber(iconSize)) {\n return { size: iconSize };\n }\n\n let size: number | string;\n let strokeWidth: number | string;\n\n switch (iconSize) {\n case 'large': {\n size = 24;\n strokeWidth = 2;\n break;\n }\n case 'middle': {\n size = 20;\n strokeWidth = 2;\n break;\n }\n case 'small': {\n size = 14;\n strokeWidth = 2;\n break;\n }\n default: {\n if (iconSize) {\n size = iconSize?.size || 24;\n strokeWidth = iconSize?.strokeWidth || 2;\n } else {\n size = '1em';\n strokeWidth = 2;\n }\n break;\n }\n }\n return { size, strokeWidth };\n};\n"],"mappings":";;AAIA,MAAa,YAAY,aAAwB;CAC/C,IAAI,SAAS,QAAQ,GACnB,OAAO,EAAE,MAAM,SAAS;CAG1B,IAAI;CACJ,IAAI;CAEJ,QAAQ,UAAR;EACE,KAAK;GACH,OAAO;GACP,cAAc;GACd;EAEF,KAAK;GACH,OAAO;GACP,cAAc;GACd;EAEF,KAAK;GACH,OAAO;GACP,cAAc;GACd;EAEF,SACE,IAAI,UAAU;GACZ,OAAO,UAAU,QAAQ;GACzB,cAAc,UAAU,eAAe;EACzC,OAAO;GACL,OAAO;GACP,cAAc;EAChB;CAGJ;CACA,OAAO;EAAE;EAAM;CAAY;AAC7B"}
|
|
@@ -56,7 +56,8 @@ const Toolbar = memo(({ children, info, minScale, maxScale }) => {
|
|
|
56
56
|
const handleCopy = useCallback(async () => {
|
|
57
57
|
setCopyLoading(true);
|
|
58
58
|
try {
|
|
59
|
-
const
|
|
59
|
+
const blob = await (await fetch(url, { mode: "cors" })).blob();
|
|
60
|
+
const clipboardBlob = await getClipboardBlob(blob);
|
|
60
61
|
await navigator.clipboard.write([new ClipboardItem(clipboardBlob)]);
|
|
61
62
|
message.success(t("image.copySuccess"));
|
|
62
63
|
} catch {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toolbar.mjs","names":["imageMessages","Flexbox"],"sources":["../../../src/Image/components/Toolbar.tsx"],"sourcesContent":["import { message } from 'antd';\nimport {\n Copy,\n Download,\n FlipHorizontal,\n FlipVertical,\n RotateCcw,\n RotateCw,\n ZoomIn,\n ZoomOut,\n} from 'lucide-react';\nimport { type ToolbarRenderInfoType } from 'rc-image/lib/Preview';\nimport { memo, type ReactNode, useCallback, useState } from 'react';\n\nimport ActionIcon from '@/ActionIcon';\nimport { Flexbox } from '@/Flex';\nimport imageMessages from '@/i18n/resources/en/image';\nimport { useTranslation } from '@/i18n/useTranslation';\nimport { TooltipGroup } from '@/Tooltip';\nimport { getClipboardBlob } from '@/utils/blobToPng';\nimport { downloadBlob } from '@/utils/downloadBlob';\n\nimport { styles } from '../style';\n\nconst getFileNameFromUrl = (url: string): string => {\n try {\n const pathname = new URL(url).pathname;\n const match = pathname.match(/\\/([^/]+)$/);\n return match ? decodeURIComponent(match[1]) : 'image';\n } catch {\n return 'image';\n }\n};\n\nconst getExtensionFromMimeType = (mimeType: string): string => {\n const map: Record<string, string> = {\n 'image/svg+xml': 'svg',\n 'image/png': 'png',\n 'image/jpeg': 'jpg',\n 'image/jpg': 'jpg',\n 'image/webp': 'webp',\n 'image/gif': 'gif',\n };\n return map[mimeType?.toLowerCase()] || mimeType?.split('/')[1]?.split('+')[0] || 'png';\n};\n\nexport interface ToolbarProps {\n children?: ReactNode;\n info: Omit<ToolbarRenderInfoType, 'current' | 'total'>;\n maxScale: number;\n minScale: number;\n}\n\nconst Toolbar = memo<ToolbarProps>(({ children, info, minScale, maxScale }) => {\n const { t } = useTranslation(imageMessages);\n const [containerEl, setContainerEl] = useState<HTMLElement | null>(null);\n const [copyLoading, setCopyLoading] = useState(false);\n const [downloadLoading, setDownloadLoading] = useState(false);\n const {\n transform: { scale },\n actions: { onFlipY, onFlipX, onRotateLeft, onRotateRight, onZoomOut, onZoomIn },\n image: { url },\n } = info;\n\n const handleDownload = useCallback(async () => {\n setDownloadLoading(true);\n try {\n const response = await fetch(url, { mode: 'cors' });\n const blob = await response.blob();\n const blobUrl = URL.createObjectURL(blob);\n let fileName = getFileNameFromUrl(url);\n const ext = getExtensionFromMimeType(blob.type);\n if (!fileName.includes('.')) {\n fileName = `${fileName}.${ext}`;\n } else if (fileName.endsWith('.svg+xml')) {\n fileName = fileName.replace(/\\.svg\\+xml$/i, '.svg');\n }\n await downloadBlob(blobUrl, fileName);\n URL.revokeObjectURL(blobUrl);\n message.success(t('image.downloadSuccess'));\n } catch {\n message.error(t('image.downloadFailed'));\n } finally {\n setDownloadLoading(false);\n }\n }, [url, t]);\n\n const handleCopy = useCallback(async () => {\n setCopyLoading(true);\n try {\n const response = await fetch(url, { mode: 'cors' });\n const blob = await response.blob();\n const clipboardBlob = await getClipboardBlob(blob);\n await navigator.clipboard.write([new ClipboardItem(clipboardBlob)]);\n message.success(t('image.copySuccess'));\n } catch {\n message.error(t('image.copyFailed'));\n } finally {\n setCopyLoading(false);\n }\n }, [url, t]);\n\n return (\n <TooltipGroup popupContainer={containerEl ?? undefined}>\n <Flexbox horizontal className={styles.toolbar} gap={4} ref={setContainerEl}>\n <ActionIcon icon={FlipHorizontal} title={t('image.flipHorizontal')} onClick={onFlipX} />\n <ActionIcon icon={FlipVertical} title={t('image.flipVertical')} onClick={onFlipY} />\n <ActionIcon icon={RotateCcw} title={t('image.rotateLeft')} onClick={onRotateLeft} />\n <ActionIcon icon={RotateCw} title={t('image.rotateRight')} onClick={onRotateRight} />\n <ActionIcon\n disabled={scale === minScale}\n icon={ZoomOut}\n title={t('image.zoomOut')}\n onClick={onZoomOut}\n />\n <ActionIcon\n disabled={scale === maxScale}\n icon={ZoomIn}\n title={t('image.zoomIn')}\n onClick={onZoomIn}\n />\n <ActionIcon\n icon={Copy}\n loading={copyLoading}\n title={t('image.copy')}\n onClick={handleCopy}\n />\n <ActionIcon\n icon={Download}\n loading={downloadLoading}\n title={t('image.download')}\n onClick={handleDownload}\n />\n {children}\n </Flexbox>\n </TooltipGroup>\n );\n});\n\nexport default Toolbar;\n"],"mappings":";;;;;;;;;;;;;AAwBA,MAAM,sBAAsB,QAAwB;CAClD,IAAI;EAEF,MAAM,QADW,IAAI,IAAI,GAAG,CAAC,CAAC,SACP,MAAM,YAAY;EACzC,OAAO,QAAQ,mBAAmB,MAAM,EAAE,IAAI;CAChD,QAAQ;EACN,OAAO;CACT;AACF;AAEA,MAAM,4BAA4B,aAA6B;CAS7D,OAAO;EAPL,iBAAiB;EACjB,aAAa;EACb,cAAc;EACd,aAAa;EACb,cAAc;EACd,aAAa;CAEN,EAAE,UAAU,YAAY,MAAM,UAAU,MAAM,GAAG,CAAC,CAAC,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC,MAAM;AACnF;AASA,MAAM,UAAU,MAAoB,EAAE,UAAU,MAAM,UAAU,eAAe;CAC7E,MAAM,EAAE,MAAM,eAAeA,aAAa;CAC1C,MAAM,CAAC,aAAa,kBAAkB,SAA6B,IAAI;CACvE,MAAM,CAAC,aAAa,kBAAkB,SAAS,KAAK;CACpD,MAAM,CAAC,iBAAiB,sBAAsB,SAAS,KAAK;CAC5D,MAAM,EACJ,WAAW,EAAE,SACb,SAAS,EAAE,SAAS,SAAS,cAAc,eAAe,WAAW,YACrE,OAAO,EAAE,UACP;CAEJ,MAAM,iBAAiB,YAAY,YAAY;EAC7C,mBAAmB,IAAI;EACvB,IAAI;GAEF,MAAM,OAAO,OAAM,MADI,MAAM,KAAK,EAAE,MAAM,OAAO,CAAC,EAAA,CACtB,KAAK;GACjC,MAAM,UAAU,IAAI,gBAAgB,IAAI;GACxC,IAAI,WAAW,mBAAmB,GAAG;GACrC,MAAM,MAAM,yBAAyB,KAAK,IAAI;GAC9C,IAAI,CAAC,SAAS,SAAS,GAAG,GACxB,WAAW,GAAG,SAAS,GAAG;QACrB,IAAI,SAAS,SAAS,UAAU,GACrC,WAAW,SAAS,QAAQ,gBAAgB,MAAM;GAEpD,MAAM,aAAa,SAAS,QAAQ;GACpC,IAAI,gBAAgB,OAAO;GAC3B,QAAQ,QAAQ,EAAE,uBAAuB,CAAC;EAC5C,QAAQ;GACN,QAAQ,MAAM,EAAE,sBAAsB,CAAC;EACzC,UAAU;GACR,mBAAmB,KAAK;EAC1B;CACF,GAAG,CAAC,KAAK,CAAC,CAAC;CAEX,MAAM,aAAa,YAAY,YAAY;EACzC,eAAe,IAAI;EACnB,IAAI;
|
|
1
|
+
{"version":3,"file":"Toolbar.mjs","names":["imageMessages","Flexbox"],"sources":["../../../src/Image/components/Toolbar.tsx"],"sourcesContent":["import { message } from 'antd';\nimport {\n Copy,\n Download,\n FlipHorizontal,\n FlipVertical,\n RotateCcw,\n RotateCw,\n ZoomIn,\n ZoomOut,\n} from 'lucide-react';\nimport { type ToolbarRenderInfoType } from 'rc-image/lib/Preview';\nimport { memo, type ReactNode, useCallback, useState } from 'react';\n\nimport ActionIcon from '@/ActionIcon';\nimport { Flexbox } from '@/Flex';\nimport imageMessages from '@/i18n/resources/en/image';\nimport { useTranslation } from '@/i18n/useTranslation';\nimport { TooltipGroup } from '@/Tooltip';\nimport { getClipboardBlob } from '@/utils/blobToPng';\nimport { downloadBlob } from '@/utils/downloadBlob';\n\nimport { styles } from '../style';\n\nconst getFileNameFromUrl = (url: string): string => {\n try {\n const pathname = new URL(url).pathname;\n const match = pathname.match(/\\/([^/]+)$/);\n return match ? decodeURIComponent(match[1]) : 'image';\n } catch {\n return 'image';\n }\n};\n\nconst getExtensionFromMimeType = (mimeType: string): string => {\n const map: Record<string, string> = {\n 'image/svg+xml': 'svg',\n 'image/png': 'png',\n 'image/jpeg': 'jpg',\n 'image/jpg': 'jpg',\n 'image/webp': 'webp',\n 'image/gif': 'gif',\n };\n return map[mimeType?.toLowerCase()] || mimeType?.split('/')[1]?.split('+')[0] || 'png';\n};\n\nexport interface ToolbarProps {\n children?: ReactNode;\n info: Omit<ToolbarRenderInfoType, 'current' | 'total'>;\n maxScale: number;\n minScale: number;\n}\n\nconst Toolbar = memo<ToolbarProps>(({ children, info, minScale, maxScale }) => {\n const { t } = useTranslation(imageMessages);\n const [containerEl, setContainerEl] = useState<HTMLElement | null>(null);\n const [copyLoading, setCopyLoading] = useState(false);\n const [downloadLoading, setDownloadLoading] = useState(false);\n const {\n transform: { scale },\n actions: { onFlipY, onFlipX, onRotateLeft, onRotateRight, onZoomOut, onZoomIn },\n image: { url },\n } = info;\n\n const handleDownload = useCallback(async () => {\n setDownloadLoading(true);\n try {\n const response = await fetch(url, { mode: 'cors' });\n const blob = await response.blob();\n const blobUrl = URL.createObjectURL(blob);\n let fileName = getFileNameFromUrl(url);\n const ext = getExtensionFromMimeType(blob.type);\n if (!fileName.includes('.')) {\n fileName = `${fileName}.${ext}`;\n } else if (fileName.endsWith('.svg+xml')) {\n fileName = fileName.replace(/\\.svg\\+xml$/i, '.svg');\n }\n await downloadBlob(blobUrl, fileName);\n URL.revokeObjectURL(blobUrl);\n message.success(t('image.downloadSuccess'));\n } catch {\n message.error(t('image.downloadFailed'));\n } finally {\n setDownloadLoading(false);\n }\n }, [url, t]);\n\n const handleCopy = useCallback(async () => {\n setCopyLoading(true);\n try {\n const response = await fetch(url, { mode: 'cors' });\n const blob = await response.blob();\n const clipboardBlob = await getClipboardBlob(blob);\n await navigator.clipboard.write([new ClipboardItem(clipboardBlob)]);\n message.success(t('image.copySuccess'));\n } catch {\n message.error(t('image.copyFailed'));\n } finally {\n setCopyLoading(false);\n }\n }, [url, t]);\n\n return (\n <TooltipGroup popupContainer={containerEl ?? undefined}>\n <Flexbox horizontal className={styles.toolbar} gap={4} ref={setContainerEl}>\n <ActionIcon icon={FlipHorizontal} title={t('image.flipHorizontal')} onClick={onFlipX} />\n <ActionIcon icon={FlipVertical} title={t('image.flipVertical')} onClick={onFlipY} />\n <ActionIcon icon={RotateCcw} title={t('image.rotateLeft')} onClick={onRotateLeft} />\n <ActionIcon icon={RotateCw} title={t('image.rotateRight')} onClick={onRotateRight} />\n <ActionIcon\n disabled={scale === minScale}\n icon={ZoomOut}\n title={t('image.zoomOut')}\n onClick={onZoomOut}\n />\n <ActionIcon\n disabled={scale === maxScale}\n icon={ZoomIn}\n title={t('image.zoomIn')}\n onClick={onZoomIn}\n />\n <ActionIcon\n icon={Copy}\n loading={copyLoading}\n title={t('image.copy')}\n onClick={handleCopy}\n />\n <ActionIcon\n icon={Download}\n loading={downloadLoading}\n title={t('image.download')}\n onClick={handleDownload}\n />\n {children}\n </Flexbox>\n </TooltipGroup>\n );\n});\n\nexport default Toolbar;\n"],"mappings":"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"}
|
package/es/Img/index.mjs
CHANGED
|
@@ -7,7 +7,8 @@ const createContainer = (as) => memo((props) => createElement(as, props));
|
|
|
7
7
|
const Img = ({ unoptimized, ...rest }) => {
|
|
8
8
|
const config = use(ConfigContext);
|
|
9
9
|
const render = config?.imgAs || "img";
|
|
10
|
-
|
|
10
|
+
const ImgContainer = useMemo(() => createContainer(render), [render]);
|
|
11
|
+
return /* @__PURE__ */ jsx(ImgContainer, {
|
|
11
12
|
unoptimized: unoptimized === void 0 ? config?.imgUnoptimized : unoptimized,
|
|
12
13
|
...rest
|
|
13
14
|
});
|
package/es/Img/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/Img/index.tsx"],"sourcesContent":["'use client';\n\nimport { type ImageProps } from 'antd';\nimport { createElement, type ElementType, type FC, memo, type Ref, use, useMemo } from 'react';\n\nimport { ConfigContext } from '@/ConfigProvider';\nimport { type ImgProps as HtmlImgeProps } from '@/types';\n\nconst createContainer = (as: ElementType) => memo((props: any) => createElement(as, props));\n\ntype ImgProps = HtmlImgeProps & ImageProps & { ref?: Ref<HTMLImageElement>; unoptimized?: boolean };\n\nconst Img: FC<ImgProps> = ({ unoptimized, ...rest }) => {\n const config = use(ConfigContext);\n const render = config?.imgAs || 'img';\n\n const ImgContainer = useMemo(() => createContainer(render), [render]);\n\n return (\n <ImgContainer\n unoptimized={unoptimized === undefined ? config?.imgUnoptimized : unoptimized}\n {...rest}\n />\n );\n};\n\nImg.displayName = 'Img';\n\nexport default Img;\n"],"mappings":";;;;;AAQA,MAAM,mBAAmB,OAAoB,MAAM,UAAe,cAAc,IAAI,KAAK,CAAC;AAI1F,MAAM,OAAqB,EAAE,aAAa,GAAG,WAAW;CACtD,MAAM,SAAS,IAAI,aAAa;CAChC,MAAM,SAAS,QAAQ,SAAS;
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/Img/index.tsx"],"sourcesContent":["'use client';\n\nimport { type ImageProps } from 'antd';\nimport { createElement, type ElementType, type FC, memo, type Ref, use, useMemo } from 'react';\n\nimport { ConfigContext } from '@/ConfigProvider';\nimport { type ImgProps as HtmlImgeProps } from '@/types';\n\nconst createContainer = (as: ElementType) => memo((props: any) => createElement(as, props));\n\ntype ImgProps = HtmlImgeProps & ImageProps & { ref?: Ref<HTMLImageElement>; unoptimized?: boolean };\n\nconst Img: FC<ImgProps> = ({ unoptimized, ...rest }) => {\n const config = use(ConfigContext);\n const render = config?.imgAs || 'img';\n\n const ImgContainer = useMemo(() => createContainer(render), [render]);\n\n return (\n <ImgContainer\n unoptimized={unoptimized === undefined ? config?.imgUnoptimized : unoptimized}\n {...rest}\n />\n );\n};\n\nImg.displayName = 'Img';\n\nexport default Img;\n"],"mappings":";;;;;AAQA,MAAM,mBAAmB,OAAoB,MAAM,UAAe,cAAc,IAAI,KAAK,CAAC;AAI1F,MAAM,OAAqB,EAAE,aAAa,GAAG,WAAW;CACtD,MAAM,SAAS,IAAI,aAAa;CAChC,MAAM,SAAS,QAAQ,SAAS;CAEhC,MAAM,eAAe,cAAc,gBAAgB,MAAM,GAAG,CAAC,MAAM,CAAC;CAEpE,OACE,oBAAC,cAAD;EACE,aAAa,gBAAgB,KAAA,IAAY,QAAQ,iBAAiB;EAClE,GAAI;CACL,CAAA;AAEL;AAEA,IAAI,cAAc"}
|
|
@@ -97,10 +97,11 @@ const updateBlockAnimation = ({ blocks, charDelay, getBlockState, pluginsCache,
|
|
|
97
97
|
settled: true
|
|
98
98
|
};
|
|
99
99
|
else {
|
|
100
|
+
const lastBirthTs = births.length > 0 ? births.at(-1) ?? renderNow : renderNow;
|
|
100
101
|
meta = resolveBlockAnimationMeta({
|
|
101
102
|
currentCharDelay: charDelay,
|
|
102
103
|
fadeDuration: 180,
|
|
103
|
-
lastElapsedMs: renderNow -
|
|
104
|
+
lastElapsedMs: renderNow - lastBirthTs,
|
|
104
105
|
previousCharDelay: runtime.charDelay,
|
|
105
106
|
state
|
|
106
107
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StreamdownRender.mjs","names":[],"sources":["../../../src/Markdown/SyntaxMarkdown/StreamdownRender.tsx"],"sourcesContent":["'use client';\n\nimport { marked } from 'marked';\nimport {\n memo,\n Profiler,\n type ProfilerOnRenderCallback,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n} from 'react';\nimport { 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 { useStableValue } from '@/hooks/useStableValue';\nimport { useMarkdownContext } from '@/Markdown/components/MarkdownProvider';\nimport {\n rehypeStreamAnimated,\n type StreamAnimatedRuntime,\n} from '@/Markdown/plugins/rehypeStreamAnimated';\nimport { useStreamdownProfiler } from '@/Markdown/streamProfiler';\nimport { type StreamAnimationGranularity } from '@/Markdown/type';\nimport { getNow } from '@/utils/getNow';\nimport { isDeepEqual } from '@/utils/isDeepEqual';\n\nimport { CachedMarkdown } from './CachedMarkdown';\nimport { type BlockAnimationMeta, resolveBlockAnimationMeta } from './streamAnimationMeta';\nimport { STREAM_FADE_DURATION, styles } from './style';\nimport { countChars, useSmoothStreamContent } from './useSmoothStreamContent';\nimport { type BlockInfo, type BlockState, useStreamQueue } from './useStreamQueue';\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 isDeepEqual(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 <CachedMarkdown {...rest}>{children}</CachedMarkdown>;\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\ninterface BlockRuntime extends StreamAnimatedRuntime {\n charCount: number;\n charDelay?: number;\n rawLength: number;\n settled: boolean;\n}\n\ninterface BlockPluginsCacheEntry {\n base: PluggableList;\n granularity: StreamAnimationGranularity;\n value: PluggableList;\n}\n\ninterface UpdateBlockAnimationArgs {\n blocks: BlockInfo[];\n charDelay: number;\n getBlockState: (index: number) => BlockState;\n pluginsCache: Map<number, BlockPluginsCacheEntry>;\n renderNow: number;\n revealClock: { lastTs: number };\n runtimes: Map<number, BlockRuntime>;\n}\n\nconst MIN_STREAM_CHAR_PACE_MS = 2;\nconst MAX_REVEAL_GAP_MS = 160;\n\n// Runs in the render phase: extends each visible block's birth timeline in\n// place and resolves its animation meta in one pass. Mutations are\n// idempotent for a given block content/length, so discarded or StrictMode\n// double renders re-derive the same state.\nconst updateBlockAnimation = ({\n blocks,\n charDelay,\n getBlockState,\n pluginsCache,\n renderNow,\n revealClock,\n runtimes,\n}: UpdateBlockAnimationArgs): Map<number, BlockAnimationMeta> => {\n const blockAnimationMeta = new Map<number, BlockAnimationMeta>();\n const alive = new Set<number>();\n let revealedNewChars = false;\n\n for (const [index, block] of blocks.entries()) {\n alive.add(block.startOffset);\n\n // Queued blocks are not rendered. Defer birth assignment so that\n // when the block later transitions to animating/streaming, its\n // chars start fading from that moment instead of having already\n // \"aged out\" of the fade window.\n const state = getBlockState(index);\n if (state === 'queued') continue;\n\n let runtime = runtimes.get(block.startOffset);\n if (!runtime) {\n runtime = { births: [], charCount: 0, rawLength: -1, settled: false, styles: [] };\n runtimes.set(block.startOffset, runtime);\n }\n\n if (runtime.rawLength !== block.content.length) {\n runtime.charCount = countChars(block.content);\n runtime.rawLength = block.content.length;\n }\n\n const blockCharCount = runtime.charCount;\n const births = runtime.births;\n\n if (births.length > blockCharCount) {\n // Block content shrunk (stream restart or upstream rewrite).\n births.length = blockCharCount;\n runtime.styles.length = blockCharCount;\n }\n\n if (births.length < blockCharCount) {\n // Chain each new char monotonically after the previous one so fades\n // never race out of order. Cap how far the fade queue can run ahead\n // of renderNow to prevent stream-faster-than-fade producing seconds\n // of invisible backlog at the tail.\n //\n // The streaming tail paces its stagger from the observed reveal-commit\n // gap instead of the queue's fixed charDelay: a commit's chars are\n // spread to land exactly until the next commit arrives, so the flow\n // stays per-char continuous no matter how far apart the throttled\n // commits are.\n const newChars = blockCharCount - births.length;\n let pace = charDelay;\n let cap = renderNow + STREAM_FADE_DURATION;\n if (state === 'streaming') {\n revealedNewChars = true;\n const gapMs = Math.min(Math.max(renderNow - revealClock.lastTs, 16), MAX_REVEAL_GAP_MS);\n pace = Math.min(charDelay, Math.max(gapMs / newChars, MIN_STREAM_CHAR_PACE_MS));\n cap = renderNow + gapMs + STREAM_FADE_DURATION;\n }\n for (let i = births.length; i < blockCharCount; i++) {\n const prevBirth = i > 0 ? births[i - 1] : renderNow - pace;\n const chained = prevBirth + pace;\n births.push(Math.min(cap, Math.max(chained, renderNow)));\n }\n }\n\n let meta: BlockAnimationMeta;\n if (runtime.settled) {\n // Settled is monotone: a revealed block's births are frozen, so once\n // its last fade completed it stays settled until the runtime is\n // pruned by a stream restart.\n meta = { charDelay: runtime.charDelay ?? charDelay, settled: true };\n } else {\n const lastBirthTs = births.length > 0 ? (births.at(-1) ?? renderNow) : renderNow;\n meta = resolveBlockAnimationMeta({\n currentCharDelay: charDelay,\n fadeDuration: STREAM_FADE_DURATION,\n lastElapsedMs: renderNow - lastBirthTs,\n previousCharDelay: runtime.charDelay,\n state,\n });\n runtime.settled = meta.settled;\n }\n runtime.charDelay = meta.charDelay;\n\n blockAnimationMeta.set(block.startOffset, meta);\n }\n\n if (revealedNewChars) {\n revealClock.lastTs = renderNow;\n }\n\n for (const key of runtimes.keys()) {\n if (!alive.has(key)) {\n runtimes.delete(key);\n pluginsCache.delete(key);\n }\n }\n\n return blockAnimationMeta;\n};\n\ninterface StreamdownBlocksProps {\n content: string;\n markdownOptions: Omit<Options, 'children'>;\n}\n\nconst StreamdownBlocks = memo<StreamdownBlocksProps>(\n ({ content: smoothedContent, markdownOptions: rest }) => {\n const { streamAnimationGranularity = 'char' } = useMarkdownContext();\n const profiler = useStreamdownProfiler();\n const components = useMarkdownComponents();\n const baseRehypePlugins = useStablePlugins(useMarkdownRehypePlugins());\n const remarkPlugins = useStablePlugins(useMarkdownRemarkPlugins());\n const generatedId = useId();\n\n const processedContentResult = useMemo(() => {\n const start = profiler ? getNow() : 0;\n const value = remend(smoothedContent);\n\n return {\n durationMs: profiler ? getNow() - start : 0,\n value,\n };\n }, [profiler, smoothedContent]);\n const processedContent = processedContentResult.value;\n\n const blocksResult = useMemo(() => {\n const start = profiler ? getNow() : 0;\n const tokens = marked.lexer(processedContent);\n let offset = 0;\n\n const value = tokens.map((token) => {\n const block = { content: token.raw, startOffset: offset };\n offset += token.raw.length;\n return block;\n });\n\n return {\n durationMs: profiler ? getNow() - start : 0,\n value,\n };\n }, [processedContent, profiler]);\n const blocks: BlockInfo[] = blocksResult.value;\n\n const { getBlockState, charDelay } = useStreamQueue(blocks);\n const blockRuntimesRef = useRef<Map<number, BlockRuntime>>(new Map());\n const blockPluginsRef = useRef<Map<number, BlockPluginsCacheEntry>>(new Map());\n const revealClockRef = useRef<{ lastTs: number }>({ lastTs: 0 });\n\n const renderNow = getNow();\n\n const animationStart = profiler ? getNow() : 0;\n const blockAnimationMeta = updateBlockAnimation({\n blocks,\n charDelay,\n getBlockState,\n pluginsCache: blockPluginsRef.current,\n renderNow,\n revealClock: revealClockRef.current,\n runtimes: blockRuntimesRef.current,\n });\n const blockAnimationDurationMs = profiler ? getNow() - animationStart : 0;\n\n useEffect(() => {\n if (!profiler) return;\n\n profiler.recordCalculation({\n durationMs: processedContentResult.durationMs,\n name: 'content-normalize',\n textLength: processedContent.length,\n });\n }, [processedContent.length, processedContentResult.durationMs, profiler]);\n\n useEffect(() => {\n if (!profiler) return;\n\n profiler.recordCalculation({\n durationMs: blocksResult.durationMs,\n itemCount: blocks.length,\n name: 'block-lex',\n textLength: processedContent.length,\n });\n }, [blocks.length, blocksResult.durationMs, processedContent.length, profiler]);\n\n useEffect(() => {\n if (!profiler) return;\n\n profiler.recordCalculation({\n durationMs: blockAnimationDurationMs,\n itemCount: blocks.length,\n name: 'block-births',\n textLength: processedContent.length,\n });\n }, [blockAnimationDurationMs, blocks.length, processedContent.length, profiler]);\n\n const resolveBlockPlugins = (startOffset: number, settled: boolean): PluggableList => {\n if (settled) return baseRehypePlugins;\n\n const cache = blockPluginsRef.current;\n const entry = cache.get(startOffset);\n if (\n entry &&\n entry.base === baseRehypePlugins &&\n entry.granularity === streamAnimationGranularity\n ) {\n return entry.value;\n }\n\n const runtime = blockRuntimesRef.current.get(startOffset);\n const value: PluggableList = [\n ...baseRehypePlugins,\n [\n rehypeStreamAnimated,\n {\n fadeDuration: STREAM_FADE_DURATION,\n granularity: streamAnimationGranularity,\n runtime,\n },\n ],\n ];\n cache.set(startOffset, {\n base: baseRehypePlugins,\n granularity: streamAnimationGranularity,\n value,\n });\n return value;\n };\n\n const handleRootRender = useCallback<ProfilerOnRenderCallback>(\n (_, phase, actualDuration, baseDuration) => {\n profiler?.recordRootCommit({\n actualDuration,\n baseDuration,\n blockCount: blocks.length,\n phase,\n textLength: processedContent.length,\n });\n },\n [blocks.length, processedContent.length, profiler],\n );\n\n const handleBlockRender = useCallback<ProfilerOnRenderCallback>(\n (id, phase, actualDuration, baseDuration) => {\n if (!profiler) return;\n\n const [, indexText, offsetText] = id.split(':');\n const blockIndex = Number(indexText);\n\n if (!Number.isFinite(blockIndex)) return;\n\n const block = blocks[blockIndex];\n if (!block) return;\n\n profiler.recordBlockCommit({\n actualDuration,\n baseDuration,\n blockChars: countChars(block.content),\n blockIndex,\n blockKey: offsetText ?? String(block.startOffset),\n phase,\n state: getBlockState(blockIndex),\n });\n },\n [blocks, getBlockState, profiler],\n );\n\n const content = (\n <div className={styles.animated}>\n {blocks.map((block, index) => {\n const animationMeta = blockAnimationMeta.get(block.startOffset);\n if (!animationMeta) return null;\n\n const plugins = resolveBlockPlugins(block.startOffset, animationMeta.settled);\n const key = `${generatedId}-${block.startOffset}`;\n\n if (!profiler) {\n return (\n <StreamdownBlock\n {...rest}\n components={components}\n key={key}\n rehypePlugins={plugins}\n remarkPlugins={remarkPlugins}\n >\n {block.content}\n </StreamdownBlock>\n );\n }\n\n return (\n <Profiler\n id={`streamdown-block:${index}:${block.startOffset}`}\n key={key}\n onRender={handleBlockRender}\n >\n <StreamdownBlock\n {...rest}\n components={components}\n rehypePlugins={plugins}\n remarkPlugins={remarkPlugins}\n >\n {block.content}\n </StreamdownBlock>\n </Profiler>\n );\n })}\n </div>\n );\n\n if (!profiler) return content;\n\n return (\n <Profiler id={'streamdown-root'} onRender={handleRootRender}>\n {content}\n </Profiler>\n );\n },\n);\n\nStreamdownBlocks.displayName = 'StreamdownBlocks';\n\n// The outer component absorbs the upstream per-chunk prop churn: every\n// streamed chunk re-renders it, but it only feeds the smoother and renders\n// a memoized child keyed on the smoother's output — so the expensive block\n// pipeline runs per reveal commit, not per chunk AND per commit.\nexport const StreamdownRender = memo<Options>(({ children, ...rest }) => {\n const { streamSmoothingPreset = 'balanced' } = useMarkdownContext();\n const escapedContent = useMarkdownContent(children || '');\n const smoothedContent = useSmoothStreamContent(\n typeof escapedContent === 'string' ? escapedContent : '',\n { preset: streamSmoothingPreset },\n );\n const markdownOptions = useStableValue(rest);\n\n return <StreamdownBlocks content={smoothedContent} markdownOptions={markdownOptions} />;\n});\n\nStreamdownRender.displayName = 'StreamdownRender';\n\nexport default StreamdownRender;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"StreamdownRender.mjs","names":[],"sources":["../../../src/Markdown/SyntaxMarkdown/StreamdownRender.tsx"],"sourcesContent":["'use client';\n\nimport { marked } from 'marked';\nimport {\n memo,\n Profiler,\n type ProfilerOnRenderCallback,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n} from 'react';\nimport { 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 { useStableValue } from '@/hooks/useStableValue';\nimport { useMarkdownContext } from '@/Markdown/components/MarkdownProvider';\nimport {\n rehypeStreamAnimated,\n type StreamAnimatedRuntime,\n} from '@/Markdown/plugins/rehypeStreamAnimated';\nimport { useStreamdownProfiler } from '@/Markdown/streamProfiler';\nimport { type StreamAnimationGranularity } from '@/Markdown/type';\nimport { getNow } from '@/utils/getNow';\nimport { isDeepEqual } from '@/utils/isDeepEqual';\n\nimport { CachedMarkdown } from './CachedMarkdown';\nimport { type BlockAnimationMeta, resolveBlockAnimationMeta } from './streamAnimationMeta';\nimport { STREAM_FADE_DURATION, styles } from './style';\nimport { countChars, useSmoothStreamContent } from './useSmoothStreamContent';\nimport { type BlockInfo, type BlockState, useStreamQueue } from './useStreamQueue';\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 isDeepEqual(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 <CachedMarkdown {...rest}>{children}</CachedMarkdown>;\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\ninterface BlockRuntime extends StreamAnimatedRuntime {\n charCount: number;\n charDelay?: number;\n rawLength: number;\n settled: boolean;\n}\n\ninterface BlockPluginsCacheEntry {\n base: PluggableList;\n granularity: StreamAnimationGranularity;\n value: PluggableList;\n}\n\ninterface UpdateBlockAnimationArgs {\n blocks: BlockInfo[];\n charDelay: number;\n getBlockState: (index: number) => BlockState;\n pluginsCache: Map<number, BlockPluginsCacheEntry>;\n renderNow: number;\n revealClock: { lastTs: number };\n runtimes: Map<number, BlockRuntime>;\n}\n\nconst MIN_STREAM_CHAR_PACE_MS = 2;\nconst MAX_REVEAL_GAP_MS = 160;\n\n// Runs in the render phase: extends each visible block's birth timeline in\n// place and resolves its animation meta in one pass. Mutations are\n// idempotent for a given block content/length, so discarded or StrictMode\n// double renders re-derive the same state.\nconst updateBlockAnimation = ({\n blocks,\n charDelay,\n getBlockState,\n pluginsCache,\n renderNow,\n revealClock,\n runtimes,\n}: UpdateBlockAnimationArgs): Map<number, BlockAnimationMeta> => {\n const blockAnimationMeta = new Map<number, BlockAnimationMeta>();\n const alive = new Set<number>();\n let revealedNewChars = false;\n\n for (const [index, block] of blocks.entries()) {\n alive.add(block.startOffset);\n\n // Queued blocks are not rendered. Defer birth assignment so that\n // when the block later transitions to animating/streaming, its\n // chars start fading from that moment instead of having already\n // \"aged out\" of the fade window.\n const state = getBlockState(index);\n if (state === 'queued') continue;\n\n let runtime = runtimes.get(block.startOffset);\n if (!runtime) {\n runtime = { births: [], charCount: 0, rawLength: -1, settled: false, styles: [] };\n runtimes.set(block.startOffset, runtime);\n }\n\n if (runtime.rawLength !== block.content.length) {\n runtime.charCount = countChars(block.content);\n runtime.rawLength = block.content.length;\n }\n\n const blockCharCount = runtime.charCount;\n const births = runtime.births;\n\n if (births.length > blockCharCount) {\n // Block content shrunk (stream restart or upstream rewrite).\n births.length = blockCharCount;\n runtime.styles.length = blockCharCount;\n }\n\n if (births.length < blockCharCount) {\n // Chain each new char monotonically after the previous one so fades\n // never race out of order. Cap how far the fade queue can run ahead\n // of renderNow to prevent stream-faster-than-fade producing seconds\n // of invisible backlog at the tail.\n //\n // The streaming tail paces its stagger from the observed reveal-commit\n // gap instead of the queue's fixed charDelay: a commit's chars are\n // spread to land exactly until the next commit arrives, so the flow\n // stays per-char continuous no matter how far apart the throttled\n // commits are.\n const newChars = blockCharCount - births.length;\n let pace = charDelay;\n let cap = renderNow + STREAM_FADE_DURATION;\n if (state === 'streaming') {\n revealedNewChars = true;\n const gapMs = Math.min(Math.max(renderNow - revealClock.lastTs, 16), MAX_REVEAL_GAP_MS);\n pace = Math.min(charDelay, Math.max(gapMs / newChars, MIN_STREAM_CHAR_PACE_MS));\n cap = renderNow + gapMs + STREAM_FADE_DURATION;\n }\n for (let i = births.length; i < blockCharCount; i++) {\n const prevBirth = i > 0 ? births[i - 1] : renderNow - pace;\n const chained = prevBirth + pace;\n births.push(Math.min(cap, Math.max(chained, renderNow)));\n }\n }\n\n let meta: BlockAnimationMeta;\n if (runtime.settled) {\n // Settled is monotone: a revealed block's births are frozen, so once\n // its last fade completed it stays settled until the runtime is\n // pruned by a stream restart.\n meta = { charDelay: runtime.charDelay ?? charDelay, settled: true };\n } else {\n const lastBirthTs = births.length > 0 ? (births.at(-1) ?? renderNow) : renderNow;\n meta = resolveBlockAnimationMeta({\n currentCharDelay: charDelay,\n fadeDuration: STREAM_FADE_DURATION,\n lastElapsedMs: renderNow - lastBirthTs,\n previousCharDelay: runtime.charDelay,\n state,\n });\n runtime.settled = meta.settled;\n }\n runtime.charDelay = meta.charDelay;\n\n blockAnimationMeta.set(block.startOffset, meta);\n }\n\n if (revealedNewChars) {\n revealClock.lastTs = renderNow;\n }\n\n for (const key of runtimes.keys()) {\n if (!alive.has(key)) {\n runtimes.delete(key);\n pluginsCache.delete(key);\n }\n }\n\n return blockAnimationMeta;\n};\n\ninterface StreamdownBlocksProps {\n content: string;\n markdownOptions: Omit<Options, 'children'>;\n}\n\nconst StreamdownBlocks = memo<StreamdownBlocksProps>(\n ({ content: smoothedContent, markdownOptions: rest }) => {\n const { streamAnimationGranularity = 'char' } = useMarkdownContext();\n const profiler = useStreamdownProfiler();\n const components = useMarkdownComponents();\n const baseRehypePlugins = useStablePlugins(useMarkdownRehypePlugins());\n const remarkPlugins = useStablePlugins(useMarkdownRemarkPlugins());\n const generatedId = useId();\n\n const processedContentResult = useMemo(() => {\n const start = profiler ? getNow() : 0;\n const value = remend(smoothedContent);\n\n return {\n durationMs: profiler ? getNow() - start : 0,\n value,\n };\n }, [profiler, smoothedContent]);\n const processedContent = processedContentResult.value;\n\n const blocksResult = useMemo(() => {\n const start = profiler ? getNow() : 0;\n const tokens = marked.lexer(processedContent);\n let offset = 0;\n\n const value = tokens.map((token) => {\n const block = { content: token.raw, startOffset: offset };\n offset += token.raw.length;\n return block;\n });\n\n return {\n durationMs: profiler ? getNow() - start : 0,\n value,\n };\n }, [processedContent, profiler]);\n const blocks: BlockInfo[] = blocksResult.value;\n\n const { getBlockState, charDelay } = useStreamQueue(blocks);\n const blockRuntimesRef = useRef<Map<number, BlockRuntime>>(new Map());\n const blockPluginsRef = useRef<Map<number, BlockPluginsCacheEntry>>(new Map());\n const revealClockRef = useRef<{ lastTs: number }>({ lastTs: 0 });\n\n const renderNow = getNow();\n\n const animationStart = profiler ? getNow() : 0;\n const blockAnimationMeta = updateBlockAnimation({\n blocks,\n charDelay,\n getBlockState,\n pluginsCache: blockPluginsRef.current,\n renderNow,\n revealClock: revealClockRef.current,\n runtimes: blockRuntimesRef.current,\n });\n const blockAnimationDurationMs = profiler ? getNow() - animationStart : 0;\n\n useEffect(() => {\n if (!profiler) return;\n\n profiler.recordCalculation({\n durationMs: processedContentResult.durationMs,\n name: 'content-normalize',\n textLength: processedContent.length,\n });\n }, [processedContent.length, processedContentResult.durationMs, profiler]);\n\n useEffect(() => {\n if (!profiler) return;\n\n profiler.recordCalculation({\n durationMs: blocksResult.durationMs,\n itemCount: blocks.length,\n name: 'block-lex',\n textLength: processedContent.length,\n });\n }, [blocks.length, blocksResult.durationMs, processedContent.length, profiler]);\n\n useEffect(() => {\n if (!profiler) return;\n\n profiler.recordCalculation({\n durationMs: blockAnimationDurationMs,\n itemCount: blocks.length,\n name: 'block-births',\n textLength: processedContent.length,\n });\n }, [blockAnimationDurationMs, blocks.length, processedContent.length, profiler]);\n\n const resolveBlockPlugins = (startOffset: number, settled: boolean): PluggableList => {\n if (settled) return baseRehypePlugins;\n\n const cache = blockPluginsRef.current;\n const entry = cache.get(startOffset);\n if (\n entry &&\n entry.base === baseRehypePlugins &&\n entry.granularity === streamAnimationGranularity\n ) {\n return entry.value;\n }\n\n const runtime = blockRuntimesRef.current.get(startOffset);\n const value: PluggableList = [\n ...baseRehypePlugins,\n [\n rehypeStreamAnimated,\n {\n fadeDuration: STREAM_FADE_DURATION,\n granularity: streamAnimationGranularity,\n runtime,\n },\n ],\n ];\n cache.set(startOffset, {\n base: baseRehypePlugins,\n granularity: streamAnimationGranularity,\n value,\n });\n return value;\n };\n\n const handleRootRender = useCallback<ProfilerOnRenderCallback>(\n (_, phase, actualDuration, baseDuration) => {\n profiler?.recordRootCommit({\n actualDuration,\n baseDuration,\n blockCount: blocks.length,\n phase,\n textLength: processedContent.length,\n });\n },\n [blocks.length, processedContent.length, profiler],\n );\n\n const handleBlockRender = useCallback<ProfilerOnRenderCallback>(\n (id, phase, actualDuration, baseDuration) => {\n if (!profiler) return;\n\n const [, indexText, offsetText] = id.split(':');\n const blockIndex = Number(indexText);\n\n if (!Number.isFinite(blockIndex)) return;\n\n const block = blocks[blockIndex];\n if (!block) return;\n\n profiler.recordBlockCommit({\n actualDuration,\n baseDuration,\n blockChars: countChars(block.content),\n blockIndex,\n blockKey: offsetText ?? String(block.startOffset),\n phase,\n state: getBlockState(blockIndex),\n });\n },\n [blocks, getBlockState, profiler],\n );\n\n const content = (\n <div className={styles.animated}>\n {blocks.map((block, index) => {\n const animationMeta = blockAnimationMeta.get(block.startOffset);\n if (!animationMeta) return null;\n\n const plugins = resolveBlockPlugins(block.startOffset, animationMeta.settled);\n const key = `${generatedId}-${block.startOffset}`;\n\n if (!profiler) {\n return (\n <StreamdownBlock\n {...rest}\n components={components}\n key={key}\n rehypePlugins={plugins}\n remarkPlugins={remarkPlugins}\n >\n {block.content}\n </StreamdownBlock>\n );\n }\n\n return (\n <Profiler\n id={`streamdown-block:${index}:${block.startOffset}`}\n key={key}\n onRender={handleBlockRender}\n >\n <StreamdownBlock\n {...rest}\n components={components}\n rehypePlugins={plugins}\n remarkPlugins={remarkPlugins}\n >\n {block.content}\n </StreamdownBlock>\n </Profiler>\n );\n })}\n </div>\n );\n\n if (!profiler) return content;\n\n return (\n <Profiler id={'streamdown-root'} onRender={handleRootRender}>\n {content}\n </Profiler>\n );\n },\n);\n\nStreamdownBlocks.displayName = 'StreamdownBlocks';\n\n// The outer component absorbs the upstream per-chunk prop churn: every\n// streamed chunk re-renders it, but it only feeds the smoother and renders\n// a memoized child keyed on the smoother's output — so the expensive block\n// pipeline runs per reveal commit, not per chunk AND per commit.\nexport const StreamdownRender = memo<Options>(({ children, ...rest }) => {\n const { streamSmoothingPreset = 'balanced' } = useMarkdownContext();\n const escapedContent = useMarkdownContent(children || '');\n const smoothedContent = useSmoothStreamContent(\n typeof escapedContent === 'string' ? escapedContent : '',\n { preset: streamSmoothingPreset },\n );\n const markdownOptions = useStableValue(rest);\n\n return <StreamdownBlocks content={smoothedContent} markdownOptions={markdownOptions} />;\n});\n\nStreamdownRender.displayName = 'StreamdownRender';\n\nexport default StreamdownRender;\n"],"mappings":"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"}
|
|
@@ -279,8 +279,8 @@ const useSmoothStreamContent = (content, { enabled = true, preset = "balanced" }
|
|
|
279
279
|
const instantCps = deltaChars * 1e3 / deltaMs;
|
|
280
280
|
const normalizedInstantCps = clamp(instantCps, config.minCps, config.maxFlushCps * 2);
|
|
281
281
|
const chunkEmaAlpha = .35;
|
|
282
|
-
chunkSizeEmaRef.current = chunkSizeEmaRef.current *
|
|
283
|
-
arrivalCpsEmaRef.current = arrivalCpsEmaRef.current *
|
|
282
|
+
chunkSizeEmaRef.current = chunkSizeEmaRef.current * .65 + appendedCount * chunkEmaAlpha;
|
|
283
|
+
arrivalCpsEmaRef.current = arrivalCpsEmaRef.current * .65 + normalizedInstantCps * chunkEmaAlpha;
|
|
284
284
|
const clampedCps = clamp(instantCps, config.minCps, config.maxActiveCps);
|
|
285
285
|
emaCpsRef.current = emaCpsRef.current * (1 - config.emaAlpha) + clampedCps * config.emaAlpha;
|
|
286
286
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSmoothStreamContent.mjs","names":["now"],"sources":["../../../src/Markdown/SyntaxMarkdown/useSmoothStreamContent.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { useStreamdownProfiler } from '@/Markdown/streamProfiler';\nimport { type StreamSmoothingPreset } from '@/Markdown/type';\nimport { getNow } from '@/utils/getNow';\n\nimport { findOpenFenceLanguage } from './fenceState';\n\ninterface StreamSmoothingPresetConfig {\n activeInputWindowMs: number;\n /**\n * Code-fence languages whose contents bypass smoothing entirely. While\n * the input ends inside an open fence in this set, every chunk is\n * `syncImmediate`-d to the display so downstream consumers (the\n * HtmlPreview iframe in particular) see partial HTML at the demo's\n * actual production rate instead of waiting on the smoother's\n * ~maxFlushCps budget. As soon as the fence closes — or another\n * non-bypass fence opens — smoothing resumes for the rest of the\n * stream. Default `['html']` covers the artifact case that motivated\n * this; tune via preset if you need to bypass `mermaid`, `svg`, etc.\n */\n bypassFencedLanguages: readonly string[];\n defaultCps: number;\n emaAlpha: number;\n flushCps: number;\n largeAppendChars: number;\n maxActiveCps: number;\n maxCps: number;\n maxFlushCps: number;\n minCommitIntervalMs: number;\n minCps: number;\n settleAfterMs: number;\n settleDrainMaxMs: number;\n settleDrainMinMs: number;\n targetBufferMs: number;\n}\n\nconst DEFAULT_BYPASS_LANGUAGES = ['html'] as const;\n\nconst PRESET_CONFIG: Record<StreamSmoothingPreset, StreamSmoothingPresetConfig> = {\n balanced: {\n activeInputWindowMs: 220,\n bypassFencedLanguages: DEFAULT_BYPASS_LANGUAGES,\n defaultCps: 38,\n emaAlpha: 0.2,\n flushCps: 120,\n largeAppendChars: 120,\n maxActiveCps: 132,\n maxCps: 72,\n maxFlushCps: 280,\n minCommitIntervalMs: 48,\n minCps: 18,\n settleAfterMs: 360,\n settleDrainMaxMs: 520,\n settleDrainMinMs: 180,\n targetBufferMs: 120,\n },\n realtime: {\n activeInputWindowMs: 140,\n bypassFencedLanguages: DEFAULT_BYPASS_LANGUAGES,\n defaultCps: 50,\n emaAlpha: 0.3,\n flushCps: 170,\n largeAppendChars: 180,\n maxActiveCps: 180,\n maxCps: 96,\n maxFlushCps: 360,\n minCommitIntervalMs: 32,\n minCps: 24,\n settleAfterMs: 260,\n settleDrainMaxMs: 360,\n settleDrainMinMs: 140,\n targetBufferMs: 40,\n },\n silky: {\n activeInputWindowMs: 320,\n bypassFencedLanguages: DEFAULT_BYPASS_LANGUAGES,\n defaultCps: 28,\n emaAlpha: 0.14,\n flushCps: 96,\n largeAppendChars: 100,\n maxActiveCps: 102,\n maxCps: 56,\n maxFlushCps: 220,\n minCommitIntervalMs: 56,\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\n// Every reveal commit re-parses and re-wraps the entire trailing block, so\n// the per-commit cost grows linearly with how far the content is from the\n// last block boundary. Widen the commit interval as that distance grows —\n// long paragraphs/code fences keep total work bounded while short blocks\n// stay at the preset's snappy interval. Per-char animation-delay stagger\n// hides the lower commit rate.\nconst MAX_COMMIT_INTERVAL_MS = 96;\nconst COMMIT_INTERVAL_TAIL_SCALE_UNITS = 256;\n\nconst findTailUnits = (content: string): number => {\n const boundary = content.lastIndexOf('\\n\\n');\n return boundary === -1 ? content.length : content.length - boundary - 2;\n};\n\nexport const countChars = (text: string): number => {\n let count = 0;\n for (const _char of text) count += 1;\n return count;\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 profiler = useStreamdownProfiler();\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 tailUnitsRef = useRef(findTailUnits(content));\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 const wakeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const clearWakeTimer = useCallback(() => {\n if (wakeTimerRef.current !== null) {\n clearTimeout(wakeTimerRef.current);\n wakeTimerRef.current = null;\n }\n }, []);\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 stopScheduling = useCallback(() => {\n stopFrameLoop();\n clearWakeTimer();\n }, [clearWakeTimer, stopFrameLoop]);\n\n const startFrameLoopRef = useRef<() => void>(() => {});\n\n const scheduleFrameWake = useCallback(\n (delayMs: number) => {\n clearWakeTimer();\n\n wakeTimerRef.current = setTimeout(\n () => {\n wakeTimerRef.current = null;\n startFrameLoopRef.current();\n },\n Math.max(1, Math.ceil(delayMs)),\n );\n },\n [clearWakeTimer],\n );\n\n const syncImmediate = useCallback(\n (nextContent: string) => {\n stopScheduling();\n\n const chars = [...nextContent];\n const now = getNow();\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 tailUnitsRef.current = findTailUnits(nextContent);\n lastInputTsRef.current = now;\n lastInputCountRef.current = chars.length;\n },\n [config.defaultCps, stopScheduling],\n );\n\n const startFrameLoop = useCallback(() => {\n clearWakeTimer();\n if (rafRef.current !== null) return;\n\n const tick = (ts: number) => {\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 if (lastFrameTsRef.current === null) {\n lastFrameTsRef.current = ts;\n rafRef.current = requestAnimationFrame(tick);\n return;\n }\n\n // Reveal commits are throttled below the display refresh rate: each\n // commit re-renders the tail block and re-runs remend + lexing, so\n // committing at 60-120fps burns CPU without visible benefit — the\n // per-char stagger inside a commit is carried by animation-delay.\n const commitIntervalMs = Math.min(\n MAX_COMMIT_INTERVAL_MS,\n config.minCommitIntervalMs * (1 + tailUnitsRef.current / COMMIT_INTERVAL_TAIL_SCALE_UNITS),\n );\n const frameIntervalMs = Math.max(0, ts - lastFrameTsRef.current);\n if (frameIntervalMs < commitIntervalMs) {\n rafRef.current = requestAnimationFrame(tick);\n return;\n }\n\n const frameStart = getNow();\n const dtSeconds = Math.max(0.001, Math.min(frameIntervalMs / 1000, 0.12));\n lastFrameTsRef.current = ts;\n\n const now = frameStart;\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 stopFrameLoop();\n scheduleFrameWake(config.activeInputWindowMs - idleMs);\n\n profiler?.recordAnimationFrame({\n backlog,\n durationMs: getNow() - frameStart,\n frameIntervalMs,\n inputActive,\n revealChars: 0,\n settling,\n });\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 profiler?.recordAnimationFrame({\n backlog,\n durationMs: getNow() - frameStart,\n frameIntervalMs,\n inputActive,\n revealChars: segment ? revealChars : backlog,\n settling,\n });\n\n rafRef.current = requestAnimationFrame(tick);\n };\n\n rafRef.current = requestAnimationFrame(tick);\n }, [\n clearWakeTimer,\n config.activeInputWindowMs,\n config.flushCps,\n config.maxActiveCps,\n config.maxCps,\n config.maxFlushCps,\n config.minCommitIntervalMs,\n config.minCps,\n config.settleAfterMs,\n config.settleDrainMaxMs,\n config.settleDrainMinMs,\n config.targetBufferMs,\n scheduleFrameWake,\n stopFrameLoop,\n ]);\n startFrameLoopRef.current = startFrameLoop;\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 = getNow();\n const appendOnly = content.startsWith(prevTargetContent);\n\n if (!appendOnly) {\n syncImmediate(content);\n return;\n }\n\n // Bypass smoothing entirely while the input ends inside an open\n // fence whose language is opted out — see the preset config.\n // Without this, a 5 KB inline `<style>` block can keep `</style>`\n // (and therefore the HtmlPreview head-close trigger) trapped in the\n // smoother's buffer for tens of seconds, leaving the iframe pinned\n // on the loading placeholder while the artifact \"looks stuck\".\n if (config.bypassFencedLanguages.length > 0) {\n const openLang = findOpenFenceLanguage(content);\n if (openLang !== null && config.bypassFencedLanguages.includes(openLang)) {\n syncImmediate(content);\n return;\n }\n }\n\n const appended = content.slice(prevTargetContent.length);\n const appendedChars = [...appended];\n const appendedCount = appendedChars.length;\n\n profiler?.recordInputAppend({\n appendedChars: appendedCount,\n contentLength: countChars(content),\n });\n\n if (appendedCount > config.largeAppendChars) {\n syncImmediate(content);\n return;\n }\n\n targetContentRef.current = content;\n targetCharsRef.current.push(...appendedChars);\n targetCountRef.current += appendedCount;\n\n tailUnitsRef.current = findTailUnits(content);\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.bypassFencedLanguages,\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 profiler,\n ]);\n\n useEffect(() => {\n return () => {\n stopScheduling();\n };\n }, [stopScheduling]);\n\n return displayedContent;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useSmoothStreamContent.mjs","names":["now"],"sources":["../../../src/Markdown/SyntaxMarkdown/useSmoothStreamContent.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { useStreamdownProfiler } from '@/Markdown/streamProfiler';\nimport { type StreamSmoothingPreset } from '@/Markdown/type';\nimport { getNow } from '@/utils/getNow';\n\nimport { findOpenFenceLanguage } from './fenceState';\n\ninterface StreamSmoothingPresetConfig {\n activeInputWindowMs: number;\n /**\n * Code-fence languages whose contents bypass smoothing entirely. While\n * the input ends inside an open fence in this set, every chunk is\n * `syncImmediate`-d to the display so downstream consumers (the\n * HtmlPreview iframe in particular) see partial HTML at the demo's\n * actual production rate instead of waiting on the smoother's\n * ~maxFlushCps budget. As soon as the fence closes — or another\n * non-bypass fence opens — smoothing resumes for the rest of the\n * stream. Default `['html']` covers the artifact case that motivated\n * this; tune via preset if you need to bypass `mermaid`, `svg`, etc.\n */\n bypassFencedLanguages: readonly string[];\n defaultCps: number;\n emaAlpha: number;\n flushCps: number;\n largeAppendChars: number;\n maxActiveCps: number;\n maxCps: number;\n maxFlushCps: number;\n minCommitIntervalMs: number;\n minCps: number;\n settleAfterMs: number;\n settleDrainMaxMs: number;\n settleDrainMinMs: number;\n targetBufferMs: number;\n}\n\nconst DEFAULT_BYPASS_LANGUAGES = ['html'] as const;\n\nconst PRESET_CONFIG: Record<StreamSmoothingPreset, StreamSmoothingPresetConfig> = {\n balanced: {\n activeInputWindowMs: 220,\n bypassFencedLanguages: DEFAULT_BYPASS_LANGUAGES,\n defaultCps: 38,\n emaAlpha: 0.2,\n flushCps: 120,\n largeAppendChars: 120,\n maxActiveCps: 132,\n maxCps: 72,\n maxFlushCps: 280,\n minCommitIntervalMs: 48,\n minCps: 18,\n settleAfterMs: 360,\n settleDrainMaxMs: 520,\n settleDrainMinMs: 180,\n targetBufferMs: 120,\n },\n realtime: {\n activeInputWindowMs: 140,\n bypassFencedLanguages: DEFAULT_BYPASS_LANGUAGES,\n defaultCps: 50,\n emaAlpha: 0.3,\n flushCps: 170,\n largeAppendChars: 180,\n maxActiveCps: 180,\n maxCps: 96,\n maxFlushCps: 360,\n minCommitIntervalMs: 32,\n minCps: 24,\n settleAfterMs: 260,\n settleDrainMaxMs: 360,\n settleDrainMinMs: 140,\n targetBufferMs: 40,\n },\n silky: {\n activeInputWindowMs: 320,\n bypassFencedLanguages: DEFAULT_BYPASS_LANGUAGES,\n defaultCps: 28,\n emaAlpha: 0.14,\n flushCps: 96,\n largeAppendChars: 100,\n maxActiveCps: 102,\n maxCps: 56,\n maxFlushCps: 220,\n minCommitIntervalMs: 56,\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\n// Every reveal commit re-parses and re-wraps the entire trailing block, so\n// the per-commit cost grows linearly with how far the content is from the\n// last block boundary. Widen the commit interval as that distance grows —\n// long paragraphs/code fences keep total work bounded while short blocks\n// stay at the preset's snappy interval. Per-char animation-delay stagger\n// hides the lower commit rate.\nconst MAX_COMMIT_INTERVAL_MS = 96;\nconst COMMIT_INTERVAL_TAIL_SCALE_UNITS = 256;\n\nconst findTailUnits = (content: string): number => {\n const boundary = content.lastIndexOf('\\n\\n');\n return boundary === -1 ? content.length : content.length - boundary - 2;\n};\n\nexport const countChars = (text: string): number => {\n let count = 0;\n for (const _char of text) count += 1;\n return count;\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 profiler = useStreamdownProfiler();\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 tailUnitsRef = useRef(findTailUnits(content));\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 const wakeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const clearWakeTimer = useCallback(() => {\n if (wakeTimerRef.current !== null) {\n clearTimeout(wakeTimerRef.current);\n wakeTimerRef.current = null;\n }\n }, []);\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 stopScheduling = useCallback(() => {\n stopFrameLoop();\n clearWakeTimer();\n }, [clearWakeTimer, stopFrameLoop]);\n\n const startFrameLoopRef = useRef<() => void>(() => {});\n\n const scheduleFrameWake = useCallback(\n (delayMs: number) => {\n clearWakeTimer();\n\n wakeTimerRef.current = setTimeout(\n () => {\n wakeTimerRef.current = null;\n startFrameLoopRef.current();\n },\n Math.max(1, Math.ceil(delayMs)),\n );\n },\n [clearWakeTimer],\n );\n\n const syncImmediate = useCallback(\n (nextContent: string) => {\n stopScheduling();\n\n const chars = [...nextContent];\n const now = getNow();\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 tailUnitsRef.current = findTailUnits(nextContent);\n lastInputTsRef.current = now;\n lastInputCountRef.current = chars.length;\n },\n [config.defaultCps, stopScheduling],\n );\n\n const startFrameLoop = useCallback(() => {\n clearWakeTimer();\n if (rafRef.current !== null) return;\n\n const tick = (ts: number) => {\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 if (lastFrameTsRef.current === null) {\n lastFrameTsRef.current = ts;\n rafRef.current = requestAnimationFrame(tick);\n return;\n }\n\n // Reveal commits are throttled below the display refresh rate: each\n // commit re-renders the tail block and re-runs remend + lexing, so\n // committing at 60-120fps burns CPU without visible benefit — the\n // per-char stagger inside a commit is carried by animation-delay.\n const commitIntervalMs = Math.min(\n MAX_COMMIT_INTERVAL_MS,\n config.minCommitIntervalMs * (1 + tailUnitsRef.current / COMMIT_INTERVAL_TAIL_SCALE_UNITS),\n );\n const frameIntervalMs = Math.max(0, ts - lastFrameTsRef.current);\n if (frameIntervalMs < commitIntervalMs) {\n rafRef.current = requestAnimationFrame(tick);\n return;\n }\n\n const frameStart = getNow();\n const dtSeconds = Math.max(0.001, Math.min(frameIntervalMs / 1000, 0.12));\n lastFrameTsRef.current = ts;\n\n const now = frameStart;\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 stopFrameLoop();\n scheduleFrameWake(config.activeInputWindowMs - idleMs);\n\n profiler?.recordAnimationFrame({\n backlog,\n durationMs: getNow() - frameStart,\n frameIntervalMs,\n inputActive,\n revealChars: 0,\n settling,\n });\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 profiler?.recordAnimationFrame({\n backlog,\n durationMs: getNow() - frameStart,\n frameIntervalMs,\n inputActive,\n revealChars: segment ? revealChars : backlog,\n settling,\n });\n\n rafRef.current = requestAnimationFrame(tick);\n };\n\n rafRef.current = requestAnimationFrame(tick);\n }, [\n clearWakeTimer,\n config.activeInputWindowMs,\n config.flushCps,\n config.maxActiveCps,\n config.maxCps,\n config.maxFlushCps,\n config.minCommitIntervalMs,\n config.minCps,\n config.settleAfterMs,\n config.settleDrainMaxMs,\n config.settleDrainMinMs,\n config.targetBufferMs,\n scheduleFrameWake,\n stopFrameLoop,\n ]);\n startFrameLoopRef.current = startFrameLoop;\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 = getNow();\n const appendOnly = content.startsWith(prevTargetContent);\n\n if (!appendOnly) {\n syncImmediate(content);\n return;\n }\n\n // Bypass smoothing entirely while the input ends inside an open\n // fence whose language is opted out — see the preset config.\n // Without this, a 5 KB inline `<style>` block can keep `</style>`\n // (and therefore the HtmlPreview head-close trigger) trapped in the\n // smoother's buffer for tens of seconds, leaving the iframe pinned\n // on the loading placeholder while the artifact \"looks stuck\".\n if (config.bypassFencedLanguages.length > 0) {\n const openLang = findOpenFenceLanguage(content);\n if (openLang !== null && config.bypassFencedLanguages.includes(openLang)) {\n syncImmediate(content);\n return;\n }\n }\n\n const appended = content.slice(prevTargetContent.length);\n const appendedChars = [...appended];\n const appendedCount = appendedChars.length;\n\n profiler?.recordInputAppend({\n appendedChars: appendedCount,\n contentLength: countChars(content),\n });\n\n if (appendedCount > config.largeAppendChars) {\n syncImmediate(content);\n return;\n }\n\n targetContentRef.current = content;\n targetCharsRef.current.push(...appendedChars);\n targetCountRef.current += appendedCount;\n\n tailUnitsRef.current = findTailUnits(content);\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.bypassFencedLanguages,\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 profiler,\n ]);\n\n useEffect(() => {\n return () => {\n stopScheduling();\n };\n }, [stopScheduling]);\n\n return displayedContent;\n};\n"],"mappings":"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"}
|
|
@@ -16,13 +16,14 @@ const RawModalStackItem = memo(({ component: Component, id, onClose, onUpdate, o
|
|
|
16
16
|
}), [close, setCanDismissByClickOutside]);
|
|
17
17
|
const openKey = options?.openKey ?? "open";
|
|
18
18
|
const onCloseKey = options?.onCloseKey ?? "onClose";
|
|
19
|
+
const injectedProps = {
|
|
20
|
+
...props,
|
|
21
|
+
[onCloseKey]: close,
|
|
22
|
+
[openKey]: open
|
|
23
|
+
};
|
|
19
24
|
return /* @__PURE__ */ jsx(ModalProvider, {
|
|
20
25
|
value: contextValue,
|
|
21
|
-
children: /* @__PURE__ */ jsx(Component, {
|
|
22
|
-
...props,
|
|
23
|
-
[onCloseKey]: close,
|
|
24
|
-
[openKey]: open
|
|
25
|
-
})
|
|
26
|
+
children: /* @__PURE__ */ jsx(Component, { ...injectedProps })
|
|
26
27
|
});
|
|
27
28
|
});
|
|
28
29
|
RawModalStackItem.displayName = "RawModalStackItem";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RawModalStackItem.mjs","names":[],"sources":["../../src/Modal/RawModalStackItem.tsx"],"sourcesContent":["'use client';\n\nimport type { ComponentType } from 'react';\nimport { memo, useMemo } from 'react';\n\nimport { useEventCallback } from '@/hooks/useEventCallback';\n\nimport { ModalProvider } from './ModalProvider';\nimport type { ModalContextValue, RawModalOptions } from './type';\n\nexport type RawModalStackItemProps = {\n component: ComponentType<any>;\n id: string;\n onClose: (id: string) => void;\n onUpdate: (id: string, nextProps: Record<string, unknown>) => void;\n open: boolean;\n options?: RawModalOptions<PropertyKey, PropertyKey>;\n props: Record<string, unknown>;\n};\n\nexport const RawModalStackItem = memo(\n ({\n component: Component,\n id,\n onClose,\n onUpdate,\n open,\n options,\n props,\n }: RawModalStackItemProps) => {\n const stableOnClose = useEventCallback(onClose);\n const close = useEventCallback(() => stableOnClose(id));\n\n const setCanDismissByClickOutside = useEventCallback((value: boolean) => {\n onUpdate(id, { mask: { closable: value } });\n });\n const contextValue: ModalContextValue = useMemo(\n () => ({ close, setCanDismissByClickOutside }),\n [close, setCanDismissByClickOutside],\n );\n const openKey = options?.openKey ?? 'open';\n const onCloseKey = options?.onCloseKey ?? 'onClose';\n const injectedProps = {\n ...props,\n [onCloseKey]: close,\n [openKey]: open,\n };\n\n return (\n <ModalProvider value={contextValue}>\n <Component {...injectedProps} />\n </ModalProvider>\n );\n },\n);\n\nRawModalStackItem.displayName = 'RawModalStackItem';\n"],"mappings":";;;;;;AAoBA,MAAa,oBAAoB,MAC9B,EACC,WAAW,WACX,IACA,SACA,UACA,MACA,SACA,YAC4B;CAC5B,MAAM,gBAAgB,iBAAiB,OAAO;CAC9C,MAAM,QAAQ,uBAAuB,cAAc,EAAE,CAAC;CAEtD,MAAM,8BAA8B,kBAAkB,UAAmB;EACvE,SAAS,IAAI,EAAE,MAAM,EAAE,UAAU,MAAM,EAAE,CAAC;CAC5C,CAAC;CACD,MAAM,eAAkC,eAC/B;EAAE;EAAO;CAA4B,IAC5C,CAAC,OAAO,2BAA2B,CACrC;CACA,MAAM,UAAU,SAAS,WAAW;CACpC,MAAM,aAAa,SAAS,cAAc;
|
|
1
|
+
{"version":3,"file":"RawModalStackItem.mjs","names":[],"sources":["../../src/Modal/RawModalStackItem.tsx"],"sourcesContent":["'use client';\n\nimport type { ComponentType } from 'react';\nimport { memo, useMemo } from 'react';\n\nimport { useEventCallback } from '@/hooks/useEventCallback';\n\nimport { ModalProvider } from './ModalProvider';\nimport type { ModalContextValue, RawModalOptions } from './type';\n\nexport type RawModalStackItemProps = {\n component: ComponentType<any>;\n id: string;\n onClose: (id: string) => void;\n onUpdate: (id: string, nextProps: Record<string, unknown>) => void;\n open: boolean;\n options?: RawModalOptions<PropertyKey, PropertyKey>;\n props: Record<string, unknown>;\n};\n\nexport const RawModalStackItem = memo(\n ({\n component: Component,\n id,\n onClose,\n onUpdate,\n open,\n options,\n props,\n }: RawModalStackItemProps) => {\n const stableOnClose = useEventCallback(onClose);\n const close = useEventCallback(() => stableOnClose(id));\n\n const setCanDismissByClickOutside = useEventCallback((value: boolean) => {\n onUpdate(id, { mask: { closable: value } });\n });\n const contextValue: ModalContextValue = useMemo(\n () => ({ close, setCanDismissByClickOutside }),\n [close, setCanDismissByClickOutside],\n );\n const openKey = options?.openKey ?? 'open';\n const onCloseKey = options?.onCloseKey ?? 'onClose';\n const injectedProps = {\n ...props,\n [onCloseKey]: close,\n [openKey]: open,\n };\n\n return (\n <ModalProvider value={contextValue}>\n <Component {...injectedProps} />\n </ModalProvider>\n );\n },\n);\n\nRawModalStackItem.displayName = 'RawModalStackItem';\n"],"mappings":";;;;;;AAoBA,MAAa,oBAAoB,MAC9B,EACC,WAAW,WACX,IACA,SACA,UACA,MACA,SACA,YAC4B;CAC5B,MAAM,gBAAgB,iBAAiB,OAAO;CAC9C,MAAM,QAAQ,uBAAuB,cAAc,EAAE,CAAC;CAEtD,MAAM,8BAA8B,kBAAkB,UAAmB;EACvE,SAAS,IAAI,EAAE,MAAM,EAAE,UAAU,MAAM,EAAE,CAAC;CAC5C,CAAC;CACD,MAAM,eAAkC,eAC/B;EAAE;EAAO;CAA4B,IAC5C,CAAC,OAAO,2BAA2B,CACrC;CACA,MAAM,UAAU,SAAS,WAAW;CACpC,MAAM,aAAa,SAAS,cAAc;CAC1C,MAAM,gBAAgB;EACpB,GAAG;GACF,aAAa;GACb,UAAU;CACb;CAEA,OACE,oBAAC,eAAD;EAAe,OAAO;EACpB,UAAA,oBAAC,WAAD,EAAW,GAAI,cAAgB,CAAA;CAClB,CAAA;AAEnB,CACF;AAEA,kBAAkB,cAAc"}
|
package/es/Modal/imperative.mjs
CHANGED
|
@@ -24,7 +24,8 @@ const getSnapshot = () => modalStack;
|
|
|
24
24
|
const getServerSnapshot = () => EMPTY_STACK;
|
|
25
25
|
const ModalPortal = ({ children, root }) => {
|
|
26
26
|
const appElement = useAppElement();
|
|
27
|
-
|
|
27
|
+
const container = root ?? appElement ?? document.body;
|
|
28
|
+
return createPortal(children, container);
|
|
28
29
|
};
|
|
29
30
|
const updateModal = (id, nextProps) => {
|
|
30
31
|
let changed = false;
|
|
@@ -135,7 +136,8 @@ const ModalHost = ({ root }) => {
|
|
|
135
136
|
const isClient = useIsClient();
|
|
136
137
|
useEffect(() => {
|
|
137
138
|
if (!isClient) return;
|
|
138
|
-
|
|
139
|
+
const scope = root ?? document.body;
|
|
140
|
+
return registerDevSingleton("ModalHost", scope);
|
|
139
141
|
}, [isClient, root]);
|
|
140
142
|
if (!isClient) return null;
|
|
141
143
|
if (stack.length === 0) return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"imperative.mjs","names":[],"sources":["../../src/Modal/imperative.tsx"],"sourcesContent":["'use client';\n\nimport type { ReactNode } from 'react';\nimport { memo, useEffect, useSyncExternalStore } from 'react';\nimport { createPortal } from 'react-dom';\n\nimport { useIsClient } from '@/hooks/useIsClient';\nimport { useAppElement } from '@/ThemeProvider';\nimport { registerDevSingleton } from '@/utils/devSingleton';\n\nimport { ModalStackItem } from './ModalStackItem';\nimport { RawModalStackItem } from './RawModalStackItem';\nimport type {\n ImperativeModalProps,\n ModalInstance,\n RawModalComponent,\n RawModalComponentProps,\n RawModalInstance,\n RawModalKeyOptions,\n RawModalOptions,\n} from './type';\n\ntype ModalStackItemBase = {\n id: string;\n};\n\ntype ModalStackItemModal = ModalStackItemBase & {\n kind: 'modal';\n props: ImperativeModalProps;\n};\n\ntype ModalStackItemRaw = ModalStackItemBase & {\n component: RawModalComponent;\n kind: 'raw';\n open: boolean;\n options?: RawModalOptions<PropertyKey, PropertyKey>;\n props: Record<string, unknown>;\n};\n\ntype TModalStackItem = ModalStackItemModal | ModalStackItemRaw;\n\ntype ModalStackProps = {\n stack: TModalStackItem[];\n};\n\nexport type ModalHostProps = {\n root?: HTMLElement | ShadowRoot | null;\n};\n\nlet modalStack: TModalStackItem[] = [];\nlet modalSeed = 0;\nconst listeners = new Set<() => void>();\nconst rawDestroyTimers = new Map<string, number>();\n\nconst notify = () => {\n listeners.forEach((listener) => listener());\n};\n\nconst subscribe = (listener: () => void) => {\n listeners.add(listener);\n return () => listeners.delete(listener);\n};\n\nconst EMPTY_STACK: TModalStackItem[] = [];\nconst getSnapshot = () => modalStack;\nconst getServerSnapshot = () => EMPTY_STACK;\n\nconst ModalPortal = ({\n children,\n root,\n}: {\n children: ReactNode;\n root?: HTMLElement | ShadowRoot | null;\n}) => {\n const appElement = useAppElement();\n const container = root ?? appElement ?? document.body;\n\n return createPortal(children, container);\n};\n\nconst updateModal = (id: string, nextProps: Partial<ImperativeModalProps>) => {\n let changed = false;\n modalStack = modalStack.map((item) => {\n if (item.id !== id) return item;\n if (item.kind !== 'modal') return item;\n changed = true;\n return {\n ...item,\n props: { ...item.props, ...nextProps },\n };\n });\n\n if (changed) notify();\n};\n\nconst updateRawProps = (id: string, nextProps: Record<string, unknown>) => {\n let changed = false;\n modalStack = modalStack.map((item) => {\n if (item.id !== id) return item;\n if (item.kind !== 'raw') return item;\n changed = true;\n return {\n ...item,\n props: { ...item.props, ...nextProps },\n };\n });\n\n if (changed) notify();\n};\n\nconst setRawOpen = (id: string, open: boolean) => {\n let changed = false;\n modalStack = modalStack.map((item) => {\n if (item.id !== id) return item;\n if (item.kind !== 'raw') return item;\n if (item.open === open) return item;\n changed = true;\n return { ...item, open };\n });\n\n if (open) {\n const timer = rawDestroyTimers.get(id);\n if (timer) {\n clearTimeout(timer);\n rawDestroyTimers.delete(id);\n }\n }\n\n if (changed) notify();\n};\n\nconst closeModal = (id: string) => {\n const target = modalStack.find((item) => item.id === id);\n if (!target) return;\n\n if (target.kind === 'modal') {\n updateModal(id, { open: false });\n return;\n }\n\n setRawOpen(id, false);\n\n const shouldDestroy = target.options?.destroyOnClose ?? true;\n if (!shouldDestroy) return;\n\n const delay = target.options?.destroyDelay ?? 200;\n const existing = rawDestroyTimers.get(id);\n if (existing) clearTimeout(existing);\n const timer = window.setTimeout(() => {\n rawDestroyTimers.delete(id);\n\n destroyModal(id);\n }, delay);\n rawDestroyTimers.set(id, timer);\n};\n\nconst destroyModal = (id: string) => {\n const timer = rawDestroyTimers.get(id);\n if (timer) {\n clearTimeout(timer);\n rawDestroyTimers.delete(id);\n }\n const nextStack = modalStack.filter((item) => item.id !== id);\n if (nextStack.length === modalStack.length) return;\n modalStack = nextStack;\n notify();\n};\n\nconst ModalStack = memo(({ stack }: ModalStackProps) => {\n const isClient = useIsClient();\n if (!isClient) return null;\n return stack.map((item) => {\n if (item.kind === 'modal') {\n return (\n <ModalStackItem\n id={item.id}\n key={item.id}\n props={item.props}\n onClose={closeModal}\n onDestroy={destroyModal}\n onUpdate={updateModal}\n />\n );\n }\n\n return (\n <RawModalStackItem\n component={item.component}\n id={item.id}\n key={item.id}\n open={item.open}\n options={item.options}\n props={item.props}\n onClose={closeModal}\n onUpdate={updateRawProps}\n />\n );\n });\n});\n\nModalStack.displayName = 'ModalStack';\n\nexport const ModalHost = ({ root }: ModalHostProps) => {\n const stack = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n const isClient = useIsClient();\n\n useEffect(() => {\n if (!isClient) return;\n // Enforce singleton per portal root (dev-only).\n const scope = root ?? document.body;\n return registerDevSingleton('ModalHost', scope);\n }, [isClient, root]);\n\n if (!isClient) return null;\n if (stack.length === 0) return null;\n\n return (\n <ModalPortal root={root}>\n <ModalStack stack={stack} />\n </ModalPortal>\n );\n};\n\nexport const createModal = (props: ImperativeModalProps): ModalInstance => {\n const id = `modal-${Date.now()}-${modalSeed++}`;\n modalStack = [\n ...modalStack,\n { id, kind: 'modal', props: { ...props, open: props.open ?? true } },\n ];\n notify();\n\n return {\n close: () => closeModal(id),\n destroy: () => destroyModal(id),\n setCanDismissByClickOutside: (value) => updateModal(id, { mask: { closable: value } }),\n update: (nextProps) => updateModal(id, nextProps),\n };\n};\n\nexport function createRawModal<P extends RawModalComponentProps>(\n component: RawModalComponent<P>,\n props: Omit<P, 'open' | 'onClose'>,\n options?: RawModalOptions,\n): RawModalInstance<P>;\n\nexport function createRawModal<P, OpenKey extends keyof P, CloseKey extends keyof P>(\n component: RawModalComponent<P>,\n props: Omit<P, OpenKey | CloseKey>,\n options: RawModalKeyOptions<OpenKey, CloseKey>,\n): RawModalInstance<P, OpenKey, CloseKey>;\n\nexport function createRawModal<P, OpenKey extends keyof P, CloseKey extends keyof P>(\n component: RawModalComponent<P>,\n props: Omit<P, OpenKey | CloseKey>,\n options?: RawModalOptions<OpenKey, CloseKey>,\n): RawModalInstance<P, OpenKey, CloseKey> {\n const id = `modal-${Date.now()}-${modalSeed++}`;\n modalStack = [\n ...modalStack,\n {\n component,\n id,\n kind: 'raw',\n open: true,\n options,\n props: props as Record<string, unknown>,\n },\n ];\n notify();\n\n return {\n close: () => closeModal(id),\n destroy: () => destroyModal(id),\n setCanDismissByClickOutside: (value) => updateRawProps(id, { mask: { closable: value } }),\n update: (nextProps) => updateRawProps(id, nextProps as Record<string, unknown>),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"imperative.mjs","names":[],"sources":["../../src/Modal/imperative.tsx"],"sourcesContent":["'use client';\n\nimport type { ReactNode } from 'react';\nimport { memo, useEffect, useSyncExternalStore } from 'react';\nimport { createPortal } from 'react-dom';\n\nimport { useIsClient } from '@/hooks/useIsClient';\nimport { useAppElement } from '@/ThemeProvider';\nimport { registerDevSingleton } from '@/utils/devSingleton';\n\nimport { ModalStackItem } from './ModalStackItem';\nimport { RawModalStackItem } from './RawModalStackItem';\nimport type {\n ImperativeModalProps,\n ModalInstance,\n RawModalComponent,\n RawModalComponentProps,\n RawModalInstance,\n RawModalKeyOptions,\n RawModalOptions,\n} from './type';\n\ntype ModalStackItemBase = {\n id: string;\n};\n\ntype ModalStackItemModal = ModalStackItemBase & {\n kind: 'modal';\n props: ImperativeModalProps;\n};\n\ntype ModalStackItemRaw = ModalStackItemBase & {\n component: RawModalComponent;\n kind: 'raw';\n open: boolean;\n options?: RawModalOptions<PropertyKey, PropertyKey>;\n props: Record<string, unknown>;\n};\n\ntype TModalStackItem = ModalStackItemModal | ModalStackItemRaw;\n\ntype ModalStackProps = {\n stack: TModalStackItem[];\n};\n\nexport type ModalHostProps = {\n root?: HTMLElement | ShadowRoot | null;\n};\n\nlet modalStack: TModalStackItem[] = [];\nlet modalSeed = 0;\nconst listeners = new Set<() => void>();\nconst rawDestroyTimers = new Map<string, number>();\n\nconst notify = () => {\n listeners.forEach((listener) => listener());\n};\n\nconst subscribe = (listener: () => void) => {\n listeners.add(listener);\n return () => listeners.delete(listener);\n};\n\nconst EMPTY_STACK: TModalStackItem[] = [];\nconst getSnapshot = () => modalStack;\nconst getServerSnapshot = () => EMPTY_STACK;\n\nconst ModalPortal = ({\n children,\n root,\n}: {\n children: ReactNode;\n root?: HTMLElement | ShadowRoot | null;\n}) => {\n const appElement = useAppElement();\n const container = root ?? appElement ?? document.body;\n\n return createPortal(children, container);\n};\n\nconst updateModal = (id: string, nextProps: Partial<ImperativeModalProps>) => {\n let changed = false;\n modalStack = modalStack.map((item) => {\n if (item.id !== id) return item;\n if (item.kind !== 'modal') return item;\n changed = true;\n return {\n ...item,\n props: { ...item.props, ...nextProps },\n };\n });\n\n if (changed) notify();\n};\n\nconst updateRawProps = (id: string, nextProps: Record<string, unknown>) => {\n let changed = false;\n modalStack = modalStack.map((item) => {\n if (item.id !== id) return item;\n if (item.kind !== 'raw') return item;\n changed = true;\n return {\n ...item,\n props: { ...item.props, ...nextProps },\n };\n });\n\n if (changed) notify();\n};\n\nconst setRawOpen = (id: string, open: boolean) => {\n let changed = false;\n modalStack = modalStack.map((item) => {\n if (item.id !== id) return item;\n if (item.kind !== 'raw') return item;\n if (item.open === open) return item;\n changed = true;\n return { ...item, open };\n });\n\n if (open) {\n const timer = rawDestroyTimers.get(id);\n if (timer) {\n clearTimeout(timer);\n rawDestroyTimers.delete(id);\n }\n }\n\n if (changed) notify();\n};\n\nconst closeModal = (id: string) => {\n const target = modalStack.find((item) => item.id === id);\n if (!target) return;\n\n if (target.kind === 'modal') {\n updateModal(id, { open: false });\n return;\n }\n\n setRawOpen(id, false);\n\n const shouldDestroy = target.options?.destroyOnClose ?? true;\n if (!shouldDestroy) return;\n\n const delay = target.options?.destroyDelay ?? 200;\n const existing = rawDestroyTimers.get(id);\n if (existing) clearTimeout(existing);\n const timer = window.setTimeout(() => {\n rawDestroyTimers.delete(id);\n\n destroyModal(id);\n }, delay);\n rawDestroyTimers.set(id, timer);\n};\n\nconst destroyModal = (id: string) => {\n const timer = rawDestroyTimers.get(id);\n if (timer) {\n clearTimeout(timer);\n rawDestroyTimers.delete(id);\n }\n const nextStack = modalStack.filter((item) => item.id !== id);\n if (nextStack.length === modalStack.length) return;\n modalStack = nextStack;\n notify();\n};\n\nconst ModalStack = memo(({ stack }: ModalStackProps) => {\n const isClient = useIsClient();\n if (!isClient) return null;\n return stack.map((item) => {\n if (item.kind === 'modal') {\n return (\n <ModalStackItem\n id={item.id}\n key={item.id}\n props={item.props}\n onClose={closeModal}\n onDestroy={destroyModal}\n onUpdate={updateModal}\n />\n );\n }\n\n return (\n <RawModalStackItem\n component={item.component}\n id={item.id}\n key={item.id}\n open={item.open}\n options={item.options}\n props={item.props}\n onClose={closeModal}\n onUpdate={updateRawProps}\n />\n );\n });\n});\n\nModalStack.displayName = 'ModalStack';\n\nexport const ModalHost = ({ root }: ModalHostProps) => {\n const stack = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n const isClient = useIsClient();\n\n useEffect(() => {\n if (!isClient) return;\n // Enforce singleton per portal root (dev-only).\n const scope = root ?? document.body;\n return registerDevSingleton('ModalHost', scope);\n }, [isClient, root]);\n\n if (!isClient) return null;\n if (stack.length === 0) return null;\n\n return (\n <ModalPortal root={root}>\n <ModalStack stack={stack} />\n </ModalPortal>\n );\n};\n\nexport const createModal = (props: ImperativeModalProps): ModalInstance => {\n const id = `modal-${Date.now()}-${modalSeed++}`;\n modalStack = [\n ...modalStack,\n { id, kind: 'modal', props: { ...props, open: props.open ?? true } },\n ];\n notify();\n\n return {\n close: () => closeModal(id),\n destroy: () => destroyModal(id),\n setCanDismissByClickOutside: (value) => updateModal(id, { mask: { closable: value } }),\n update: (nextProps) => updateModal(id, nextProps),\n };\n};\n\nexport function createRawModal<P extends RawModalComponentProps>(\n component: RawModalComponent<P>,\n props: Omit<P, 'open' | 'onClose'>,\n options?: RawModalOptions,\n): RawModalInstance<P>;\n\nexport function createRawModal<P, OpenKey extends keyof P, CloseKey extends keyof P>(\n component: RawModalComponent<P>,\n props: Omit<P, OpenKey | CloseKey>,\n options: RawModalKeyOptions<OpenKey, CloseKey>,\n): RawModalInstance<P, OpenKey, CloseKey>;\n\nexport function createRawModal<P, OpenKey extends keyof P, CloseKey extends keyof P>(\n component: RawModalComponent<P>,\n props: Omit<P, OpenKey | CloseKey>,\n options?: RawModalOptions<OpenKey, CloseKey>,\n): RawModalInstance<P, OpenKey, CloseKey> {\n const id = `modal-${Date.now()}-${modalSeed++}`;\n modalStack = [\n ...modalStack,\n {\n component,\n id,\n kind: 'raw',\n open: true,\n options,\n props: props as Record<string, unknown>,\n },\n ];\n notify();\n\n return {\n close: () => closeModal(id),\n destroy: () => destroyModal(id),\n setCanDismissByClickOutside: (value) => updateRawProps(id, { mask: { closable: value } }),\n update: (nextProps) => updateRawProps(id, nextProps as Record<string, unknown>),\n };\n}\n"],"mappings":"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"}
|
|
@@ -74,7 +74,7 @@ const NeuralNetworkLoading = memo(({ size = 16, className, style }) => {
|
|
|
74
74
|
}, `node-${layerIndex}-${nodeIndex}`));
|
|
75
75
|
}
|
|
76
76
|
const connections = [];
|
|
77
|
-
for (let layerIndex = 0; layerIndex <
|
|
77
|
+
for (let layerIndex = 0; layerIndex < 2; layerIndex++) for (let nodeIndex = 0; nodeIndex < NODE_COUNT; nodeIndex++) {
|
|
78
78
|
const x1 = 25 + layerIndex * 25;
|
|
79
79
|
const y1 = 25 + nodeIndex * 25;
|
|
80
80
|
for (let targetIndex = 0; targetIndex < NODE_COUNT; targetIndex++) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NeuralNetworkLoading.mjs","names":[],"sources":["../../src/NeuralNetworkLoading/NeuralNetworkLoading.tsx"],"sourcesContent":["'use client';\n\nimport { createStaticStyles, keyframes } from 'antd-style';\nimport { type CSSProperties, memo } from 'react';\n\nimport type { NeuralNetworkLoadingProps } from './type';\n\nconst pulseAnim = keyframes`\n 0%, 100% { opacity: 0.3; }\n 50% { opacity: 1; }\n`;\n\nconst flowAnim = keyframes`\n 0% { transform: translateX(0); opacity: 0.5; }\n 50% { opacity: 1; }\n 100% { transform: translateX(var(--flow-distance)); opacity: 0.5; }\n`;\n\nconst rotateAnim = keyframes`\n 100% { transform: rotate(360deg); }\n`;\n\nconst scaleAnim = keyframes`\n 0%, 100% { transform: scale(0.8); opacity: 0.5; }\n 50% { transform: scale(1); opacity: 1; }\n`;\n\nconst styles = createStaticStyles(({ css, cssVar }) => ({\n center: css`\n fill: ${cssVar.colorTextSecondary};\n animation: ${scaleAnim} 2s infinite;\n `,\n connection: css`\n opacity: 0.3;\n stroke: ${cssVar.colorTextSecondary};\n stroke-width: 0.5;\n `,\n container: css`\n display: flex;\n align-items: center;\n justify-content: center;\n `,\n node: css`\n fill: ${cssVar.colorTextSecondary};\n animation: ${pulseAnim} 2s infinite;\n `,\n particle: css`\n fill: ${cssVar.colorTextSecondary};\n animation: ${flowAnim} 2s infinite;\n `,\n ring: css`\n transform-origin: center;\n\n fill: none;\n stroke: ${cssVar.colorFill};\n stroke-dasharray: 0 8;\n stroke-width: 1;\n\n animation: ${rotateAnim} 20s infinite linear;\n `,\n svg: css`\n width: 100%;\n height: 100%;\n `,\n}));\n\nconst LAYER_COUNT = 3;\nconst NODE_COUNT = 3;\n\nconst NeuralNetworkLoading = memo<NeuralNetworkLoadingProps>(({ size = 16, className, style }) => {\n const nodes = [];\n for (let layerIndex = 0; layerIndex < LAYER_COUNT; layerIndex++) {\n for (let nodeIndex = 0; nodeIndex < NODE_COUNT; nodeIndex++) {\n const x = 25 + layerIndex * 25;\n const y = 25 + nodeIndex * 25;\n const delay = (layerIndex * NODE_COUNT + nodeIndex) * 0.2;\n nodes.push(\n <circle\n className={styles.node}\n cx={x}\n cy={y}\n key={`node-${layerIndex}-${nodeIndex}`}\n r=\"3\"\n style={{ animationDelay: `${delay}s` }}\n />,\n );\n }\n }\n\n const connections = [];\n for (let layerIndex = 0; layerIndex < LAYER_COUNT - 1; layerIndex++) {\n for (let nodeIndex = 0; nodeIndex < NODE_COUNT; nodeIndex++) {\n const x1 = 25 + layerIndex * 25;\n const y1 = 25 + nodeIndex * 25;\n for (let targetIndex = 0; targetIndex < NODE_COUNT; targetIndex++) {\n const x2 = 25 + (layerIndex + 1) * 25;\n const y2 = 25 + targetIndex * 25;\n connections.push(\n <line\n className={styles.connection}\n key={`connection-${layerIndex}-${nodeIndex}-${targetIndex}`}\n x1={x1}\n x2={x2}\n y1={y1}\n y2={y2}\n />,\n );\n }\n }\n }\n\n const particles = [0, 1, 2].map((index) => (\n <circle\n className={styles.particle}\n cx={25}\n cy={50}\n key={`particle-${index}`}\n r=\"1.5\"\n style={\n {\n '--flow-distance': '50px',\n 'animationDelay': `${index * 0.6}s`,\n } as CSSProperties\n }\n />\n ));\n\n return (\n <div\n className={[styles.container, className].filter(Boolean).join(' ')}\n style={{ height: size, width: size, ...style }}\n >\n <svg className={styles.svg} viewBox=\"0 0 100 100\" xmlns=\"http://www.w3.org/2000/svg\">\n {connections}\n {nodes}\n {particles}\n <rect className={styles.center} height=\"6\" width=\"6\" x=\"47\" y=\"47\" />\n <circle className={styles.ring} cx=\"50\" cy=\"50\" r=\"40\" />\n </svg>\n </div>\n );\n});\n\nNeuralNetworkLoading.displayName = 'NeuralNetworkLoading';\n\nexport default NeuralNetworkLoading;\n"],"mappings":";;;;;AAOA,MAAM,YAAY,SAAS;;;;AAK3B,MAAM,WAAW,SAAS;;;;;AAM1B,MAAM,aAAa,SAAS;;;AAI5B,MAAM,YAAY,SAAS;;;;AAK3B,MAAM,SAAS,oBAAoB,EAAE,KAAK,cAAc;CACtD,QAAQ,GAAG;YACD,OAAO,mBAAmB;iBACrB,UAAU;;CAEzB,YAAY,GAAG;;cAEH,OAAO,mBAAmB;;;CAGtC,WAAW,GAAG;;;;;CAKd,MAAM,GAAG;YACC,OAAO,mBAAmB;iBACrB,UAAU;;CAEzB,UAAU,GAAG;YACH,OAAO,mBAAmB;iBACrB,SAAS;;CAExB,MAAM,GAAG;;;;cAIG,OAAO,UAAU;;;;iBAId,WAAW;;CAE1B,KAAK,GAAG;;;;AAIV,EAAE;AAEF,MAAM,cAAc;AACpB,MAAM,aAAa;AAEnB,MAAM,uBAAuB,MAAiC,EAAE,OAAO,IAAI,WAAW,YAAY;CAChG,MAAM,QAAQ,CAAC;CACf,KAAK,IAAI,aAAa,GAAG,aAAa,aAAa,cACjD,KAAK,IAAI,YAAY,GAAG,YAAY,YAAY,aAAa;EAC3D,MAAM,IAAI,KAAK,aAAa;EAC5B,MAAM,IAAI,KAAK,YAAY;EAC3B,MAAM,SAAS,aAAa,aAAa,aAAa;EACtD,MAAM,KACJ,oBAAC,UAAD;GACE,WAAW,OAAO;GAClB,IAAI;GACJ,IAAI;GAEJ,GAAE;GACF,OAAO,EAAE,gBAAgB,GAAG,MAAM,GAAG;EACtC,GAHM,QAAQ,WAAW,GAAG,WAG5B,CACH;CACF;CAGF,MAAM,cAAc,CAAC;CACrB,KAAK,IAAI,aAAa,GAAG,aAAa,
|
|
1
|
+
{"version":3,"file":"NeuralNetworkLoading.mjs","names":[],"sources":["../../src/NeuralNetworkLoading/NeuralNetworkLoading.tsx"],"sourcesContent":["'use client';\n\nimport { createStaticStyles, keyframes } from 'antd-style';\nimport { type CSSProperties, memo } from 'react';\n\nimport type { NeuralNetworkLoadingProps } from './type';\n\nconst pulseAnim = keyframes`\n 0%, 100% { opacity: 0.3; }\n 50% { opacity: 1; }\n`;\n\nconst flowAnim = keyframes`\n 0% { transform: translateX(0); opacity: 0.5; }\n 50% { opacity: 1; }\n 100% { transform: translateX(var(--flow-distance)); opacity: 0.5; }\n`;\n\nconst rotateAnim = keyframes`\n 100% { transform: rotate(360deg); }\n`;\n\nconst scaleAnim = keyframes`\n 0%, 100% { transform: scale(0.8); opacity: 0.5; }\n 50% { transform: scale(1); opacity: 1; }\n`;\n\nconst styles = createStaticStyles(({ css, cssVar }) => ({\n center: css`\n fill: ${cssVar.colorTextSecondary};\n animation: ${scaleAnim} 2s infinite;\n `,\n connection: css`\n opacity: 0.3;\n stroke: ${cssVar.colorTextSecondary};\n stroke-width: 0.5;\n `,\n container: css`\n display: flex;\n align-items: center;\n justify-content: center;\n `,\n node: css`\n fill: ${cssVar.colorTextSecondary};\n animation: ${pulseAnim} 2s infinite;\n `,\n particle: css`\n fill: ${cssVar.colorTextSecondary};\n animation: ${flowAnim} 2s infinite;\n `,\n ring: css`\n transform-origin: center;\n\n fill: none;\n stroke: ${cssVar.colorFill};\n stroke-dasharray: 0 8;\n stroke-width: 1;\n\n animation: ${rotateAnim} 20s infinite linear;\n `,\n svg: css`\n width: 100%;\n height: 100%;\n `,\n}));\n\nconst LAYER_COUNT = 3;\nconst NODE_COUNT = 3;\n\nconst NeuralNetworkLoading = memo<NeuralNetworkLoadingProps>(({ size = 16, className, style }) => {\n const nodes = [];\n for (let layerIndex = 0; layerIndex < LAYER_COUNT; layerIndex++) {\n for (let nodeIndex = 0; nodeIndex < NODE_COUNT; nodeIndex++) {\n const x = 25 + layerIndex * 25;\n const y = 25 + nodeIndex * 25;\n const delay = (layerIndex * NODE_COUNT + nodeIndex) * 0.2;\n nodes.push(\n <circle\n className={styles.node}\n cx={x}\n cy={y}\n key={`node-${layerIndex}-${nodeIndex}`}\n r=\"3\"\n style={{ animationDelay: `${delay}s` }}\n />,\n );\n }\n }\n\n const connections = [];\n for (let layerIndex = 0; layerIndex < LAYER_COUNT - 1; layerIndex++) {\n for (let nodeIndex = 0; nodeIndex < NODE_COUNT; nodeIndex++) {\n const x1 = 25 + layerIndex * 25;\n const y1 = 25 + nodeIndex * 25;\n for (let targetIndex = 0; targetIndex < NODE_COUNT; targetIndex++) {\n const x2 = 25 + (layerIndex + 1) * 25;\n const y2 = 25 + targetIndex * 25;\n connections.push(\n <line\n className={styles.connection}\n key={`connection-${layerIndex}-${nodeIndex}-${targetIndex}`}\n x1={x1}\n x2={x2}\n y1={y1}\n y2={y2}\n />,\n );\n }\n }\n }\n\n const particles = [0, 1, 2].map((index) => (\n <circle\n className={styles.particle}\n cx={25}\n cy={50}\n key={`particle-${index}`}\n r=\"1.5\"\n style={\n {\n '--flow-distance': '50px',\n 'animationDelay': `${index * 0.6}s`,\n } as CSSProperties\n }\n />\n ));\n\n return (\n <div\n className={[styles.container, className].filter(Boolean).join(' ')}\n style={{ height: size, width: size, ...style }}\n >\n <svg className={styles.svg} viewBox=\"0 0 100 100\" xmlns=\"http://www.w3.org/2000/svg\">\n {connections}\n {nodes}\n {particles}\n <rect className={styles.center} height=\"6\" width=\"6\" x=\"47\" y=\"47\" />\n <circle className={styles.ring} cx=\"50\" cy=\"50\" r=\"40\" />\n </svg>\n </div>\n );\n});\n\nNeuralNetworkLoading.displayName = 'NeuralNetworkLoading';\n\nexport default NeuralNetworkLoading;\n"],"mappings":";;;;;AAOA,MAAM,YAAY,SAAS;;;;AAK3B,MAAM,WAAW,SAAS;;;;;AAM1B,MAAM,aAAa,SAAS;;;AAI5B,MAAM,YAAY,SAAS;;;;AAK3B,MAAM,SAAS,oBAAoB,EAAE,KAAK,cAAc;CACtD,QAAQ,GAAG;YACD,OAAO,mBAAmB;iBACrB,UAAU;;CAEzB,YAAY,GAAG;;cAEH,OAAO,mBAAmB;;;CAGtC,WAAW,GAAG;;;;;CAKd,MAAM,GAAG;YACC,OAAO,mBAAmB;iBACrB,UAAU;;CAEzB,UAAU,GAAG;YACH,OAAO,mBAAmB;iBACrB,SAAS;;CAExB,MAAM,GAAG;;;;cAIG,OAAO,UAAU;;;;iBAId,WAAW;;CAE1B,KAAK,GAAG;;;;AAIV,EAAE;AAEF,MAAM,cAAc;AACpB,MAAM,aAAa;AAEnB,MAAM,uBAAuB,MAAiC,EAAE,OAAO,IAAI,WAAW,YAAY;CAChG,MAAM,QAAQ,CAAC;CACf,KAAK,IAAI,aAAa,GAAG,aAAa,aAAa,cACjD,KAAK,IAAI,YAAY,GAAG,YAAY,YAAY,aAAa;EAC3D,MAAM,IAAI,KAAK,aAAa;EAC5B,MAAM,IAAI,KAAK,YAAY;EAC3B,MAAM,SAAS,aAAa,aAAa,aAAa;EACtD,MAAM,KACJ,oBAAC,UAAD;GACE,WAAW,OAAO;GAClB,IAAI;GACJ,IAAI;GAEJ,GAAE;GACF,OAAO,EAAE,gBAAgB,GAAG,MAAM,GAAG;EACtC,GAHM,QAAQ,WAAW,GAAG,WAG5B,CACH;CACF;CAGF,MAAM,cAAc,CAAC;CACrB,KAAK,IAAI,aAAa,GAAG,aAAa,GAAiB,cACrD,KAAK,IAAI,YAAY,GAAG,YAAY,YAAY,aAAa;EAC3D,MAAM,KAAK,KAAK,aAAa;EAC7B,MAAM,KAAK,KAAK,YAAY;EAC5B,KAAK,IAAI,cAAc,GAAG,cAAc,YAAY,eAAe;GACjE,MAAM,KAAK,MAAM,aAAa,KAAK;GACnC,MAAM,KAAK,KAAK,cAAc;GAC9B,YAAY,KACV,oBAAC,QAAD;IACE,WAAW,OAAO;IAEd;IACA;IACA;IACA;GACL,GALM,cAAc,WAAW,GAAG,UAAU,GAAG,aAK/C,CACH;EACF;CACF;CAGF,MAAM,YAAY;EAAC;EAAG;EAAG;CAAC,CAAC,CAAC,KAAK,UAC/B,oBAAC,UAAD;EACE,WAAW,OAAO;EAClB,IAAI;EACJ,IAAI;EAEJ,GAAE;EACF,OACE;GACE,mBAAmB;GACnB,kBAAkB,GAAG,QAAQ,GAAI;EACnC;CAEH,GARM,YAAY,OAQlB,CACF;CAED,OACE,oBAAC,OAAD;EACE,WAAW,CAAC,OAAO,WAAW,SAAS,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;EACjE,OAAO;GAAE,QAAQ;GAAM,OAAO;GAAM,GAAG;EAAM;EAE7C,UAAA,qBAAC,OAAD;GAAK,WAAW,OAAO;GAAK,SAAQ;GAAc,OAAM;GAAxD,UAAA;IACG;IACA;IACA;IACD,oBAAC,QAAD;KAAM,WAAW,OAAO;KAAQ,QAAO;KAAI,OAAM;KAAI,GAAE;KAAK,GAAE;IAAM,CAAA;IACpE,oBAAC,UAAD;KAAQ,WAAW,OAAO;KAAM,IAAG;KAAK,IAAG;KAAK,GAAE;IAAM,CAAA;GACrD;;CACF,CAAA;AAET,CAAC;AAED,qBAAqB,cAAc"}
|
|
@@ -48,22 +48,23 @@ const ScrollShadow = ({ className, children, orientation = "vertical", hideScrol
|
|
|
48
48
|
else if (finalScrollState.right) attributes["data-right-scroll"] = true;
|
|
49
49
|
return attributes;
|
|
50
50
|
}, [orientation, finalScrollState]);
|
|
51
|
+
const scrollPosition = useMemo(() => {
|
|
52
|
+
if (orientation === "vertical") {
|
|
53
|
+
if (finalScrollState.top && finalScrollState.bottom) return "top-bottom";
|
|
54
|
+
if (finalScrollState.top) return "top";
|
|
55
|
+
if (finalScrollState.bottom) return "bottom";
|
|
56
|
+
} else {
|
|
57
|
+
if (finalScrollState.left && finalScrollState.right) return "left-right";
|
|
58
|
+
if (finalScrollState.left) return "left";
|
|
59
|
+
if (finalScrollState.right) return "right";
|
|
60
|
+
}
|
|
61
|
+
return "none";
|
|
62
|
+
}, [orientation, finalScrollState]);
|
|
51
63
|
return /* @__PURE__ */ jsx(FlexBasic_default, {
|
|
52
64
|
className: cx(variants({
|
|
53
65
|
hideScrollBar,
|
|
54
66
|
orientation,
|
|
55
|
-
scrollPosition
|
|
56
|
-
if (orientation === "vertical") {
|
|
57
|
-
if (finalScrollState.top && finalScrollState.bottom) return "top-bottom";
|
|
58
|
-
if (finalScrollState.top) return "top";
|
|
59
|
-
if (finalScrollState.bottom) return "bottom";
|
|
60
|
-
} else {
|
|
61
|
-
if (finalScrollState.left && finalScrollState.right) return "left-right";
|
|
62
|
-
if (finalScrollState.left) return "left";
|
|
63
|
-
if (finalScrollState.right) return "right";
|
|
64
|
-
}
|
|
65
|
-
return "none";
|
|
66
|
-
}, [orientation, finalScrollState])
|
|
67
|
+
scrollPosition
|
|
67
68
|
}), className),
|
|
68
69
|
ref: mergeRefs([domRef, ref]),
|
|
69
70
|
style: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScrollShadow.mjs","names":["Flexbox"],"sources":["../../src/ScrollShadow/ScrollShadow.tsx"],"sourcesContent":["'use client';\n\nimport { cx } from 'antd-style';\nimport type { FC } from 'react';\nimport { useMemo, useRef } from 'react';\nimport { mergeRefs } from 'react-merge-refs';\n\nimport { Flexbox } from '@/Flex';\nimport { useEventCallback } from '@/hooks/useEventCallback';\n\nimport { variants } from './style';\nimport type { ScrollShadowProps } from './type';\nimport { useScrollOverflow } from './useScrollOverflow';\n\nconst ScrollShadow: FC<ScrollShadowProps> = ({\n className,\n children,\n orientation = 'vertical',\n hideScrollBar = false,\n size = 16,\n offset = 8,\n visibility = 'auto',\n isEnabled = true,\n onVisibilityChange,\n style,\n ref,\n ...rest\n}) => {\n // Convert size prop to CSS variable\n const cssVariables = useMemo<Record<string, string>>(\n () => ({\n '--scroll-shadow-size': `${size}%`,\n }),\n [size],\n );\n const domRef = useRef<HTMLDivElement>(null);\n\n const onVisibilityChangeEvent = useEventCallback<\n NonNullable<ScrollShadowProps['onVisibilityChange']>\n >((visibility) => {\n onVisibilityChange?.(visibility);\n });\n // 使用滚动检测钩子\n const scrollState = useScrollOverflow({\n domRef,\n isEnabled: isEnabled && visibility === 'auto',\n offset,\n onVisibilityChange: onVisibilityChangeEvent,\n orientation,\n updateDeps: [children],\n });\n\n // 决定最终的滚动状态\n const finalScrollState = useMemo(() => {\n if (visibility === 'always') {\n return {\n bottom: true,\n left: true,\n right: true,\n top: true,\n };\n }\n\n if (visibility === 'never') {\n return {\n bottom: false,\n left: false,\n right: false,\n top: false,\n };\n }\n\n return scrollState;\n }, [visibility, scrollState]);\n\n // 计算数据属性\n const dataAttributes = useMemo(() => {\n const attributes: Record<string, boolean | string> = {\n 'data-orientation': orientation,\n };\n\n if (orientation === 'vertical') {\n if (finalScrollState.top && finalScrollState.bottom) {\n attributes['data-top-bottom-scroll'] = true;\n } else if (finalScrollState.top) {\n attributes['data-top-scroll'] = true;\n } else if (finalScrollState.bottom) {\n attributes['data-bottom-scroll'] = true;\n }\n } else {\n if (finalScrollState.left && finalScrollState.right) {\n attributes['data-left-right-scroll'] = true;\n } else if (finalScrollState.left) {\n attributes['data-left-scroll'] = true;\n } else if (finalScrollState.right) {\n attributes['data-right-scroll'] = true;\n }\n }\n\n return attributes;\n }, [orientation, finalScrollState]);\n\n // 计算滚动位置变体\n const scrollPosition = useMemo(() => {\n if (orientation === 'vertical') {\n if (finalScrollState.top && finalScrollState.bottom) return 'top-bottom';\n if (finalScrollState.top) return 'top';\n if (finalScrollState.bottom) return 'bottom';\n } else {\n if (finalScrollState.left && finalScrollState.right) return 'left-right';\n if (finalScrollState.left) return 'left';\n if (finalScrollState.right) return 'right';\n }\n return 'none';\n }, [orientation, finalScrollState]);\n\n return (\n <Flexbox\n className={cx(variants({ hideScrollBar, orientation, scrollPosition }), className)}\n ref={mergeRefs<HTMLDivElement>([domRef, ref])}\n style={{\n ...cssVariables,\n ...style,\n }}\n {...dataAttributes}\n {...rest}\n >\n {children}\n </Flexbox>\n );\n};\n\nScrollShadow.displayName = 'ScrollShadow';\n\nexport default ScrollShadow;\n"],"mappings":";;;;;;;;;;AAcA,MAAM,gBAAuC,EAC3C,WACA,UACA,cAAc,YACd,gBAAgB,OAChB,OAAO,IACP,SAAS,GACT,aAAa,QACb,YAAY,MACZ,oBACA,OACA,KACA,GAAG,WACC;CAEJ,MAAM,eAAe,eACZ,EACL,wBAAwB,GAAG,KAAK,GAClC,IACA,CAAC,IAAI,CACP;CACA,MAAM,SAAS,OAAuB,IAAI;CAE1C,MAAM,0BAA0B,kBAE7B,eAAe;EAChB,qBAAqB,UAAU;CACjC,CAAC;CAED,MAAM,cAAc,kBAAkB;EACpC;EACA,WAAW,aAAa,eAAe;EACvC;EACA,oBAAoB;EACpB;EACA,YAAY,CAAC,QAAQ;CACvB,CAAC;CAGD,MAAM,mBAAmB,cAAc;EACrC,IAAI,eAAe,UACjB,OAAO;GACL,QAAQ;GACR,MAAM;GACN,OAAO;GACP,KAAK;EACP;EAGF,IAAI,eAAe,SACjB,OAAO;GACL,QAAQ;GACR,MAAM;GACN,OAAO;GACP,KAAK;EACP;EAGF,OAAO;CACT,GAAG,CAAC,YAAY,WAAW,CAAC;CAG5B,MAAM,iBAAiB,cAAc;EACnC,MAAM,aAA+C,EACnD,oBAAoB,YACtB;EAEA,IAAI,gBAAgB,YACd;OAAA,iBAAiB,OAAO,iBAAiB,QAC3C,WAAW,4BAA4B;QAClC,IAAI,iBAAiB,KAC1B,WAAW,qBAAqB;QAC3B,IAAI,iBAAiB,QAC1B,WAAW,wBAAwB;EAAA,OAGrC,IAAI,iBAAiB,QAAQ,iBAAiB,OAC5C,WAAW,4BAA4B;OAClC,IAAI,iBAAiB,MAC1B,WAAW,sBAAsB;OAC5B,IAAI,iBAAiB,OAC1B,WAAW,uBAAuB;EAItC,OAAO;CACT,GAAG,CAAC,aAAa,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"ScrollShadow.mjs","names":["Flexbox"],"sources":["../../src/ScrollShadow/ScrollShadow.tsx"],"sourcesContent":["'use client';\n\nimport { cx } from 'antd-style';\nimport type { FC } from 'react';\nimport { useMemo, useRef } from 'react';\nimport { mergeRefs } from 'react-merge-refs';\n\nimport { Flexbox } from '@/Flex';\nimport { useEventCallback } from '@/hooks/useEventCallback';\n\nimport { variants } from './style';\nimport type { ScrollShadowProps } from './type';\nimport { useScrollOverflow } from './useScrollOverflow';\n\nconst ScrollShadow: FC<ScrollShadowProps> = ({\n className,\n children,\n orientation = 'vertical',\n hideScrollBar = false,\n size = 16,\n offset = 8,\n visibility = 'auto',\n isEnabled = true,\n onVisibilityChange,\n style,\n ref,\n ...rest\n}) => {\n // Convert size prop to CSS variable\n const cssVariables = useMemo<Record<string, string>>(\n () => ({\n '--scroll-shadow-size': `${size}%`,\n }),\n [size],\n );\n const domRef = useRef<HTMLDivElement>(null);\n\n const onVisibilityChangeEvent = useEventCallback<\n NonNullable<ScrollShadowProps['onVisibilityChange']>\n >((visibility) => {\n onVisibilityChange?.(visibility);\n });\n // 使用滚动检测钩子\n const scrollState = useScrollOverflow({\n domRef,\n isEnabled: isEnabled && visibility === 'auto',\n offset,\n onVisibilityChange: onVisibilityChangeEvent,\n orientation,\n updateDeps: [children],\n });\n\n // 决定最终的滚动状态\n const finalScrollState = useMemo(() => {\n if (visibility === 'always') {\n return {\n bottom: true,\n left: true,\n right: true,\n top: true,\n };\n }\n\n if (visibility === 'never') {\n return {\n bottom: false,\n left: false,\n right: false,\n top: false,\n };\n }\n\n return scrollState;\n }, [visibility, scrollState]);\n\n // 计算数据属性\n const dataAttributes = useMemo(() => {\n const attributes: Record<string, boolean | string> = {\n 'data-orientation': orientation,\n };\n\n if (orientation === 'vertical') {\n if (finalScrollState.top && finalScrollState.bottom) {\n attributes['data-top-bottom-scroll'] = true;\n } else if (finalScrollState.top) {\n attributes['data-top-scroll'] = true;\n } else if (finalScrollState.bottom) {\n attributes['data-bottom-scroll'] = true;\n }\n } else {\n if (finalScrollState.left && finalScrollState.right) {\n attributes['data-left-right-scroll'] = true;\n } else if (finalScrollState.left) {\n attributes['data-left-scroll'] = true;\n } else if (finalScrollState.right) {\n attributes['data-right-scroll'] = true;\n }\n }\n\n return attributes;\n }, [orientation, finalScrollState]);\n\n // 计算滚动位置变体\n const scrollPosition = useMemo(() => {\n if (orientation === 'vertical') {\n if (finalScrollState.top && finalScrollState.bottom) return 'top-bottom';\n if (finalScrollState.top) return 'top';\n if (finalScrollState.bottom) return 'bottom';\n } else {\n if (finalScrollState.left && finalScrollState.right) return 'left-right';\n if (finalScrollState.left) return 'left';\n if (finalScrollState.right) return 'right';\n }\n return 'none';\n }, [orientation, finalScrollState]);\n\n return (\n <Flexbox\n className={cx(variants({ hideScrollBar, orientation, scrollPosition }), className)}\n ref={mergeRefs<HTMLDivElement>([domRef, ref])}\n style={{\n ...cssVariables,\n ...style,\n }}\n {...dataAttributes}\n {...rest}\n >\n {children}\n </Flexbox>\n );\n};\n\nScrollShadow.displayName = 'ScrollShadow';\n\nexport default ScrollShadow;\n"],"mappings":";;;;;;;;;;AAcA,MAAM,gBAAuC,EAC3C,WACA,UACA,cAAc,YACd,gBAAgB,OAChB,OAAO,IACP,SAAS,GACT,aAAa,QACb,YAAY,MACZ,oBACA,OACA,KACA,GAAG,WACC;CAEJ,MAAM,eAAe,eACZ,EACL,wBAAwB,GAAG,KAAK,GAClC,IACA,CAAC,IAAI,CACP;CACA,MAAM,SAAS,OAAuB,IAAI;CAE1C,MAAM,0BAA0B,kBAE7B,eAAe;EAChB,qBAAqB,UAAU;CACjC,CAAC;CAED,MAAM,cAAc,kBAAkB;EACpC;EACA,WAAW,aAAa,eAAe;EACvC;EACA,oBAAoB;EACpB;EACA,YAAY,CAAC,QAAQ;CACvB,CAAC;CAGD,MAAM,mBAAmB,cAAc;EACrC,IAAI,eAAe,UACjB,OAAO;GACL,QAAQ;GACR,MAAM;GACN,OAAO;GACP,KAAK;EACP;EAGF,IAAI,eAAe,SACjB,OAAO;GACL,QAAQ;GACR,MAAM;GACN,OAAO;GACP,KAAK;EACP;EAGF,OAAO;CACT,GAAG,CAAC,YAAY,WAAW,CAAC;CAG5B,MAAM,iBAAiB,cAAc;EACnC,MAAM,aAA+C,EACnD,oBAAoB,YACtB;EAEA,IAAI,gBAAgB,YACd;OAAA,iBAAiB,OAAO,iBAAiB,QAC3C,WAAW,4BAA4B;QAClC,IAAI,iBAAiB,KAC1B,WAAW,qBAAqB;QAC3B,IAAI,iBAAiB,QAC1B,WAAW,wBAAwB;EAAA,OAGrC,IAAI,iBAAiB,QAAQ,iBAAiB,OAC5C,WAAW,4BAA4B;OAClC,IAAI,iBAAiB,MAC1B,WAAW,sBAAsB;OAC5B,IAAI,iBAAiB,OAC1B,WAAW,uBAAuB;EAItC,OAAO;CACT,GAAG,CAAC,aAAa,gBAAgB,CAAC;CAGlC,MAAM,iBAAiB,cAAc;EACnC,IAAI,gBAAgB,YAAY;GAC9B,IAAI,iBAAiB,OAAO,iBAAiB,QAAQ,OAAO;GAC5D,IAAI,iBAAiB,KAAK,OAAO;GACjC,IAAI,iBAAiB,QAAQ,OAAO;EACtC,OAAO;GACL,IAAI,iBAAiB,QAAQ,iBAAiB,OAAO,OAAO;GAC5D,IAAI,iBAAiB,MAAM,OAAO;GAClC,IAAI,iBAAiB,OAAO,OAAO;EACrC;EACA,OAAO;CACT,GAAG,CAAC,aAAa,gBAAgB,CAAC;CAElC,OACE,oBAACA,mBAAD;EACE,WAAW,GAAG,SAAS;GAAE;GAAe;GAAa;EAAe,CAAC,GAAG,SAAS;EACjF,KAAK,UAA0B,CAAC,QAAQ,GAAG,CAAC;EAC5C,OAAO;GACL,GAAG;GACH,GAAG;EACL;EACA,GAAI;EACJ,GAAI;EAEH;CACM,CAAA;AAEb;AAEA,aAAa,cAAc"}
|
|
@@ -17,12 +17,13 @@ const SkeletonButton = ({ active, block = false, shape = "default", size = "defa
|
|
|
17
17
|
large: cssVar.borderRadiusLG,
|
|
18
18
|
small: cssVar.borderRadiusSM
|
|
19
19
|
};
|
|
20
|
+
const borderRadius = shape === "circle" ? "50%" : shape === "round" ? `calc(${cssVar.borderRadius} * 2)` : RADIUS_MAP[resolvedSize];
|
|
20
21
|
return /* @__PURE__ */ jsx(SkeletonBlock, {
|
|
21
22
|
active,
|
|
22
23
|
className,
|
|
23
24
|
height: baseHeight,
|
|
24
25
|
style: {
|
|
25
|
-
borderRadius
|
|
26
|
+
borderRadius,
|
|
26
27
|
...style
|
|
27
28
|
},
|
|
28
29
|
width: finalWidth,
|