@@ -116,6 +116,12 @@ export const Scrollbars = forwardRef<ScrollbarsRef, ScrollbarsProps>(
116116 const viewScrollLeftRef = useRef ( 0 )
117117 const viewScrollTopRef = useRef ( 0 )
118118
119+ // Dragging state refs (following legacy pattern)
120+ const draggingRef = useRef ( false )
121+ const prevPageXRef = useRef ( 0 )
122+ const prevPageYRef = useRef ( 0 )
123+ const trackMouseOverRef = useRef ( false )
124+
119125 // Utility functions
120126 const getValues = useCallback ( ( ) : ScrollValues => {
121127 const view = viewRef . current
@@ -181,6 +187,169 @@ export const Scrollbars = forwardRef<ScrollbarsRef, ScrollbarsProps>(
181187 return Math . max ( height , thumbMinSize )
182188 } , [ thumbSize , thumbMinSize ] )
183189
190+ // Helper functions for offset calculations (from legacy)
191+ const getScrollLeftForOffset = useCallback (
192+ ( offset : number ) => {
193+ const view = viewRef . current
194+ const trackHorizontal = trackHorizontalRef . current
195+ if ( ! view || ! trackHorizontal ) return 0
196+
197+ const { scrollWidth, clientWidth } = view
198+ const trackWidth = getInnerWidth ( trackHorizontal )
199+ const thumbWidth = getThumbHorizontalWidth ( )
200+
201+ return (
202+ ( offset / ( trackWidth - thumbWidth ) ) * ( scrollWidth - clientWidth )
203+ )
204+ } ,
205+ [ getThumbHorizontalWidth ]
206+ )
207+
208+ const getScrollTopForOffset = useCallback (
209+ ( offset : number ) => {
210+ const view = viewRef . current
211+ const trackVertical = trackVerticalRef . current
212+ if ( ! view || ! trackVertical ) return 0
213+
214+ const { scrollHeight, clientHeight } = view
215+ const trackHeight = getInnerHeight ( trackVertical )
216+ const thumbHeight = getThumbVerticalHeight ( )
217+
218+ return (
219+ ( offset / ( trackHeight - thumbHeight ) ) * ( scrollHeight - clientHeight )
220+ )
221+ } ,
222+ [ getThumbVerticalHeight ]
223+ )
224+
225+ // Drag setup/teardown (from legacy)
226+ const setupDragging = useCallback ( ( ) => {
227+ document . body . style . userSelect = 'none'
228+ document . onselectstart = ( ) => false
229+ } , [ ] )
230+
231+ const teardownDragging = useCallback ( ( ) => {
232+ document . body . style . userSelect = ''
233+ document . onselectstart = null
234+ } , [ ] )
235+
236+ // handleDrag - called on mousemove during drag (from legacy)
237+ const handleDrag = useCallback (
238+ ( event : MouseEvent ) => {
239+ if ( prevPageXRef . current ) {
240+ const { clientX } = event
241+ const trackHorizontal = trackHorizontalRef . current
242+ if ( trackHorizontal && viewRef . current ) {
243+ const { left : trackLeft } = trackHorizontal . getBoundingClientRect ( )
244+ const thumbWidth = getThumbHorizontalWidth ( )
245+ const clickPosition = thumbWidth - prevPageXRef . current
246+ const offset = - trackLeft + clientX - clickPosition
247+ viewRef . current . scrollLeft = getScrollLeftForOffset ( offset )
248+ }
249+ }
250+ if ( prevPageYRef . current ) {
251+ const { clientY } = event
252+ const trackVertical = trackVerticalRef . current
253+ if ( trackVertical && viewRef . current ) {
254+ const { top : trackTop } = trackVertical . getBoundingClientRect ( )
255+ const thumbHeight = getThumbVerticalHeight ( )
256+ const clickPosition = thumbHeight - prevPageYRef . current
257+ const offset = - trackTop + clientY - clickPosition
258+ viewRef . current . scrollTop = getScrollTopForOffset ( offset )
259+ }
260+ }
261+ return false
262+ } ,
263+ [
264+ getScrollLeftForOffset ,
265+ getScrollTopForOffset ,
266+ getThumbHorizontalWidth ,
267+ getThumbVerticalHeight ,
268+ ]
269+ )
270+
271+ // handleDragEnd - called on mouseup (from legacy)
272+ const handleDragEnd = useCallback ( ( ) => {
273+ draggingRef . current = false
274+ prevPageXRef . current = 0
275+ prevPageYRef . current = 0
276+ teardownDragging ( )
277+ document . removeEventListener ( 'mousemove' , handleDrag )
278+ document . removeEventListener ( 'mouseup' , handleDragEnd )
279+ } , [ teardownDragging , handleDrag ] )
280+
281+ // handleDragStart - called on thumb mousedown (from legacy)
282+ const handleDragStart = useCallback (
283+ ( event : MouseEvent ) => {
284+ draggingRef . current = true
285+ event . stopImmediatePropagation ( )
286+ setupDragging ( )
287+ document . addEventListener ( 'mousemove' , handleDrag )
288+ document . addEventListener ( 'mouseup' , handleDragEnd )
289+ } ,
290+ [ setupDragging , handleDrag , handleDragEnd ]
291+ )
292+
293+ // Track mouse down handlers (from legacy)
294+ const handleHorizontalTrackMouseDown = useCallback (
295+ ( event : MouseEvent ) => {
296+ event . preventDefault ( )
297+ const target = event . target as HTMLElement
298+ const { clientX } = event
299+ const { left : targetLeft } = target . getBoundingClientRect ( )
300+ const thumbWidth = getThumbHorizontalWidth ( )
301+ const offset = Math . abs ( targetLeft - clientX ) - thumbWidth / 2
302+
303+ if ( viewRef . current ) {
304+ viewRef . current . scrollLeft = getScrollLeftForOffset ( offset )
305+ }
306+ } ,
307+ [ getScrollLeftForOffset , getThumbHorizontalWidth ]
308+ )
309+
310+ const handleVerticalTrackMouseDown = useCallback (
311+ ( event : MouseEvent ) => {
312+ event . preventDefault ( )
313+ const target = event . target as HTMLElement
314+ const { clientY } = event
315+ const { top : targetTop } = target . getBoundingClientRect ( )
316+ const thumbHeight = getThumbVerticalHeight ( )
317+ const offset = Math . abs ( targetTop - clientY ) - thumbHeight / 2
318+
319+ if ( viewRef . current ) {
320+ viewRef . current . scrollTop = getScrollTopForOffset ( offset )
321+ }
322+ } ,
323+ [ getScrollTopForOffset , getThumbVerticalHeight ]
324+ )
325+
326+ // Thumb mouse down handlers (from legacy)
327+ const handleHorizontalThumbMouseDown = useCallback (
328+ ( event : MouseEvent ) => {
329+ event . preventDefault ( )
330+ handleDragStart ( event )
331+ const target = event . target as HTMLElement
332+ const { clientX } = event
333+ const { offsetWidth } = target
334+ const { left } = target . getBoundingClientRect ( )
335+ prevPageXRef . current = offsetWidth - ( clientX - left )
336+ } ,
337+ [ handleDragStart ]
338+ )
339+
340+ const handleVerticalThumbMouseDown = useCallback (
341+ ( event : MouseEvent ) => {
342+ event . preventDefault ( )
343+ handleDragStart ( event )
344+ const target = event . target as HTMLElement
345+ const { clientY } = event
346+ const { offsetHeight } = target
347+ const { top } = target . getBoundingClientRect ( )
348+ prevPageYRef . current = offsetHeight - ( clientY - top )
349+ } ,
350+ [ handleDragStart ]
351+ )
352+
184353 // Scroll methods for imperative API
185354 const scrollLeft = useCallback ( ( left = 0 ) => {
186355 if ( ! viewRef . current ) return
@@ -365,6 +534,21 @@ export const Scrollbars = forwardRef<ScrollbarsRef, ScrollbarsProps>(
365534 }
366535 } , [ autoHide ] )
367536
537+ // Track mouse enter/leave handlers for auto-hide (from legacy)
538+ const handleTrackMouseEnter = useCallback ( ( ) => {
539+ trackMouseOverRef . current = true
540+ if ( autoHide ) {
541+ showScrollbars ( )
542+ }
543+ } , [ autoHide , showScrollbars ] )
544+
545+ const handleTrackMouseLeave = useCallback ( ( ) => {
546+ trackMouseOverRef . current = false
547+ if ( autoHide ) {
548+ hideScrollbars ( )
549+ }
550+ } , [ autoHide , hideScrollbars ] )
551+
368552 // Event handlers
369553 const handleScroll = useCallback (
370554 ( event : Event ) => {
@@ -460,6 +644,77 @@ export const Scrollbars = forwardRef<ScrollbarsRef, ScrollbarsProps>(
460644 }
461645 } , [ handleScroll , autoHide , showScrollbars , hideScrollbars ] )
462646
647+ // Effect to add track/thumb mouse listeners (from legacy addListeners)
648+ useEffect ( ( ) => {
649+ const trackHorizontal = trackHorizontalRef . current
650+ const trackVertical = trackVerticalRef . current
651+ const thumbHorizontal = thumbHorizontalRef . current
652+ const thumbVertical = thumbVerticalRef . current
653+
654+ // Don't add listeners if no native scrollbar width
655+ if ( ! getScrollbarWidth ( ) ) return
656+
657+ if (
658+ ! trackHorizontal ||
659+ ! trackVertical ||
660+ ! thumbHorizontal ||
661+ ! thumbVertical
662+ )
663+ return
664+
665+ // Wrapper functions to properly type the event handlers
666+ const onHorizontalTrackMouseDown = ( e : Event ) =>
667+ handleHorizontalTrackMouseDown ( e as MouseEvent )
668+ const onVerticalTrackMouseDown = ( e : Event ) =>
669+ handleVerticalTrackMouseDown ( e as MouseEvent )
670+ const onHorizontalThumbMouseDown = ( e : Event ) =>
671+ handleHorizontalThumbMouseDown ( e as MouseEvent )
672+ const onVerticalThumbMouseDown = ( e : Event ) =>
673+ handleVerticalThumbMouseDown ( e as MouseEvent )
674+
675+ // Add event listeners
676+ trackHorizontal . addEventListener ( 'mouseenter' , handleTrackMouseEnter )
677+ trackHorizontal . addEventListener ( 'mouseleave' , handleTrackMouseLeave )
678+ trackHorizontal . addEventListener ( 'mousedown' , onHorizontalTrackMouseDown )
679+ trackVertical . addEventListener ( 'mouseenter' , handleTrackMouseEnter )
680+ trackVertical . addEventListener ( 'mouseleave' , handleTrackMouseLeave )
681+ trackVertical . addEventListener ( 'mousedown' , onVerticalTrackMouseDown )
682+ thumbHorizontal . addEventListener ( 'mousedown' , onHorizontalThumbMouseDown )
683+ thumbVertical . addEventListener ( 'mousedown' , onVerticalThumbMouseDown )
684+
685+ return ( ) => {
686+ // Remove event listeners
687+ trackHorizontal . removeEventListener ( 'mouseenter' , handleTrackMouseEnter )
688+ trackHorizontal . removeEventListener ( 'mouseleave' , handleTrackMouseLeave )
689+ trackHorizontal . removeEventListener (
690+ 'mousedown' ,
691+ onHorizontalTrackMouseDown
692+ )
693+ trackVertical . removeEventListener ( 'mouseenter' , handleTrackMouseEnter )
694+ trackVertical . removeEventListener ( 'mouseleave' , handleTrackMouseLeave )
695+ trackVertical . removeEventListener ( 'mousedown' , onVerticalTrackMouseDown )
696+ thumbHorizontal . removeEventListener (
697+ 'mousedown' ,
698+ onHorizontalThumbMouseDown
699+ )
700+ thumbVertical . removeEventListener ( 'mousedown' , onVerticalThumbMouseDown )
701+
702+ // Teardown any ongoing drag
703+ draggingRef . current = false
704+ prevPageXRef . current = 0
705+ prevPageYRef . current = 0
706+ document . body . style . userSelect = ''
707+ document . onselectstart = null
708+ }
709+ } , [
710+ handleTrackMouseEnter ,
711+ handleTrackMouseLeave ,
712+ handleHorizontalTrackMouseDown ,
713+ handleVerticalTrackMouseDown ,
714+ handleHorizontalThumbMouseDown ,
715+ handleVerticalThumbMouseDown ,
716+ ] )
717+
463718 // Imperative API
464719 useImperativeHandle (
465720 ref ,
0 commit comments