Skip to content

Commit bf8325a

Browse files
Merge pull request #2 from dev-ahmedmahmoud/bugfix/1-problems-on-dragging-the-thumb-or-clicking-on-the-scrollbar
bugfix #1 fix vertical and horizontal scrollbar interactivity
2 parents 2fbf6fa + d1f69a1 commit bf8325a

File tree

2 files changed

+622
-1
lines changed

2 files changed

+622
-1
lines changed

src/Scrollbars.tsx

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)