@lobehub/ui 5.24.0 → 5.26.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/Image.mjs +138 -37
- package/es/Image/Image.mjs.map +1 -1
- package/es/Image/PreviewGroup.d.mts +1 -0
- package/es/Image/PreviewGroup.mjs +33 -10
- package/es/Image/PreviewGroup.mjs.map +1 -1
- package/es/Image/index.d.mts +2 -2
- package/es/Image/index.mjs +1 -1
- package/es/Image/style.mjs +126 -71
- package/es/Image/style.mjs.map +1 -1
- package/es/Image/type.d.mts +14 -17
- package/es/Image/viewer/GalleryNav.mjs +44 -0
- package/es/Image/viewer/GalleryNav.mjs.map +1 -0
- package/es/Image/viewer/ImageViewer.mjs +275 -0
- package/es/Image/viewer/ImageViewer.mjs.map +1 -0
- package/es/Image/viewer/PreviewOutlet.mjs +19 -0
- package/es/Image/viewer/PreviewOutlet.mjs.map +1 -0
- package/es/Image/viewer/Toolbar.mjs +184 -0
- package/es/Image/viewer/Toolbar.mjs.map +1 -0
- package/es/Image/viewer/ViewerChrome.mjs +54 -0
- package/es/Image/viewer/ViewerChrome.mjs.map +1 -0
- package/es/Image/viewer/geometry.mjs +85 -0
- package/es/Image/viewer/geometry.mjs.map +1 -0
- package/es/Image/viewer/registry.d.mts +2 -0
- package/es/Image/viewer/registry.mjs +52 -0
- package/es/Image/viewer/registry.mjs.map +1 -0
- package/es/Image/viewer/useChromeIdle.mjs +72 -0
- package/es/Image/viewer/useChromeIdle.mjs.map +1 -0
- package/es/Image/viewer/useFinalFocus.mjs +10 -0
- package/es/Image/viewer/useFinalFocus.mjs.map +1 -0
- package/es/Image/viewer/useFlipTransition.mjs +321 -0
- package/es/Image/viewer/useFlipTransition.mjs.map +1 -0
- package/es/Image/viewer/useGalleryNav.mjs +89 -0
- package/es/Image/viewer/useGalleryNav.mjs.map +1 -0
- package/es/Image/viewer/useRefitTransition.mjs +47 -0
- package/es/Image/viewer/useRefitTransition.mjs.map +1 -0
- package/es/Image/viewer/useViewerGestures.mjs +148 -0
- package/es/Image/viewer/useViewerGestures.mjs.map +1 -0
- package/es/Image/viewer/useZoomPan.mjs +346 -0
- package/es/Image/viewer/useZoomPan.mjs.map +1 -0
- 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/Toast/hostGuard.mjs +26 -0
- package/es/base-ui/Toast/hostGuard.mjs.map +1 -0
- package/es/base-ui/Toast/imperative.mjs +43 -20
- package/es/base-ui/Toast/imperative.mjs.map +1 -1
- package/es/base-ui/Toast/pendingQueue.mjs +30 -0
- package/es/base-ui/Toast/pendingQueue.mjs.map +1 -0
- package/es/base-ui/index.d.mts +6 -1
- package/es/base-ui/index.mjs +5 -2
- 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/es/i18n/resources/en/image.d.mts +5 -0
- package/es/i18n/resources/en/image.mjs +6 -1
- package/es/i18n/resources/en/image.mjs.map +1 -1
- package/es/i18n/resources/zhCn/image.d.mts +5 -0
- package/es/i18n/resources/zhCn/image.mjs +6 -1
- package/es/i18n/resources/zhCn/image.mjs.map +1 -1
- package/es/index.mjs +8 -8
- package/package.json +1 -1
- package/es/Image/components/Preview.mjs +0 -23
- package/es/Image/components/Preview.mjs.map +0 -1
- package/es/Image/components/Toolbar.mjs +0 -128
- package/es/Image/components/Toolbar.mjs.map +0 -1
- package/es/Image/components/usePreview.mjs +0 -58
- package/es/Image/components/usePreview.mjs.map +0 -1
- package/es/Image/components/usePreviewGroup.mjs +0 -57
- package/es/Image/components/usePreviewGroup.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useViewerGestures.mjs","names":[],"sources":["../../../src/Image/viewer/useViewerGestures.ts"],"sourcesContent":["import type { MouseEvent, PointerEvent } from 'react';\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nimport type { Point } from './geometry';\nimport type { WheelLikeEvent } from './useZoomPan';\n\nconst DRAG_THRESHOLD = 4;\nconst DOUBLE_CLICK_WINDOW = 250;\n\nexport type ViewerCursor = 'grab' | 'grabbing' | 'zoom-out';\n\nexport interface UseViewerGesturesOptions {\n dragBy: (delta: Point) => void;\n dragEnd: () => void;\n handleDoubleClick: (point: Point) => void;\n handleWheel: (event: WheelLikeEvent) => void;\n isClean: boolean;\n isZoomed: boolean;\n onClose: () => void;\n reset: () => void;\n zoomIn: () => void;\n zoomOut: () => void;\n}\n\nexport interface UseViewerGesturesResult {\n cancelPendingClose: () => void;\n cursor: ViewerCursor;\n imageRef: (node: HTMLImageElement | null) => void;\n onImageClick: (event: MouseEvent<HTMLElement>) => void;\n onImageDoubleClick: (event: MouseEvent<HTMLElement>) => void;\n onPointerDown: (event: PointerEvent<HTMLElement>) => void;\n onPointerFinish: (event: PointerEvent<HTMLElement>) => void;\n onPointerMove: (event: PointerEvent<HTMLElement>) => void;\n onSurfaceClick: () => void;\n popupRef: (node: HTMLElement | null) => void;\n}\n\ninterface DragState {\n id: number;\n last: Point;\n panning: boolean;\n start: Point;\n}\n\nconst capturePointer = (node: Element, pointerId: number) => {\n if (typeof node.setPointerCapture === 'function') node.setPointerCapture(pointerId);\n};\n\nexport const isTypingTarget = (target: EventTarget | null) =>\n target instanceof HTMLElement &&\n (target.isContentEditable || ['INPUT', 'SELECT', 'TEXTAREA'].includes(target.tagName));\n\nexport const useViewerGestures = ({\n dragBy,\n dragEnd,\n handleDoubleClick,\n handleWheel,\n isClean,\n isZoomed,\n onClose,\n reset,\n zoomIn,\n zoomOut,\n}: UseViewerGesturesOptions): UseViewerGesturesResult => {\n const [popup, setPopup] = useState<HTMLElement | null>(null);\n const [panning, setPanning] = useState(false);\n\n const imageNodeRef = useRef<HTMLImageElement | null>(null);\n const dragRef = useRef<DragState | null>(null);\n const movedRef = useRef(false);\n const pendingCloseRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const isCleanRef = useRef(isClean);\n isCleanRef.current = isClean;\n\n const cancelPendingClose = useCallback(() => {\n if (pendingCloseRef.current === null) return;\n clearTimeout(pendingCloseRef.current);\n pendingCloseRef.current = null;\n }, []);\n\n useEffect(() => cancelPendingClose, [cancelPendingClose]);\n\n const imageRef = useCallback((node: HTMLImageElement | null) => {\n imageNodeRef.current = node;\n }, []);\n\n useEffect(() => {\n if (!popup) return;\n const listener = (event: globalThis.WheelEvent) => handleWheel(event);\n popup.addEventListener('wheel', listener, { passive: false });\n return () => popup.removeEventListener('wheel', listener);\n }, [handleWheel, popup]);\n\n useEffect(() => {\n const listener = (event: KeyboardEvent) => {\n if (event.altKey || event.ctrlKey || event.metaKey || event.defaultPrevented) return;\n if (isTypingTarget(event.target)) return;\n switch (event.key) {\n case '+':\n case '=': {\n zoomIn();\n break;\n }\n case '-': {\n zoomOut();\n break;\n }\n case '0': {\n reset();\n break;\n }\n default: {\n return;\n }\n }\n event.preventDefault();\n };\n document.addEventListener('keydown', listener);\n return () => document.removeEventListener('keydown', listener);\n }, [reset, zoomIn, zoomOut]);\n\n const onPointerDown = useCallback(\n (event: PointerEvent<HTMLElement>) => {\n if (dragRef.current?.panning) return;\n if (event.button !== 0) return;\n cancelPendingClose();\n const start = { x: event.clientX, y: event.clientY };\n const panEligible = isZoomed && event.target === imageNodeRef.current;\n movedRef.current = false;\n dragRef.current = { id: event.pointerId, last: start, panning: panEligible, start };\n if (!panEligible) return;\n capturePointer(event.target as Element, event.pointerId);\n setPanning(true);\n },\n [cancelPendingClose, isZoomed],\n );\n\n const onPointerMove = useCallback(\n (event: PointerEvent<HTMLElement>) => {\n const drag = dragRef.current;\n if (!drag || drag.id !== event.pointerId) return;\n const point = { x: event.clientX, y: event.clientY };\n if (Math.hypot(point.x - drag.start.x, point.y - drag.start.y) > DRAG_THRESHOLD) {\n movedRef.current = true;\n }\n if (drag.panning) dragBy({ x: point.x - drag.last.x, y: point.y - drag.last.y });\n drag.last = point;\n },\n [dragBy],\n );\n\n const onPointerFinish = useCallback(\n (event: PointerEvent<HTMLElement>) => {\n const drag = dragRef.current;\n if (!drag || drag.id !== event.pointerId) return;\n dragRef.current = null;\n if (!drag.panning) return;\n setPanning(false);\n dragEnd();\n },\n [dragEnd],\n );\n\n const onSurfaceClick = useCallback(() => {\n if (movedRef.current) return;\n cancelPendingClose();\n onClose();\n }, [cancelPendingClose, onClose]);\n\n const onImageClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n event.stopPropagation();\n cancelPendingClose();\n if (movedRef.current || isZoomed) return;\n pendingCloseRef.current = setTimeout(() => {\n pendingCloseRef.current = null;\n if (!isCleanRef.current) return;\n onClose();\n }, DOUBLE_CLICK_WINDOW);\n },\n [cancelPendingClose, isZoomed, onClose],\n );\n\n const onImageDoubleClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n event.stopPropagation();\n cancelPendingClose();\n handleDoubleClick({ x: event.clientX, y: event.clientY });\n },\n [cancelPendingClose, handleDoubleClick],\n );\n\n return {\n cancelPendingClose,\n cursor: isZoomed ? (panning ? 'grabbing' : 'grab') : 'zoom-out',\n imageRef,\n onImageClick,\n onImageDoubleClick,\n onPointerDown,\n onPointerFinish,\n onPointerMove,\n onSurfaceClick,\n popupRef: setPopup,\n };\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { anchoredZoom, clampPan, clampScale, computeFit, doubleClickTarget, normalizeRotation, panBounds, rubberBand, wheelZoomFactor } from "./geometry.mjs";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { animate, motionValue } from "motion/react";
|
|
4
|
+
//#region src/Image/viewer/useZoomPan.ts
|
|
5
|
+
const WHEEL_CLOSE_THRESHOLD = 100;
|
|
6
|
+
const WHEEL_IDLE_MS = 300;
|
|
7
|
+
const ZOOM_STEP = 1.5;
|
|
8
|
+
const RESET_TRANSITION = {
|
|
9
|
+
damping: 30,
|
|
10
|
+
stiffness: 300,
|
|
11
|
+
type: "spring"
|
|
12
|
+
};
|
|
13
|
+
const CLEAN_EPSILON = .01;
|
|
14
|
+
const useZoomPan = ({ natural, viewport, maxScale, onCloseRequest, isClosing, fillViewport }) => {
|
|
15
|
+
const [{ scale, x, y, rotate, flipX, flipY }] = useState(() => ({
|
|
16
|
+
flipX: motionValue(false),
|
|
17
|
+
flipY: motionValue(false),
|
|
18
|
+
rotate: motionValue(0),
|
|
19
|
+
scale: motionValue(1),
|
|
20
|
+
x: motionValue(0),
|
|
21
|
+
y: motionValue(0)
|
|
22
|
+
}));
|
|
23
|
+
const naturalRef = useRef(natural);
|
|
24
|
+
const viewportRef = useRef(viewport);
|
|
25
|
+
const maxScaleRef = useRef(maxScale ?? 8);
|
|
26
|
+
maxScaleRef.current = maxScale ?? 8;
|
|
27
|
+
const fillViewportRef = useRef(fillViewport ?? false);
|
|
28
|
+
fillViewportRef.current = fillViewport ?? false;
|
|
29
|
+
const onCloseRequestRef = useRef(onCloseRequest);
|
|
30
|
+
onCloseRequestRef.current = onCloseRequest;
|
|
31
|
+
const isClosingRef = useRef(isClosing);
|
|
32
|
+
isClosingRef.current = isClosing;
|
|
33
|
+
const closeAccumRef = useRef(0);
|
|
34
|
+
const disarmedRef = useRef(false);
|
|
35
|
+
const idleTimerRef = useRef(null);
|
|
36
|
+
const resetCloseTracking = useCallback(() => {
|
|
37
|
+
if (idleTimerRef.current !== null) {
|
|
38
|
+
clearTimeout(idleTimerRef.current);
|
|
39
|
+
idleTimerRef.current = null;
|
|
40
|
+
}
|
|
41
|
+
closeAccumRef.current = 0;
|
|
42
|
+
disarmedRef.current = false;
|
|
43
|
+
}, []);
|
|
44
|
+
const closing = useCallback(() => isClosingRef.current?.() ?? false, []);
|
|
45
|
+
const isCleanState = useCallback(() => Math.abs(scale.get() - 1) < CLEAN_EPSILON && Math.abs(rotate.get()) < CLEAN_EPSILON && !flipX.get() && !flipY.get(), [
|
|
46
|
+
flipX,
|
|
47
|
+
flipY,
|
|
48
|
+
rotate,
|
|
49
|
+
scale
|
|
50
|
+
]);
|
|
51
|
+
const computeDerived = useCallback(() => {
|
|
52
|
+
const currentScale = scale.get();
|
|
53
|
+
return {
|
|
54
|
+
canZoomIn: currentScale < maxScaleRef.current,
|
|
55
|
+
canZoomOut: currentScale > 1,
|
|
56
|
+
isClean: isCleanState(),
|
|
57
|
+
isZoomed: currentScale > 1,
|
|
58
|
+
rotation: normalizeRotation(rotate.get())
|
|
59
|
+
};
|
|
60
|
+
}, [
|
|
61
|
+
isCleanState,
|
|
62
|
+
rotate,
|
|
63
|
+
scale
|
|
64
|
+
]);
|
|
65
|
+
const [derived, setDerived] = useState(computeDerived);
|
|
66
|
+
const syncDerived = useCallback(() => {
|
|
67
|
+
setDerived((prev) => {
|
|
68
|
+
const next = computeDerived();
|
|
69
|
+
if (prev.canZoomIn === next.canZoomIn && prev.canZoomOut === next.canZoomOut && prev.isClean === next.isClean && prev.isZoomed === next.isZoomed && prev.rotation === next.rotation) return prev;
|
|
70
|
+
return next;
|
|
71
|
+
});
|
|
72
|
+
}, [computeDerived]);
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
const unsubscribers = [
|
|
75
|
+
scale,
|
|
76
|
+
rotate,
|
|
77
|
+
flipX,
|
|
78
|
+
flipY
|
|
79
|
+
].map((value) => value.on("change", syncDerived));
|
|
80
|
+
syncDerived();
|
|
81
|
+
return () => {
|
|
82
|
+
for (const unsubscribe of unsubscribers) unsubscribe();
|
|
83
|
+
};
|
|
84
|
+
}, [
|
|
85
|
+
flipX,
|
|
86
|
+
flipY,
|
|
87
|
+
rotate,
|
|
88
|
+
scale,
|
|
89
|
+
syncDerived
|
|
90
|
+
]);
|
|
91
|
+
const getFitRect = useCallback(() => computeFit(naturalRef.current, viewportRef.current, normalizeRotation(rotate.get()), fillViewportRef.current), [rotate]);
|
|
92
|
+
const applyTransform = useCallback((next, fitRect) => {
|
|
93
|
+
const clamped = clampPan(next, fitRect, viewportRef.current);
|
|
94
|
+
scale.jump(next.scale);
|
|
95
|
+
x.jump(clamped.x);
|
|
96
|
+
y.jump(clamped.y);
|
|
97
|
+
}, [
|
|
98
|
+
scale,
|
|
99
|
+
x,
|
|
100
|
+
y
|
|
101
|
+
]);
|
|
102
|
+
const animateTransform = useCallback((next, fitRect) => {
|
|
103
|
+
const clamped = clampPan(next, fitRect, viewportRef.current);
|
|
104
|
+
animate(scale, next.scale, RESET_TRANSITION);
|
|
105
|
+
animate(x, clamped.x, RESET_TRANSITION);
|
|
106
|
+
animate(y, clamped.y, RESET_TRANSITION);
|
|
107
|
+
}, [
|
|
108
|
+
scale,
|
|
109
|
+
x,
|
|
110
|
+
y
|
|
111
|
+
]);
|
|
112
|
+
const zoomBy = useCallback((factor) => {
|
|
113
|
+
if (closing()) return;
|
|
114
|
+
const currentScale = scale.get();
|
|
115
|
+
const targetScale = clampScale(currentScale * factor, maxScaleRef.current);
|
|
116
|
+
const fitRect = getFitRect();
|
|
117
|
+
const anchor = {
|
|
118
|
+
x: viewportRef.current.width / 2,
|
|
119
|
+
y: viewportRef.current.height / 2
|
|
120
|
+
};
|
|
121
|
+
const next = anchoredZoom({
|
|
122
|
+
scale: currentScale,
|
|
123
|
+
x: x.get(),
|
|
124
|
+
y: y.get()
|
|
125
|
+
}, targetScale, anchor, fitRect);
|
|
126
|
+
animateTransform(next, fitRect);
|
|
127
|
+
}, [
|
|
128
|
+
animateTransform,
|
|
129
|
+
closing,
|
|
130
|
+
getFitRect,
|
|
131
|
+
scale,
|
|
132
|
+
x,
|
|
133
|
+
y
|
|
134
|
+
]);
|
|
135
|
+
const zoomIn = useCallback(() => zoomBy(ZOOM_STEP), [zoomBy]);
|
|
136
|
+
const zoomOut = useCallback(() => zoomBy(1 / ZOOM_STEP), [zoomBy]);
|
|
137
|
+
const handleDoubleClick = useCallback((point) => {
|
|
138
|
+
if (closing()) return;
|
|
139
|
+
const currentScale = scale.get();
|
|
140
|
+
const fitRect = getFitRect();
|
|
141
|
+
const target = doubleClickTarget(currentScale, naturalRef.current, fitRect, normalizeRotation(rotate.get()));
|
|
142
|
+
const targetScale = clampScale(target, maxScaleRef.current);
|
|
143
|
+
const next = anchoredZoom({
|
|
144
|
+
scale: currentScale,
|
|
145
|
+
x: x.get(),
|
|
146
|
+
y: y.get()
|
|
147
|
+
}, targetScale, point, fitRect);
|
|
148
|
+
animateTransform(next, fitRect);
|
|
149
|
+
}, [
|
|
150
|
+
animateTransform,
|
|
151
|
+
closing,
|
|
152
|
+
getFitRect,
|
|
153
|
+
rotate,
|
|
154
|
+
scale,
|
|
155
|
+
x,
|
|
156
|
+
y
|
|
157
|
+
]);
|
|
158
|
+
const handleWheel = useCallback((event) => {
|
|
159
|
+
event.preventDefault?.();
|
|
160
|
+
if (closing()) return;
|
|
161
|
+
if (idleTimerRef.current !== null) clearTimeout(idleTimerRef.current);
|
|
162
|
+
idleTimerRef.current = setTimeout(() => {
|
|
163
|
+
idleTimerRef.current = null;
|
|
164
|
+
closeAccumRef.current = 0;
|
|
165
|
+
disarmedRef.current = false;
|
|
166
|
+
}, WHEEL_IDLE_MS);
|
|
167
|
+
const currentScale = scale.get();
|
|
168
|
+
if (!!(event.ctrlKey || event.metaKey) || currentScale > 1) {
|
|
169
|
+
disarmedRef.current = true;
|
|
170
|
+
closeAccumRef.current = 0;
|
|
171
|
+
const rawTarget = currentScale * wheelZoomFactor(event.deltaY);
|
|
172
|
+
if (rawTarget <= 1) {
|
|
173
|
+
animate(scale, 1, RESET_TRANSITION);
|
|
174
|
+
animate(x, 0, RESET_TRANSITION);
|
|
175
|
+
animate(y, 0, RESET_TRANSITION);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const targetScale = clampScale(rawTarget, maxScaleRef.current);
|
|
179
|
+
const fitRect = getFitRect();
|
|
180
|
+
const anchor = {
|
|
181
|
+
x: event.clientX,
|
|
182
|
+
y: event.clientY
|
|
183
|
+
};
|
|
184
|
+
const next = anchoredZoom({
|
|
185
|
+
scale: currentScale,
|
|
186
|
+
x: x.get(),
|
|
187
|
+
y: y.get()
|
|
188
|
+
}, targetScale, anchor, fitRect);
|
|
189
|
+
applyTransform(next, fitRect);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (disarmedRef.current || !isCleanState()) return;
|
|
193
|
+
closeAccumRef.current += Math.abs(event.deltaY);
|
|
194
|
+
if (closeAccumRef.current >= WHEEL_CLOSE_THRESHOLD) {
|
|
195
|
+
closeAccumRef.current = 0;
|
|
196
|
+
onCloseRequestRef.current?.();
|
|
197
|
+
}
|
|
198
|
+
}, [
|
|
199
|
+
applyTransform,
|
|
200
|
+
closing,
|
|
201
|
+
getFitRect,
|
|
202
|
+
isCleanState,
|
|
203
|
+
scale,
|
|
204
|
+
x,
|
|
205
|
+
y
|
|
206
|
+
]);
|
|
207
|
+
const rotateBy = useCallback((delta) => {
|
|
208
|
+
if (closing()) return;
|
|
209
|
+
rotate.jump(normalizeRotation(rotate.get() + delta));
|
|
210
|
+
scale.jump(1);
|
|
211
|
+
x.jump(0);
|
|
212
|
+
y.jump(0);
|
|
213
|
+
}, [
|
|
214
|
+
closing,
|
|
215
|
+
rotate,
|
|
216
|
+
scale,
|
|
217
|
+
x,
|
|
218
|
+
y
|
|
219
|
+
]);
|
|
220
|
+
const rotateLeft = useCallback(() => rotateBy(-90), [rotateBy]);
|
|
221
|
+
const rotateRight = useCallback(() => rotateBy(90), [rotateBy]);
|
|
222
|
+
const flipHorizontal = useCallback(() => {
|
|
223
|
+
if (closing()) return;
|
|
224
|
+
flipX.set(!flipX.get());
|
|
225
|
+
}, [closing, flipX]);
|
|
226
|
+
const flipVertical = useCallback(() => {
|
|
227
|
+
if (closing()) return;
|
|
228
|
+
flipY.set(!flipY.get());
|
|
229
|
+
}, [closing, flipY]);
|
|
230
|
+
const reset = useCallback(() => {
|
|
231
|
+
if (closing()) return;
|
|
232
|
+
animate(scale, 1, RESET_TRANSITION);
|
|
233
|
+
animate(x, 0, RESET_TRANSITION);
|
|
234
|
+
animate(y, 0, RESET_TRANSITION);
|
|
235
|
+
animate(rotate, 0, RESET_TRANSITION);
|
|
236
|
+
flipX.set(false);
|
|
237
|
+
flipY.set(false);
|
|
238
|
+
}, [
|
|
239
|
+
closing,
|
|
240
|
+
flipX,
|
|
241
|
+
flipY,
|
|
242
|
+
rotate,
|
|
243
|
+
scale,
|
|
244
|
+
x,
|
|
245
|
+
y
|
|
246
|
+
]);
|
|
247
|
+
const dragBy = useCallback((delta) => {
|
|
248
|
+
if (closing()) return;
|
|
249
|
+
const fitRect = getFitRect();
|
|
250
|
+
const bounds = panBounds({
|
|
251
|
+
scale: scale.get(),
|
|
252
|
+
x: 0,
|
|
253
|
+
y: 0
|
|
254
|
+
}, fitRect, viewportRef.current);
|
|
255
|
+
x.set(rubberBand(x.get() + delta.x, bounds.x.min, bounds.x.max));
|
|
256
|
+
y.set(rubberBand(y.get() + delta.y, bounds.y.min, bounds.y.max));
|
|
257
|
+
}, [
|
|
258
|
+
closing,
|
|
259
|
+
getFitRect,
|
|
260
|
+
scale,
|
|
261
|
+
x,
|
|
262
|
+
y
|
|
263
|
+
]);
|
|
264
|
+
const dragEnd = useCallback(() => {
|
|
265
|
+
if (closing()) return;
|
|
266
|
+
const fitRect = getFitRect();
|
|
267
|
+
const clamped = clampPan({
|
|
268
|
+
scale: scale.get(),
|
|
269
|
+
x: x.get(),
|
|
270
|
+
y: y.get()
|
|
271
|
+
}, fitRect, viewportRef.current);
|
|
272
|
+
animate(x, clamped.x, RESET_TRANSITION);
|
|
273
|
+
animate(y, clamped.y, RESET_TRANSITION);
|
|
274
|
+
}, [
|
|
275
|
+
closing,
|
|
276
|
+
getFitRect,
|
|
277
|
+
scale,
|
|
278
|
+
x,
|
|
279
|
+
y
|
|
280
|
+
]);
|
|
281
|
+
const escIntent = useCallback(() => isCleanState() ? "close" : "reset", [isCleanState]);
|
|
282
|
+
const setViewport = useCallback((next) => {
|
|
283
|
+
viewportRef.current = next;
|
|
284
|
+
const fitRect = computeFit(naturalRef.current, next, normalizeRotation(rotate.get()), fillViewportRef.current);
|
|
285
|
+
const clamped = clampPan({
|
|
286
|
+
scale: scale.get(),
|
|
287
|
+
x: x.get(),
|
|
288
|
+
y: y.get()
|
|
289
|
+
}, fitRect, next);
|
|
290
|
+
x.set(clamped.x);
|
|
291
|
+
y.set(clamped.y);
|
|
292
|
+
}, [
|
|
293
|
+
rotate,
|
|
294
|
+
scale,
|
|
295
|
+
x,
|
|
296
|
+
y
|
|
297
|
+
]);
|
|
298
|
+
const setNatural = useCallback((next) => {
|
|
299
|
+
naturalRef.current = next;
|
|
300
|
+
const fitRect = computeFit(next, viewportRef.current, normalizeRotation(rotate.get()), fillViewportRef.current);
|
|
301
|
+
const clamped = clampPan({
|
|
302
|
+
scale: scale.get(),
|
|
303
|
+
x: x.get(),
|
|
304
|
+
y: y.get()
|
|
305
|
+
}, fitRect, viewportRef.current);
|
|
306
|
+
x.set(clamped.x);
|
|
307
|
+
y.set(clamped.y);
|
|
308
|
+
}, [
|
|
309
|
+
rotate,
|
|
310
|
+
scale,
|
|
311
|
+
x,
|
|
312
|
+
y
|
|
313
|
+
]);
|
|
314
|
+
return {
|
|
315
|
+
canZoomIn: derived.canZoomIn,
|
|
316
|
+
canZoomOut: derived.canZoomOut,
|
|
317
|
+
dragBy,
|
|
318
|
+
dragEnd,
|
|
319
|
+
escIntent,
|
|
320
|
+
flipHorizontal,
|
|
321
|
+
flipVertical,
|
|
322
|
+
flipX,
|
|
323
|
+
flipY,
|
|
324
|
+
handleDoubleClick,
|
|
325
|
+
handleWheel,
|
|
326
|
+
isClean: derived.isClean,
|
|
327
|
+
isZoomed: derived.isZoomed,
|
|
328
|
+
reset,
|
|
329
|
+
resetCloseTracking,
|
|
330
|
+
rotate,
|
|
331
|
+
rotateLeft,
|
|
332
|
+
rotateRight,
|
|
333
|
+
rotation: derived.rotation,
|
|
334
|
+
scale,
|
|
335
|
+
setNatural,
|
|
336
|
+
setViewport,
|
|
337
|
+
x,
|
|
338
|
+
y,
|
|
339
|
+
zoomIn,
|
|
340
|
+
zoomOut
|
|
341
|
+
};
|
|
342
|
+
};
|
|
343
|
+
//#endregion
|
|
344
|
+
export { useZoomPan };
|
|
345
|
+
|
|
346
|
+
//# sourceMappingURL=useZoomPan.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useZoomPan.mjs","names":[],"sources":["../../../src/Image/viewer/useZoomPan.ts"],"sourcesContent":["import type { MotionValue } from 'motion/react';\nimport { animate, motionValue } from 'motion/react';\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nimport type { Point, Rect, Rotation, Size, TransformState } from './geometry';\nimport {\n anchoredZoom,\n clampPan,\n clampScale,\n computeFit,\n DEFAULT_MAX_SCALE,\n doubleClickTarget,\n normalizeRotation,\n panBounds,\n rubberBand,\n wheelZoomFactor,\n} from './geometry';\n\nconst WHEEL_CLOSE_THRESHOLD = 100;\nconst WHEEL_IDLE_MS = 300;\nconst ZOOM_STEP = 1.5;\nconst RESET_TRANSITION = { damping: 30, stiffness: 300, type: 'spring' as const };\nconst CLEAN_EPSILON = 0.01;\n\nexport interface WheelLikeEvent {\n clientX: number;\n clientY: number;\n ctrlKey?: boolean;\n deltaY: number;\n metaKey?: boolean;\n preventDefault?: () => void;\n}\n\nexport interface UseZoomPanOptions {\n fillViewport?: boolean;\n isClosing?: () => boolean;\n maxScale?: number;\n natural: Size;\n onCloseRequest?: () => void;\n viewport: Size;\n}\n\nexport interface UseZoomPanResult {\n canZoomIn: boolean;\n canZoomOut: boolean;\n dragBy: (delta: Point) => void;\n dragEnd: () => void;\n escIntent: () => 'reset' | 'close';\n flipHorizontal: () => void;\n flipVertical: () => void;\n flipX: MotionValue<boolean>;\n flipY: MotionValue<boolean>;\n handleDoubleClick: (point: Point) => void;\n handleWheel: (event: WheelLikeEvent) => void;\n isClean: boolean;\n isZoomed: boolean;\n reset: () => void;\n resetCloseTracking: () => void;\n rotate: MotionValue<number>;\n rotateLeft: () => void;\n rotateRight: () => void;\n rotation: Rotation;\n scale: MotionValue<number>;\n setNatural: (natural: Size) => void;\n setViewport: (viewport: Size) => void;\n x: MotionValue<number>;\n y: MotionValue<number>;\n zoomIn: () => void;\n zoomOut: () => void;\n}\n\ninterface DerivedState {\n canZoomIn: boolean;\n canZoomOut: boolean;\n isClean: boolean;\n isZoomed: boolean;\n rotation: Rotation;\n}\n\nexport const useZoomPan = ({\n natural,\n viewport,\n maxScale,\n onCloseRequest,\n isClosing,\n fillViewport,\n}: UseZoomPanOptions): UseZoomPanResult => {\n const [{ scale, x, y, rotate, flipX, flipY }] = useState(() => ({\n flipX: motionValue(false),\n flipY: motionValue(false),\n rotate: motionValue(0),\n scale: motionValue(1),\n x: motionValue(0),\n y: motionValue(0),\n }));\n\n const naturalRef = useRef(natural);\n const viewportRef = useRef(viewport);\n const maxScaleRef = useRef(maxScale ?? DEFAULT_MAX_SCALE);\n maxScaleRef.current = maxScale ?? DEFAULT_MAX_SCALE;\n const fillViewportRef = useRef(fillViewport ?? false);\n fillViewportRef.current = fillViewport ?? false;\n\n const onCloseRequestRef = useRef(onCloseRequest);\n onCloseRequestRef.current = onCloseRequest;\n\n const isClosingRef = useRef(isClosing);\n isClosingRef.current = isClosing;\n\n const closeAccumRef = useRef(0);\n const disarmedRef = useRef(false);\n const idleTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const resetCloseTracking = useCallback(() => {\n if (idleTimerRef.current !== null) {\n clearTimeout(idleTimerRef.current);\n idleTimerRef.current = null;\n }\n closeAccumRef.current = 0;\n disarmedRef.current = false;\n }, []);\n\n // The close FLIP spring animates these same scale/x/y values. Any of\n // these entry points still firing during the ~300ms close window would\n // either fight that spring visually or, for wheel/reset specifically,\n // race escIntent()/isCleanState() into cancelling the close outright.\n const closing = useCallback(() => isClosingRef.current?.() ?? false, []);\n\n const isCleanState = useCallback(\n () =>\n Math.abs(scale.get() - 1) < CLEAN_EPSILON &&\n Math.abs(rotate.get()) < CLEAN_EPSILON &&\n !flipX.get() &&\n !flipY.get(),\n [flipX, flipY, rotate, scale],\n );\n\n const computeDerived = useCallback((): DerivedState => {\n const currentScale = scale.get();\n return {\n canZoomIn: currentScale < maxScaleRef.current,\n canZoomOut: currentScale > 1,\n isClean: isCleanState(),\n isZoomed: currentScale > 1,\n rotation: normalizeRotation(rotate.get()),\n };\n }, [isCleanState, rotate, scale]);\n\n const [derived, setDerived] = useState<DerivedState>(computeDerived);\n\n const syncDerived = useCallback(() => {\n setDerived((prev) => {\n const next = computeDerived();\n if (\n prev.canZoomIn === next.canZoomIn &&\n prev.canZoomOut === next.canZoomOut &&\n prev.isClean === next.isClean &&\n prev.isZoomed === next.isZoomed &&\n prev.rotation === next.rotation\n ) {\n return prev;\n }\n return next;\n });\n }, [computeDerived]);\n\n useEffect(() => {\n const unsubscribers = [scale, rotate, flipX, flipY].map((value) =>\n value.on('change', syncDerived),\n );\n syncDerived();\n return () => {\n for (const unsubscribe of unsubscribers) unsubscribe();\n };\n }, [flipX, flipY, rotate, scale, syncDerived]);\n\n const getFitRect = useCallback(\n () =>\n computeFit(\n naturalRef.current,\n viewportRef.current,\n normalizeRotation(rotate.get()),\n fillViewportRef.current,\n ),\n [rotate],\n );\n\n const applyTransform = useCallback(\n (next: TransformState, fitRect: Rect) => {\n const clamped = clampPan(next, fitRect, viewportRef.current);\n // .jump(), not .set(): a surviving spring on these same values (e.g. a\n // dragEnd clamp-back or wheel snap-back still in flight) would otherwise\n // keep animating toward its own target and silently overwrite this\n // instant write on its next tick — motion's set() never stops an\n // active animation, only jump() does.\n scale.jump(next.scale);\n x.jump(clamped.x);\n y.jump(clamped.y);\n },\n [scale, x, y],\n );\n\n // Wheel/pinch stays on the instant applyTransform for 1:1 gesture tracking;\n // stepped inputs (toolbar/keyboard zoom, double-click) animate instead.\n const animateTransform = useCallback(\n (next: TransformState, fitRect: Rect) => {\n const clamped = clampPan(next, fitRect, viewportRef.current);\n animate(scale, next.scale, RESET_TRANSITION);\n animate(x, clamped.x, RESET_TRANSITION);\n animate(y, clamped.y, RESET_TRANSITION);\n },\n [scale, x, y],\n );\n\n const zoomBy = useCallback(\n (factor: number) => {\n if (closing()) return;\n const currentScale = scale.get();\n const targetScale = clampScale(currentScale * factor, maxScaleRef.current);\n const fitRect = getFitRect();\n const anchor: Point = { x: viewportRef.current.width / 2, y: viewportRef.current.height / 2 };\n const next = anchoredZoom(\n { scale: currentScale, x: x.get(), y: y.get() },\n targetScale,\n anchor,\n fitRect,\n );\n animateTransform(next, fitRect);\n },\n [animateTransform, closing, getFitRect, scale, x, y],\n );\n\n const zoomIn = useCallback(() => zoomBy(ZOOM_STEP), [zoomBy]);\n const zoomOut = useCallback(() => zoomBy(1 / ZOOM_STEP), [zoomBy]);\n\n const handleDoubleClick = useCallback(\n (point: Point) => {\n if (closing()) return;\n const currentScale = scale.get();\n const fitRect = getFitRect();\n const target = doubleClickTarget(\n currentScale,\n naturalRef.current,\n fitRect,\n normalizeRotation(rotate.get()),\n );\n const targetScale = clampScale(target, maxScaleRef.current);\n const next = anchoredZoom(\n { scale: currentScale, x: x.get(), y: y.get() },\n targetScale,\n point,\n fitRect,\n );\n animateTransform(next, fitRect);\n },\n [animateTransform, closing, getFitRect, rotate, scale, x, y],\n );\n\n const handleWheel = useCallback(\n (event: WheelLikeEvent) => {\n event.preventDefault?.();\n if (closing()) return;\n\n if (idleTimerRef.current !== null) clearTimeout(idleTimerRef.current);\n idleTimerRef.current = setTimeout(() => {\n idleTimerRef.current = null;\n closeAccumRef.current = 0;\n disarmedRef.current = false;\n }, WHEEL_IDLE_MS);\n\n const currentScale = scale.get();\n const isPinch = !!(event.ctrlKey || event.metaKey);\n const zoomed = currentScale > 1;\n\n if (isPinch || zoomed) {\n disarmedRef.current = true;\n closeAccumRef.current = 0;\n\n const rawTarget = currentScale * wheelZoomFactor(event.deltaY);\n\n if (rawTarget <= 1) {\n animate(scale, 1, RESET_TRANSITION);\n animate(x, 0, RESET_TRANSITION);\n animate(y, 0, RESET_TRANSITION);\n return;\n }\n\n const targetScale = clampScale(rawTarget, maxScaleRef.current);\n const fitRect = getFitRect();\n const anchor: Point = { x: event.clientX, y: event.clientY };\n const next = anchoredZoom(\n { scale: currentScale, x: x.get(), y: y.get() },\n targetScale,\n anchor,\n fitRect,\n );\n applyTransform(next, fitRect);\n return;\n }\n\n if (disarmedRef.current || !isCleanState()) return;\n\n closeAccumRef.current += Math.abs(event.deltaY);\n if (closeAccumRef.current >= WHEEL_CLOSE_THRESHOLD) {\n closeAccumRef.current = 0;\n onCloseRequestRef.current?.();\n }\n },\n [applyTransform, closing, getFitRect, isCleanState, scale, x, y],\n );\n\n const rotateBy = useCallback(\n (delta: 90 | -90) => {\n if (closing()) return;\n rotate.jump(normalizeRotation(rotate.get() + delta));\n scale.jump(1);\n x.jump(0);\n y.jump(0);\n },\n [closing, rotate, scale, x, y],\n );\n\n const rotateLeft = useCallback(() => rotateBy(-90), [rotateBy]);\n const rotateRight = useCallback(() => rotateBy(90), [rotateBy]);\n\n const flipHorizontal = useCallback(() => {\n if (closing()) return;\n flipX.set(!flipX.get());\n }, [closing, flipX]);\n\n const flipVertical = useCallback(() => {\n if (closing()) return;\n flipY.set(!flipY.get());\n }, [closing, flipY]);\n\n const reset = useCallback(() => {\n if (closing()) return;\n animate(scale, 1, RESET_TRANSITION);\n animate(x, 0, RESET_TRANSITION);\n animate(y, 0, RESET_TRANSITION);\n animate(rotate, 0, RESET_TRANSITION);\n flipX.set(false);\n flipY.set(false);\n }, [closing, flipX, flipY, rotate, scale, x, y]);\n\n const dragBy = useCallback(\n (delta: Point) => {\n // A pointer can still be down when close starts (e.g. pinch to just\n // over 1x — inside isClean's epsilon, so Esc/wheel reads 'close' —\n // while the same gesture is still panning). Gated for consistency\n // with the other entry points even though .set() here can't cancel\n // the close spring outright, only cause a self-correcting flicker.\n if (closing()) return;\n const fitRect = getFitRect();\n const bounds = panBounds({ scale: scale.get(), x: 0, y: 0 }, fitRect, viewportRef.current);\n x.set(rubberBand(x.get() + delta.x, bounds.x.min, bounds.x.max));\n y.set(rubberBand(y.get() + delta.y, bounds.y.min, bounds.y.max));\n },\n [closing, getFitRect, scale, x, y],\n );\n\n const dragEnd = useCallback(() => {\n if (closing()) return;\n const fitRect = getFitRect();\n const clamped = clampPan(\n { scale: scale.get(), x: x.get(), y: y.get() },\n fitRect,\n viewportRef.current,\n );\n animate(x, clamped.x, RESET_TRANSITION);\n animate(y, clamped.y, RESET_TRANSITION);\n }, [closing, getFitRect, scale, x, y]);\n\n const escIntent = useCallback(\n (): 'reset' | 'close' => (isCleanState() ? 'close' : 'reset'),\n [isCleanState],\n );\n\n const setViewport = useCallback(\n (next: Size) => {\n viewportRef.current = next;\n const fitRect = computeFit(\n naturalRef.current,\n next,\n normalizeRotation(rotate.get()),\n fillViewportRef.current,\n );\n const clamped = clampPan({ scale: scale.get(), x: x.get(), y: y.get() }, fitRect, next);\n x.set(clamped.x);\n y.set(clamped.y);\n },\n [rotate, scale, x, y],\n );\n\n const setNatural = useCallback(\n (next: Size) => {\n naturalRef.current = next;\n const fitRect = computeFit(\n next,\n viewportRef.current,\n normalizeRotation(rotate.get()),\n fillViewportRef.current,\n );\n const clamped = clampPan(\n { scale: scale.get(), x: x.get(), y: y.get() },\n fitRect,\n viewportRef.current,\n );\n x.set(clamped.x);\n y.set(clamped.y);\n },\n [rotate, scale, x, y],\n );\n\n return {\n canZoomIn: derived.canZoomIn,\n canZoomOut: derived.canZoomOut,\n dragBy,\n dragEnd,\n escIntent,\n flipHorizontal,\n flipVertical,\n flipX,\n flipY,\n handleDoubleClick,\n handleWheel,\n isClean: derived.isClean,\n isZoomed: derived.isZoomed,\n reset,\n resetCloseTracking,\n rotate,\n rotateLeft,\n rotateRight,\n rotation: derived.rotation,\n scale,\n setNatural,\n setViewport,\n x,\n y,\n zoomIn,\n zoomOut,\n };\n};\n\nexport type { Point, Rotation, Size } from './geometry';\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
|
}
|