// https://www.npmjs.com/package/react-swipeable const { useRef, useMemo } = React; const LEFT = "Left"; const RIGHT = "Right"; const UP = "Up"; const DOWN = "Down"; /* global document */ const defaultProps = { delta: 10, preventDefaultTouchmoveEvent: false, rotationAngle: 0, trackMouse: false, trackTouch: true }; const initialState = { first: true, initial: [0, 0], start: 0, swiping: false, xy: [0, 0] }; const mouseMove = "mousemove"; const mouseUp = "mouseup"; const touchEnd = "touchend"; const touchMove = "touchmove"; const touchStart = "touchstart"; function getDirection(absX, absY, deltaX, deltaY) { if (absX > absY) { if (deltaX > 0) { return RIGHT; } return LEFT; } else if (deltaY > 0) { return DOWN; } return UP; } function rotateXYByAngle(pos, angle) { if (angle === 0) return pos; const angleInRadians = Math.PI / 180 * angle; const x = pos[0] * Math.cos(angleInRadians) + pos[1] * Math.sin(angleInRadians); const y = pos[1] * Math.cos(angleInRadians) - pos[0] * Math.sin(angleInRadians); return [x, y]; } function getHandlers(set, handlerProps) { const onStart = event => { // if more than a single touch don't track, for now... if (event && "touches" in event && event.touches.length > 1) return; set((state, props) => { // setup mouse listeners on document to track swipe since swipe can leave container if (props.trackMouse) { document.addEventListener(mouseMove, onMove); document.addEventListener(mouseUp, onUp); } const { clientX, clientY } = "touches" in event ? event.touches[0] : event; const xy = rotateXYByAngle([clientX, clientY], props.rotationAngle); return { ...state, ...initialState, initial: [...xy], xy, start: event.timeStamp || 0 }; }); }; const onMove = event => { set((state, props) => { // Discount a swipe if additional touches are present after // a swipe has started. if ("touches" in event && event.touches.length > 1) { return state; } const { clientX, clientY } = "touches" in event ? event.touches[0] : event; const [x, y] = rotateXYByAngle([clientX, clientY], props.rotationAngle); const deltaX = x - state.xy[0]; const deltaY = y - state.xy[1]; const absX = Math.abs(deltaX); const absY = Math.abs(deltaY); const time = (event.timeStamp || 0) - state.start; const velocity = Math.sqrt(absX * absX + absY * absY) / (time || 1); const vxvy = [deltaX / (time || 1), deltaY / (time || 1)]; // if swipe is under delta and we have not started to track a swipe: skip update if (absX < props.delta && absY < props.delta && !state.swiping) return state; const dir = getDirection(absX, absY, deltaX, deltaY); const eventData = { absX, absY, deltaX, deltaY, dir, event, first: state.first, initial: state.initial, velocity, vxvy }; props.onSwiping && props.onSwiping(eventData); // track if a swipe is cancelable(handler for swiping or swiped(dir) exists) // so we can call preventDefault if needed let cancelablePageSwipe = false; if (props.onSwiping || props.onSwiped || `onSwiped${dir}` in props) { cancelablePageSwipe = true; } if (cancelablePageSwipe && props.preventDefaultTouchmoveEvent && props.trackTouch && event.cancelable) event.preventDefault(); return { ...state, // first is now always false first: false, eventData, swiping: true }; }); }; const onEnd = event => { set((state, props) => { let eventData; if (state.swiping && state.eventData) { eventData = { ...state.eventData, event }; props.onSwiped && props.onSwiped(eventData); const onSwipedDir = `onSwiped${eventData.dir}`; if (onSwipedDir in props) { props[onSwipedDir](eventData); } } else { props.onTap && props.onTap({ event }); } return { ...state, ...initialState, eventData }; }); }; const cleanUpMouse = () => { // safe to just call removeEventListener document.removeEventListener(mouseMove, onMove); document.removeEventListener(mouseUp, onUp); }; const onUp = e => { cleanUpMouse(); onEnd(e); }; /** * Switch of "passive" property for now. * When `preventDefaultTouchmoveEvent` is: * - true => { passive: false } * - false => { passive: true } * * Could take entire `addEventListener` options object as a param later? */ const attachTouch = (el, passive) => { let cleanup = () => {}; if (el && el.addEventListener) { // attach touch event listeners and handlers const tls = [[touchStart, onStart], [touchMove, onMove], [touchEnd, onEnd]]; tls.forEach(([e, h]) => el.addEventListener(e, h, { passive })); // return properly scoped cleanup method for removing listeners, options not required cleanup = () => tls.forEach(([e, h]) => el.removeEventListener(e, h)); } return cleanup; }; const onRef = el => { // "inline" ref functions are called twice on render, once with null then again with DOM element // ignore null here if (el === null) return; set((state, props) => { // if the same DOM el as previous just return state if (state.el === el) return state; const addState = {}; // if new DOM el clean up old DOM and reset cleanUpTouch if (state.el && state.el !== el && state.cleanUpTouch) { state.cleanUpTouch(); addState.cleanUpTouch = undefined; } // only attach if we want to track touch if (props.trackTouch && el) { addState.cleanUpTouch = attachTouch(el, !props.preventDefaultTouchmoveEvent); } // store event attached DOM el for comparison, clean up, and re-attachment return { ...state, el, ...addState }; }); }; // set ref callback to attach touch event listeners const output = { ref: onRef }; // if track mouse attach mouse down listener if (handlerProps.trackMouse) { output.onMouseDown = onStart; } return [output, attachTouch]; } function updateTransientState(state, props, attachTouch) { const addState = {}; // clean up touch handlers if no longer tracking touches if (!props.trackTouch && state.cleanUpTouch) { state.cleanUpTouch(); addState.cleanUpTouch = undefined; } else if (props.trackTouch && !state.cleanUpTouch) { // attach/re-attach touch handlers if (state.el) { addState.cleanUpTouch = attachTouch(state.el, !props.preventDefaultTouchmoveEvent); } } return { ...state, ...addState }; } function useSwipeable(options) { const { trackMouse } = options; const transientState = useRef({ ...initialState }); const transientProps = useRef({ ...defaultProps }); transientProps.current = { ...defaultProps, ...options }; const [handlers, attachTouch] = useMemo(() => getHandlers(stateSetter => transientState.current = stateSetter(transientState.current, transientProps.current), { trackMouse }), [trackMouse]); transientState.current = updateTransientState(transientState.current, transientProps.current, attachTouch); return handlers; } // export { DOWN, LEFT, RIGHT, UP, useSwipeable }; //# sourceMappingURL=react-swipeable.modern.js.map