import * as Hammer from "hammerjs"; import * as _ from "lodash"; import * as Pressure from "pressure"; /** Tap & Hold **/ const TAP_HOLD_TO_SAVE_THRESHOLD = 31; /** * Provides an inaccurate mechanism for detecting if a user held on an image long enough for it to bring up * the download sheet. We use a few factors to make an educated guess that this event has occured: * * 1. The user has held on the image for a long enough period of time. * 2. The 'touchcancel' or 'touchend' event was fired after they had passed that threshold * 3. The user has not moved on the image as they were holding with reasonable threshold */ export const setImagePossiblyRevealedDownloadSheetListeners = ( element: HTMLImageElement, revealedCallback: () => void ) => { let held: number = 0; let incrementHeldInterval: any; element.addEventListener( "touchstart", imagePossiblyRevealedDownloadStartEventListener.bind(null, (e: TouchEvent) => { held = 0; incrementHeldInterval = setInterval(() => { held++; }, 1); }), { passive: true } ); element.addEventListener( "touchmove", imagePossiblyRevealedDownloadMoveEventListener.bind(null, (e: TouchEvent) => { clearInterval(incrementHeldInterval); held = 0; }), { passive: true } ); element.addEventListener( "touchend", imagePossiblyRevealedDownloadEndEventListener.bind(null, (e: TouchEvent) => { if (held > TAP_HOLD_TO_SAVE_THRESHOLD) { revealedCallback(); } clearInterval(incrementHeldInterval); held = 0; }), { passive: true } ); element.addEventListener( "touchcancel", imagePossiblyRevealedDownloadCancelEventListener.bind(null, (e: TouchEvent) => { if (held > TAP_HOLD_TO_SAVE_THRESHOLD) { revealedCallback(); } clearInterval(incrementHeldInterval); held = 0; }), { passive: true } ); }; export const removeImagePossiblyRevealedDownloadSheetListeners = (element: HTMLImageElement) => { element.removeEventListener( "touchstart", imagePossiblyRevealedDownloadStartEventListener.bind(null) ); element.removeEventListener( "touchmove", imagePossiblyRevealedDownloadMoveEventListener.bind(null) ); element.removeEventListener("touchend", imagePossiblyRevealedDownloadEndEventListener.bind(null)); element.removeEventListener( "touchcancel", imagePossiblyRevealedDownloadCancelEventListener.bind(null) ); }; const imagePossiblyRevealedDownloadStartEventListener = ( callback: (e: TouchEvent) => void, e: TouchEvent ) => { callback(e); }; const imagePossiblyRevealedDownloadMoveEventListener = _.throttle( (callback: (e: TouchEvent) => void, e: TouchEvent) => { callback(e); }, 50 ); const imagePossiblyRevealedDownloadEndEventListener = ( callback: (e: TouchEvent) => void, e: TouchEvent ) => { callback(e); }; const imagePossiblyRevealedDownloadCancelEventListener = ( callback: (e: TouchEvent) => void, e: TouchEvent ) => { callback(e); }; export const addPrevent3dTouchListener = (element: HTMLElement, convertToClick?: boolean) => { if (!element) return; // Clear out the same previous listener incase it was set removePrevent3dTouchListener(element); Pressure.set(element, { end: () => { if (convertToClick) { element.click(); } }, startDeepPress: (e) => { e.preventDefault(); }, }); element.style["-webkit-touch-callout"] = "initial"; element.style["-webkit-user-select"] = "initial"; } /** * This is to be the inverse of what we add with Pressure.js. * Unfortunately, Pressure.js doesn't yet provide this feature so we * need to manually undo it. */ export const removePrevent3dTouchListener = (element: HTMLElement) => { if (!element) return; element.style["-webkit-touch-callout"] = "initial"; element.style["-webkit-user-select"] = "initial"; element.removeEventListener('touchstart', () => null); } export const setTapRecognizers = (element: HTMLElement, onSingleTap?: (e: Event) => void, onDoubleTap?: (e: Event) => void) => { const hammer = new Hammer.Manager(element, { domEvents: true, }); const singleTap = new Hammer.Tap({ event: "singletap" }); const doubleTap = new Hammer.Tap({ event: "doubletap", taps: 2 }); hammer.add([doubleTap, singleTap]); doubleTap.recognizeWith(singleTap); singleTap.requireFailure(doubleTap); if (_.isFunction(onSingleTap)) { hammer.on("singletap", onSingleTap); } if (_.isFunction(onDoubleTap)) { hammer.on("doubletap", onDoubleTap); } element.style["-webkit-touch-callout"] = "initial"; element.style["-webkit-user-select"] = "initial"; }