@@ -24,6 +24,7 @@ export function TypingFeedback({
2424 syntaxThemeId,
2525} : TypingFeedbackProps ) {
2626 const activeCursorRef = useRef < HTMLSpanElement > ( null ) ;
27+ const scrollFrameRef = useRef < number | null > ( null ) ;
2728
2829 const lineCount = useMemo ( ( ) => {
2930 let count = 1 ;
@@ -41,30 +42,46 @@ export function TypingFeedback({
4142 const container = element . closest ( '.scrollbar-auto-hide' ) as HTMLElement ;
4243
4344 if ( container ) {
44- const rect = element . getBoundingClientRect ( ) ;
45- const containerRect = container . getBoundingClientRect ( ) ;
46-
47- // Only scroll down if cursor is below viewport, never scroll up
48- const buffer = 100 ; // pixels from bottom before triggering scroll
49- const isBelowView = rect . bottom > containerRect . bottom - buffer ;
50-
51- if ( isBelowView ) {
52- // Calculate position to keep cursor visible with buffer from bottom
53- const relativeTop = rect . top - containerRect . top + container . scrollTop ;
54- const elementHeight = rect . height ;
55- const containerHeight = container . clientHeight ;
56-
57- // Position cursor at 2/3 down the viewport (not centered, keeps more context above)
58- const targetPosition = containerHeight * 0.66 ;
59- const scrollTop = relativeTop - targetPosition + ( elementHeight / 2 ) ;
60-
61- // Only scroll if we need to go down
62- if ( scrollTop > container . scrollTop ) {
63- container . scrollTop = scrollTop ;
64- }
45+ // Cancel any pending scroll frame
46+ if ( scrollFrameRef . current !== null ) {
47+ cancelAnimationFrame ( scrollFrameRef . current ) ;
6548 }
49+
50+ // Schedule scroll update, but only one at a time
51+ scrollFrameRef . current = requestAnimationFrame ( ( ) => {
52+ scrollFrameRef . current = null ;
53+
54+ const rect = element . getBoundingClientRect ( ) ;
55+ const containerRect = container . getBoundingClientRect ( ) ;
56+
57+ // Only scroll down if cursor is below viewport, never scroll up
58+ const buffer = 100 ; // pixels from bottom before triggering scroll
59+ const isBelowView = rect . bottom > containerRect . bottom - buffer ;
60+
61+ if ( isBelowView ) {
62+ // Calculate position to keep cursor visible with buffer from bottom
63+ const relativeTop = rect . top - containerRect . top + container . scrollTop ;
64+ const containerHeight = container . clientHeight ;
65+
66+ // Position cursor at 2/3 down the viewport (not centered, keeps more context above)
67+ const targetPosition = containerHeight * 0.66 ;
68+ const scrollTop = relativeTop - targetPosition ;
69+
70+ // Only scroll if we need to go down
71+ if ( scrollTop > container . scrollTop ) {
72+ container . scrollTop = scrollTop ;
73+ }
74+ }
75+ } ) ;
6676 }
6777 }
78+
79+ // Cleanup on unmount
80+ return ( ) => {
81+ if ( scrollFrameRef . current !== null ) {
82+ cancelAnimationFrame ( scrollFrameRef . current ) ;
83+ }
84+ } ;
6885 } , [ typedLength ] ) ;
6986
7087 return (
0 commit comments