1- import { useMemo } from "react" ;
1+ import { useEffect , useMemo , useRef } from "react" ;
22import { motion } from "framer-motion" ;
33import { feedbackClass , type CharacterFeedback } from "../../../shared/feedback/feedback" ;
44import { getSyntaxColorClass , getTokenTypeAtIndex , type SyntaxToken } from "../../../shared/syntax/syntax" ;
@@ -23,6 +23,8 @@ export function TypingFeedback({
2323 fontSize,
2424 syntaxThemeId,
2525} : TypingFeedbackProps ) {
26+ const activeCursorRef = useRef < HTMLSpanElement > ( null ) ;
27+
2628 const lineCount = useMemo ( ( ) => {
2729 let count = 1 ;
2830 feedback . forEach ( ( item ) => {
@@ -33,6 +35,38 @@ export function TypingFeedback({
3335 return count ;
3436 } , [ feedback ] ) ;
3537
38+ useEffect ( ( ) => {
39+ if ( activeCursorRef . current ) {
40+ const element = activeCursorRef . current ;
41+ const container = element . closest ( '.scrollbar-auto-hide' ) as HTMLElement ;
42+
43+ 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+ }
65+ }
66+ }
67+ }
68+ } , [ typedLength ] ) ;
69+
3670 return (
3771 < div className = { typingFeedbackStyles . wrapper } >
3872 < div
@@ -60,6 +94,7 @@ export function TypingFeedback({
6094 return (
6195 < span
6296 key = { item . index }
97+ ref = { isActive ? activeCursorRef : undefined }
6398 className = { typingFeedbackStyles . pendingSpan ( syntaxClass ) }
6499 >
65100 { item . char }
@@ -82,6 +117,7 @@ export function TypingFeedback({
82117 return (
83118 < span
84119 key = { item . index }
120+ ref = { isActive ? activeCursorRef : undefined }
85121 className = { typingFeedbackStyles . typedSpan ( syntaxClass , decorationClass ) }
86122 title = {
87123 item . typed && item . typed !== item . char
0 commit comments