refactor: Update useScrollAnchor to handle user scrolling and overflow
This commit is contained in:
		
							parent
							
								
									1e9b66d823
								
							
						
					
					
						commit
						5ac03f5123
					
				| @ -1,87 +1,106 @@ | |||||||
| import { useCallback, useEffect, useRef, useState } from "react" | import { useCallback, useEffect, useRef, useState } from "react" | ||||||
|  | import { useMessageOption } from "./useMessageOption" | ||||||
| 
 | 
 | ||||||
| export const useScrollAnchor = () => { | export const useScrollAnchor = () => { | ||||||
|   const messagesRef = useRef<HTMLDivElement>(null) |   const { isProcessing, messages } = useMessageOption() | ||||||
|   const scrollRef = useRef<HTMLDivElement>(null) |  | ||||||
|   const visibilityRef = useRef<HTMLDivElement>(null) |  | ||||||
| 
 | 
 | ||||||
|  |   const [isAtTop, setIsAtTop] = useState(false) | ||||||
|   const [isAtBottom, setIsAtBottom] = useState(true) |   const [isAtBottom, setIsAtBottom] = useState(true) | ||||||
|   const [isVisible, setIsVisible] = useState(false) |   const [userScrolled, setUserScrolled] = useState(false) | ||||||
|  |   const [isOverflowing, setIsOverflowing] = useState(false) | ||||||
|  | 
 | ||||||
|  |   const messagesStartRef = useRef<HTMLDivElement>(null) | ||||||
|  |   const messagesEndRef = useRef<HTMLDivElement>(null) | ||||||
|  |   const containerRef = useRef<HTMLDivElement>(null) | ||||||
|  |   const isAutoScrolling = useRef(false) | ||||||
|  | 
 | ||||||
|  |   console.log(`isAtTop: ${isAtTop}, isAtBottom: ${isAtBottom}, userScrolled: ${userScrolled}, isOverflowing: ${isOverflowing}`) | ||||||
|  | 
 | ||||||
|  |   useEffect(() => { | ||||||
|  |     if (!isProcessing && userScrolled) { | ||||||
|  |       console.log("userScrolled") | ||||||
|  |       setUserScrolled(false) | ||||||
|  |     } | ||||||
|  |   }, [isProcessing]) | ||||||
|  | 
 | ||||||
|  |   useEffect(() => { | ||||||
|  |     if (isProcessing && !userScrolled) { | ||||||
|  |       scrollToBottom() | ||||||
|  |     } | ||||||
|  |   }, [messages]) | ||||||
|  | 
 | ||||||
|  |   useEffect(() => { | ||||||
|  |     const container = containerRef.current | ||||||
|  |     if (!container) return | ||||||
|  | 
 | ||||||
|  |     const topObserver = new IntersectionObserver( | ||||||
|  |       ([entry]) => { | ||||||
|  |         setIsAtTop(entry.isIntersecting) | ||||||
|  |       }, | ||||||
|  |       { threshold: 1 } | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     const bottomObserver = new IntersectionObserver( | ||||||
|  |       ([entry]) => { | ||||||
|  |         setIsAtBottom(entry.isIntersecting) | ||||||
|  |         if (entry.isIntersecting) { | ||||||
|  |           setUserScrolled(false) | ||||||
|  |         } else if (!isAutoScrolling.current) { | ||||||
|  |           setUserScrolled(true) | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       { threshold: 1 } | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     if (messagesStartRef.current) { | ||||||
|  |       topObserver.observe(messagesStartRef.current) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (messagesEndRef.current) { | ||||||
|  |       bottomObserver.observe(messagesEndRef.current) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const resizeObserver = new ResizeObserver(() => { | ||||||
|  |       setIsOverflowing(container.scrollHeight > container.clientHeight) | ||||||
|  |     }) | ||||||
|  | 
 | ||||||
|  |     resizeObserver.observe(container) | ||||||
|  | 
 | ||||||
|  |     return () => { | ||||||
|  |       topObserver.disconnect() | ||||||
|  |       bottomObserver.disconnect() | ||||||
|  |       resizeObserver.disconnect() | ||||||
|  |     } | ||||||
|  |   }, []) | ||||||
|  | 
 | ||||||
|  |   const scrollToTop = useCallback(() => { | ||||||
|  |     if (messagesStartRef.current) { | ||||||
|  |       messagesStartRef.current.scrollIntoView({ behavior: "smooth" }) | ||||||
|  |     } | ||||||
|  |   }, []) | ||||||
| 
 | 
 | ||||||
|   const scrollToBottom = useCallback(() => { |   const scrollToBottom = useCallback(() => { | ||||||
|     if (messagesRef.current) { |     isAutoScrolling.current = true | ||||||
|       messagesRef.current.scrollIntoView({ | 
 | ||||||
|         block: "end", |     setTimeout(() => { | ||||||
|         behavior: "smooth" |       if (messagesEndRef.current) { | ||||||
|       }) |         messagesEndRef.current.scrollIntoView({ behavior: "smooth" }) | ||||||
|     } |       } | ||||||
|  | 
 | ||||||
|  |       isAutoScrolling.current = false | ||||||
|  |     }, 100) | ||||||
|   }, []) |   }, []) | ||||||
| 
 | 
 | ||||||
|   useEffect(() => { |  | ||||||
|     if (messagesRef.current) { |  | ||||||
|       if (isAtBottom && !isVisible) { |  | ||||||
|         messagesRef.current.scrollIntoView({ |  | ||||||
|           block: "end" |  | ||||||
|         }) |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   }, [isAtBottom, isVisible]) |  | ||||||
| 
 |  | ||||||
|   useEffect(() => { |  | ||||||
|     const { current } = scrollRef |  | ||||||
| 
 |  | ||||||
|     if (current) { |  | ||||||
|       const handleScroll = (event: Event) => { |  | ||||||
|         const target = event.target as HTMLDivElement |  | ||||||
|         const offset = 25 |  | ||||||
|         const isAtBottom = |  | ||||||
|           target.scrollTop + target.clientHeight >= target.scrollHeight - offset |  | ||||||
|         console.log(target.scrollTop, target.clientHeight, target.scrollHeight) |  | ||||||
|         setIsAtBottom(isAtBottom) |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       current.addEventListener("scroll", handleScroll, { |  | ||||||
|         passive: true |  | ||||||
|       }) |  | ||||||
| 
 |  | ||||||
|       return () => { |  | ||||||
|         current.removeEventListener("scroll", handleScroll) |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   }, []) |  | ||||||
| 
 |  | ||||||
|   useEffect(() => { |  | ||||||
|     if (visibilityRef.current) { |  | ||||||
|       let observer = new IntersectionObserver( |  | ||||||
|         (entries) => { |  | ||||||
|           entries.forEach((entry) => { |  | ||||||
|             console.log(entry.isIntersecting) |  | ||||||
|             if (entry.isIntersecting) { |  | ||||||
|               setIsVisible(true) |  | ||||||
|             } else { |  | ||||||
|               setIsVisible(false) |  | ||||||
|             } |  | ||||||
|           }) |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           rootMargin: "0px 0px -100px 0px" |  | ||||||
|         } |  | ||||||
|       ) |  | ||||||
| 
 |  | ||||||
|       observer.observe(visibilityRef.current) |  | ||||||
| 
 |  | ||||||
|       return () => { |  | ||||||
|         observer.disconnect() |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   return { |   return { | ||||||
|     messagesRef, |     messagesStartRef, | ||||||
|     scrollRef, |     messagesEndRef, | ||||||
|     visibilityRef, |     containerRef, | ||||||
|     scrollToBottom, |     isAtTop, | ||||||
|     isAtBottom, |     isAtBottom, | ||||||
|     isVisible |     userScrolled, | ||||||
|  |     isOverflowing, | ||||||
|  |     scrollToTop, | ||||||
|  |     scrollToBottom, | ||||||
|  |     setIsAtBottom | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @ -39,3 +39,17 @@ export const getAdvancedOllamaSettings = async () => { | |||||||
| export const copilotResumeLastChat = async () => { | export const copilotResumeLastChat = async () => { | ||||||
|   return await storage.get<boolean>("copilotResumeLastChat") |   return await storage.get<boolean>("copilotResumeLastChat") | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | export const defaultSidebarOpen = async () => { | ||||||
|  |   const sidebarOpen = await storage.get("sidebarOpen") | ||||||
|  |   if (!sidebarOpen || sidebarOpen === "") { | ||||||
|  |     return "right_clk" | ||||||
|  |   } | ||||||
|  |   return sidebarOpen | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | export const setSidebarOpen = async (sidebarOpen: string) => { | ||||||
|  |   await storage.set("sidebarOpen", sidebarOpen) | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user