Created
February 14, 2021 04:02
-
-
Save cv2k10/e9dbba1fbf397a6a7826dcfd29b933b0 to your computer and use it in GitHub Desktop.
Revisions
-
cv2k10 created this gist
Feb 14, 2021 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,270 @@ // 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