From 420e494439ddb60c1fca407e8fbd1e9f0e7d9e46 Mon Sep 17 00:00:00 2001 From: liuyingbo03 Date: Mon, 3 Nov 2025 10:48:29 +0800 Subject: [PATCH] feat: add disable/enable auto scroll API --- demo/Demo.tsx | 42 +++++++++++++++++++++++++---------------- src/StickToBottom.tsx | 10 ++++++++++ src/useStickToBottom.ts | 35 +++++++++++++++++++++++++++++----- 3 files changed, 66 insertions(+), 21 deletions(-) diff --git a/demo/Demo.tsx b/demo/Demo.tsx index ee8d828..1d1f051 100644 --- a/demo/Demo.tsx +++ b/demo/Demo.tsx @@ -8,7 +8,7 @@ function ScrollToBottom() { return ( !isAtBottom && ( + ); @@ -49,11 +59,11 @@ function Messages({ animation, speed }: { animation: ScrollBehavior; speed: numb const messages = useFakeMessages(speed); return ( -
-

{animation}:

+
+

{animation}:

@@ -67,10 +77,10 @@ export function Demo() { const [speed, setSpeed] = useState(0.2); return ( -
+
setSpeed(+e.target.value)} min={0} @@ -78,14 +88,14 @@ export function Demo() { step={0.01} > -
- - +
+ +
); } function Message({ children }: { children: React.ReactNode }) { - return
{children}
; + return
{children}
; } diff --git a/src/StickToBottom.tsx b/src/StickToBottom.tsx index 1799466..7831a07 100644 --- a/src/StickToBottom.tsx +++ b/src/StickToBottom.tsx @@ -21,6 +21,8 @@ import { type StickToBottomOptions, type StickToBottomState, type StopScroll, + type DisableAutoScroll, + type EnableAutoScroll, useStickToBottom, } from "./useStickToBottom.js"; @@ -31,6 +33,8 @@ export interface StickToBottomContext { React.RefCallback; scrollToBottom: ScrollToBottom; stopScroll: StopScroll; + disableAutoScroll: DisableAutoScroll; + enableAutoScroll: EnableAutoScroll; isAtBottom: boolean; escapedFromLock: boolean; get targetScrollTop(): GetTargetScrollTop | null; @@ -87,6 +91,8 @@ export function StickToBottom({ contentRef, scrollToBottom, stopScroll, + disableAutoScroll, + enableAutoScroll, isAtBottom, escapedFromLock, state, @@ -96,6 +102,8 @@ export function StickToBottom({ () => ({ scrollToBottom, stopScroll, + disableAutoScroll, + enableAutoScroll, scrollRef, isAtBottom, escapedFromLock, @@ -114,6 +122,8 @@ export function StickToBottom({ contentRef, scrollRef, stopScroll, + disableAutoScroll, + enableAutoScroll, escapedFromLock, state, ], diff --git a/src/useStickToBottom.ts b/src/useStickToBottom.ts index b6dbdf8..33077d3 100644 --- a/src/useStickToBottom.ts +++ b/src/useStickToBottom.ts @@ -127,6 +127,8 @@ export type ScrollToBottom = ( scrollOptions?: ScrollToBottomOptions, ) => Promise | boolean; export type StopScroll = () => void; +export type DisableAutoScroll = () => void; +export type EnableAutoScroll = () => void; const STICK_TO_BOTTOM_OFFSET_PX = 70; const SIXTY_FPS_INTERVAL_MS = 1000 / 60; @@ -152,7 +154,7 @@ export const useStickToBottom = ( const [escapedFromLock, updateEscapedFromLock] = useState(false); const [isAtBottom, updateIsAtBottom] = useState(options.initial !== false); const [isNearBottom, setIsNearBottom] = useState(false); - + const disableAutoScrollRef = useRef(false); const optionsRef = useRef(null!); optionsRef.current = options; @@ -353,7 +355,7 @@ export const useStickToBottom = ( * requested animatino. */ if (state.scrollTop < state.calculatedTargetScrollTop) { - return scrollToBottom({ + return internalScrollToBottom({ animation: mergeAnimations( optionsRef.current, optionsRef.current.resize, @@ -391,11 +393,31 @@ export const useStickToBottom = ( [setIsAtBottom, isSelecting, state], ); - const stopScroll = useCallback((): void => { + const internalScrollToBottom = useCallback( + (scrollOptions) => { + if (disableAutoScrollRef.current) return Promise.resolve(false); + return scrollToBottom(scrollOptions); + }, + [scrollToBottom], + ); + + const stopScroll = useCallback(() => { setEscapedFromLock(true); setIsAtBottom(false); }, [setEscapedFromLock, setIsAtBottom]); + const disableAutoScroll = useCallback(() => { + setEscapedFromLock(true); + setIsAtBottom(false); + disableAutoScrollRef.current = true; + }, [setEscapedFromLock, setIsAtBottom]); + + const enableAutoScroll = useCallback(() => { + setEscapedFromLock(true); + setIsAtBottom(false); + disableAutoScrollRef.current = false; + }, [setEscapedFromLock, setIsAtBottom]); + const handleScroll = useCallback( ({ target }: Event) => { if (target !== scrollRef.current) { @@ -538,8 +560,7 @@ export const useStickToBottom = ( ? optionsRef.current.resize : optionsRef.current.initial, ); - - scrollToBottom({ + internalScrollToBottom({ animation, wait: true, preserveScrollPosition: true, @@ -584,6 +605,8 @@ export const useStickToBottom = ( scrollRef, scrollToBottom, stopScroll, + disableAutoScroll, + enableAutoScroll, isAtBottom: isAtBottom || isNearBottom, isNearBottom, escapedFromLock, @@ -598,6 +621,8 @@ export interface StickToBottomInstance { React.RefCallback; scrollToBottom: ScrollToBottom; stopScroll: StopScroll; + disableAutoScroll: DisableAutoScroll; + enableAutoScroll: EnableAutoScroll; isAtBottom: boolean; isNearBottom: boolean; escapedFromLock: boolean;