UNPKG

@evermade/overflow-slider

Version:

Accessible slider that is powered by overflow: auto.

112 lines (98 loc) 3.4 kB
import { Slider, DeepPartial } from '../../core/types'; const DEFAULT_DRAGGED_DISTANCE_THAT_PREVENTS_CLICK = 20; export type DragScrollingOptions = { draggedDistanceThatPreventsClick: number, }; export default function DragScrollingPlugin( args?: DeepPartial<DragScrollingOptions> ) { const options = <DragScrollingOptions>{ draggedDistanceThatPreventsClick: args?.draggedDistanceThatPreventsClick ?? DEFAULT_DRAGGED_DISTANCE_THAT_PREVENTS_CLICK, }; return ( slider: Slider ) => { let isMouseDown = false; let startX = 0; let scrollLeft = 0; let isMovingForward = false; let programmaticScrollStarted = false; let mayNeedToSnap = false; // add data attribute to container slider.container.setAttribute( 'data-has-drag-scrolling', 'true' ); const mouseDown = (e: MouseEvent) => { programmaticScrollStarted = false; if ( ! slider.details.hasOverflow ) { return; } if ( ! slider.container.contains( e.target as Node ) ) { return; } isMouseDown = true; startX = e.pageX - slider.container.getBoundingClientRect().left; scrollLeft = slider.container.scrollLeft; // change cursor to grabbing slider.container.style.cursor = 'grabbing'; slider.container.style.scrollBehavior = 'auto'; // prevent focus going to the slides e.preventDefault(); e.stopPropagation(); }; const mouseMove = (e: MouseEvent) => { if ( ! slider.details.hasOverflow ) { programmaticScrollStarted = false; return; } if (!isMouseDown) { programmaticScrollStarted = false; return; } e.preventDefault(); if (!programmaticScrollStarted) { programmaticScrollStarted = true; slider.emit('programmaticScrollStart'); } const x = e.pageX - slider.container.getBoundingClientRect().left; const walk = (x - startX); const newScrollLeft = scrollLeft - walk; mayNeedToSnap = true; if ( Math.floor( slider.container.scrollLeft ) !== Math.floor( newScrollLeft ) ) { isMovingForward = slider.container.scrollLeft < newScrollLeft; } slider.container.scrollLeft = newScrollLeft; const absWalk = Math.abs(walk); const slides = slider.container.querySelectorAll( slider.options.slidesSelector ); const pointerEvents = absWalk > options.draggedDistanceThatPreventsClick ? 'none' : ''; slides.forEach((slide) => { (<HTMLElement>slide).style.pointerEvents = pointerEvents; }); }; const mouseUp = () => { if (!slider.details.hasOverflow) { programmaticScrollStarted = false; return; } isMouseDown = false; slider.container.style.cursor = ''; setTimeout(() => { programmaticScrollStarted = false; slider.container.style.scrollBehavior = ''; const slides = slider.container.querySelectorAll( slider.options.slidesSelector ); slides.forEach((slide) => { (<HTMLElement>slide).style.pointerEvents = ''; }); }, 50); }; window.addEventListener('mousedown', mouseDown); window.addEventListener('mousemove', mouseMove); window.addEventListener('mouseup', mouseUp); // emulate scroll snapping if ( slider.options.emulateScrollSnap ) { const snap = () => { if (!mayNeedToSnap || isMouseDown) { return; } mayNeedToSnap = false; slider.snapToClosestSlide(isMovingForward ? 'next' : 'prev'); }; slider.on( 'programmaticScrollEnd', snap ); window.addEventListener( 'mouseup', snap ); } }; };