@@ -124,6 +124,9 @@ export function VTabBar({ workspace, className }: VTabBarProps) {
124124 const dragSourceRef = useRef < string | null > ( null ) ;
125125 const didResetHoverForDragRef = useRef ( false ) ;
126126 const scrollContainerRef = useRef < HTMLDivElement > ( null ) ;
127+ const scrollAnimFrameRef = useRef < number | null > ( null ) ;
128+ const scrollDirectionRef = useRef < number > ( 0 ) ;
129+ const scrollSpeedRef = useRef < number > ( 0 ) ;
127130
128131 useEffect ( ( ) => {
129132 setOrderedTabIds ( tabIds ) ;
@@ -143,7 +146,59 @@ export function VTabBar({ workspace, className }: VTabBarProps) {
143146 el ?. scrollIntoView ( { block : "nearest" } ) ;
144147 } , [ activeTabId , documentHasFocus ] ) ;
145148
149+ const stopScrollLoop = useCallback ( ( ) => {
150+ if ( scrollAnimFrameRef . current != null ) {
151+ cancelAnimationFrame ( scrollAnimFrameRef . current ) ;
152+ scrollAnimFrameRef . current = null ;
153+ }
154+ scrollDirectionRef . current = 0 ;
155+ } , [ ] ) ;
156+
157+ const startScrollLoop = useCallback ( ( ) => {
158+ if ( scrollAnimFrameRef . current != null ) {
159+ return ;
160+ }
161+ const loop = ( ) => {
162+ const container = scrollContainerRef . current ;
163+ if ( container == null || scrollDirectionRef . current === 0 ) {
164+ scrollAnimFrameRef . current = null ;
165+ return ;
166+ }
167+ container . scrollTop += scrollDirectionRef . current * scrollSpeedRef . current ;
168+ scrollAnimFrameRef . current = requestAnimationFrame ( loop ) ;
169+ } ;
170+ scrollAnimFrameRef . current = requestAnimationFrame ( loop ) ;
171+ } , [ ] ) ;
172+
173+ const updateScrollFromDragY = useCallback (
174+ ( clientY : number ) => {
175+ const container = scrollContainerRef . current ;
176+ if ( container == null ) {
177+ return ;
178+ }
179+ const EdgeZone = 60 ;
180+ const MaxScrollSpeed = 12 ;
181+ const rect = container . getBoundingClientRect ( ) ;
182+ const relY = clientY - rect . top ;
183+ const height = rect . height ;
184+ if ( relY < EdgeZone ) {
185+ scrollDirectionRef . current = - 1 ;
186+ scrollSpeedRef . current = MaxScrollSpeed * ( 1 - relY / EdgeZone ) ;
187+ startScrollLoop ( ) ;
188+ } else if ( relY > height - EdgeZone ) {
189+ scrollDirectionRef . current = 1 ;
190+ scrollSpeedRef . current = MaxScrollSpeed * ( 1 - ( height - relY ) / EdgeZone ) ;
191+ startScrollLoop ( ) ;
192+ } else {
193+ scrollDirectionRef . current = 0 ;
194+ stopScrollLoop ( ) ;
195+ }
196+ } ,
197+ [ startScrollLoop , stopScrollLoop ]
198+ ) ;
199+
146200 const clearDragState = ( ) => {
201+ stopScrollLoop ( ) ;
147202 if ( dragSourceRef . current != null && ! didResetHoverForDragRef . current ) {
148203 didResetHoverForDragRef . current = true ;
149204 setHoverResetVersion ( ( version ) => version + 1 ) ;
@@ -185,6 +240,7 @@ export function VTabBar({ workspace, className }: VTabBarProps) {
185240 className = "relative flex min-h-0 flex-1 flex-col overflow-y-auto"
186241 onDragOver = { ( event ) => {
187242 event . preventDefault ( ) ;
243+ updateScrollFromDragY ( event . clientY ) ;
188244 if ( event . target === event . currentTarget ) {
189245 setDropIndex ( orderedTabIds . length ) ;
190246 setDropLineTop ( event . currentTarget . scrollHeight ) ;
0 commit comments