@@ -40,6 +40,8 @@ import styles from './carousel.module.scss';
4040
4141type scrollVisibilityApiType = React . ContextType < typeof VisibilityContext > ;
4242
43+ const SCROLL_LOCK_WAIT_IN_MILLISECONDS : number = 40 ;
44+
4345const isVisible = ( element : HTMLDivElement ) : boolean => {
4446 const rect : DOMRect = element . getBoundingClientRect ( ) ;
4547 return (
@@ -103,7 +105,10 @@ export const Carousel: FC<CarouselProps> = React.forwardRef(
103105 const [ itemsNumber , setItemsNumber ] = useState < number > ( 0 ) ;
104106 const [ visible , setVisible ] = useState < boolean > ( ) ;
105107 const [ mouseEnter , setMouseEnter ] = useState < boolean > ( false ) ;
108+ const [ scrollLock , setScrollLock ] = useState < boolean > ( false ) ;
109+ const timerRef = useRef < NodeJS . Timeout > ( ) ;
106110 const [ _single , setSingle ] = useState < boolean > ( single ) ;
111+ const [ _visibleElements , setVisibleElements ] = useState < number > ( 0 ) ;
107112
108113 // ============================ Strings ===========================
109114 const [ paginationLocale ] = useLocaleReceiver ( 'Pagination' ) ;
@@ -152,7 +157,7 @@ export const Carousel: FC<CarouselProps> = React.forwardRef(
152157 } , [ animating ] ) ;
153158
154159 useEffect ( ( ) => {
155- window . addEventListener ( 'scroll' , handleScroll ) ;
160+ window . addEventListener ( 'scroll' , handleScroll , { passive : true } ) ;
156161
157162 return ( ) => {
158163 window . removeEventListener ( 'scroll' , handleScroll ) ;
@@ -161,16 +166,15 @@ export const Carousel: FC<CarouselProps> = React.forwardRef(
161166
162167 useEffect ( ( ) => {
163168 if ( scrollMenuRef ?. current ) {
164- scrollMenuRef . current . addEventListener ( 'wheel' , preventYScroll , false ) ;
169+ // passive: false, to ensure prevent default
170+ scrollMenuRef . current . addEventListener ( 'wheel' , preventYScroll , {
171+ passive : false ,
172+ } ) ;
165173 }
166174
167175 return ( ) => {
168176 if ( scrollMenuRef ?. current ) {
169- scrollMenuRef . current . removeEventListener (
170- 'wheel' ,
171- preventYScroll ,
172- false
173- ) ;
177+ scrollMenuRef . current . removeEventListener ( 'wheel' , preventYScroll ) ;
174178 }
175179 } ;
176180 } ) ;
@@ -275,9 +279,9 @@ export const Carousel: FC<CarouselProps> = React.forwardRef(
275279 apiObj : scrollVisibilityApiType ,
276280 event : React . WheelEvent
277281 ) : void => {
278- if ( event . deltaY < 0 ) {
282+ if ( event . deltaY < 0 || event . deltaX < 0 ) {
279283 apiObj . scrollNextGroup ( ) ;
280- } else if ( event . deltaY > 0 ) {
284+ } else if ( event . deltaY > 0 || event . deltaX > 0 ) {
281285 apiObj . scrollPrevGroup ( ) ;
282286 }
283287 } ;
@@ -286,7 +290,7 @@ export const Carousel: FC<CarouselProps> = React.forwardRef(
286290 apiObj : scrollVisibilityApiType ,
287291 event : React . WheelEvent
288292 ) : void => {
289- if ( event . deltaY < 0 ) {
293+ if ( event . deltaY < 0 || event . deltaX < 0 ) {
290294 apiObj . scrollBySingleItem (
291295 apiObj . getNextElement ( ) ,
292296 'smooth' ,
@@ -298,7 +302,7 @@ export const Carousel: FC<CarouselProps> = React.forwardRef(
298302 ? - DEFAULT_GAP_WIDTH
299303 : OCCLUSION_AVOIDANCE_BUFFER
300304 ) ;
301- } else if ( event . deltaY > 0 ) {
305+ } else if ( event . deltaY > 0 || event . deltaX > 0 ) {
302306 const gapWidth : number = ! ! props . carouselScrollMenuProps ?. gap
303307 ? props . carouselScrollMenuProps ?. gap
304308 : DEFAULT_GAP_WIDTH ;
@@ -316,23 +320,25 @@ export const Carousel: FC<CarouselProps> = React.forwardRef(
316320 apiObj : scrollVisibilityApiType ,
317321 event : React . WheelEvent
318322 ) : void => {
319- const isTouchpad =
320- Math . abs ( event . deltaX ) !== 0 || Math . abs ( event . deltaY ) < 15 ;
321-
322- if ( isTouchpad ) {
323- event . stopPropagation ( ) ;
324- return ;
325- }
326-
327- if ( _single ) {
328- handleSingleItemScrollOnWheel ( apiObj , event ) ;
329- } else {
330- handleGroupScrollOnWheel ( apiObj , event ) ;
323+ // Prevent spamming of scroll.
324+ clearTimeout ( timerRef . current ) ;
325+ timerRef . current = setTimeout (
326+ ( ) => setScrollLock ( false ) ,
327+ SCROLL_LOCK_WAIT_IN_MILLISECONDS
328+ ) ;
329+ if ( ! scrollLock ) {
330+ setScrollLock ( true ) ;
331+ if ( _single ) {
332+ handleSingleItemScrollOnWheel ( apiObj , event ) ;
333+ } else {
334+ handleGroupScrollOnWheel ( apiObj , event ) ;
335+ }
331336 }
332337 } ;
333338
334339 const preventYScroll = ( event : { preventDefault : ( ) => void } ) : void => {
335- if ( mouseEnter ) {
340+ // Prevent document scroll only when hovering over a carousel that may be scrolled.
341+ if ( mouseEnter && ( previousButtonRef . current || nextButtonRef . current ) ) {
336342 event . preventDefault ( ) ;
337343 }
338344 } ;
@@ -508,6 +514,7 @@ export const Carousel: FC<CarouselProps> = React.forwardRef(
508514 if ( initComplete && visibleElements ?. length ) {
509515 setPreviousDisabled ( isFirstItemVisible ) ;
510516 setNextDisabled ( isLastItemVisible ) ;
517+ setVisibleElements ( visibleElements . length ) ;
511518 }
512519 } , [
513520 initComplete ,
@@ -540,10 +547,9 @@ export const Carousel: FC<CarouselProps> = React.forwardRef(
540547 return ;
541548 }
542549
543- console . log ( scrollMenuRef . current ?. offsetWidth ) ;
544-
545550 if ( ! single ) {
546- setSingle ( scrollMenuRef . current ?. offsetWidth <= 680 ) ;
551+ // If the number of visible elements is less than 3, swap to single scroll.
552+ setSingle ( _visibleElements < 3 ) ;
547553 }
548554 } ;
549555
@@ -552,7 +558,7 @@ export const Carousel: FC<CarouselProps> = React.forwardRef(
552558 return ;
553559 }
554560 updateScrollMode ( ) ;
555- } , [ _single ] ) ;
561+ } , [ _single , _visibleElements ] ) ;
556562
557563 return (
558564 < LocaleReceiver componentName = { 'Pagination' } defaultLocale = { enUS } >
0 commit comments