UNPKG

react-timekeeper

Version:

Time picker based on the style of the Google Keep app

217 lines (179 loc) 6.81 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = useClockEvents; var _react = require("react"); var _constants = require("../helpers/constants"); var _dom = require("../helpers/dom"); var _math = require("../helpers/math"); const { atan2 } = Math; /* solely responsible for transforming click events into angles (which are later converted into time depending on current mode and other restrictions) */ function useClockEvents(clock, handleChange) { const wrapper = (0, _react.useRef)(null); const calcOffsetCache = (0, _react.useRef)(null); const dragCount = (0, _react.useRef)(0); const cleanupRef = (0, _react.useRef)(() => {}); // avoid recomputing all the event listeners, prolly unnecessary... const handleChangeRef = (0, _react.useRef)(handleChange); (0, _react.useEffect)(() => { handleChangeRef.current = handleChange; }, [handleChange]); const calculatePoint = (0, _react.useCallback)((offsetX, offsetY, canAutoChangeMode) => { // if user just clicks/taps a number (drag count < 2), then just assume it's a rough tap // and force a rounded/coarse number (ie: 1, 2, 3, 4 is tapped, assume 0 or 5) const wasTapped = dragCount.current < 2; const x = offsetX - _constants.CLOCK_RADIUS; const y = -offsetY + _constants.CLOCK_RADIUS; const a = atan2(y, x); let d = 90 - (0, _math.deg)(a); if (d < 0) { d = 360 + d; } // ensure touch doesn't bleed outside of clock radius if (!(0, _math.isWithinRadius)(x, y, _constants.CLOCK_RADIUS) && wasTapped) { return false; } const isInnerClick = (0, _math.isWithinRadius)(x, y, _constants.INNER_NUMBER_RADIUS); // update time on main handleChangeRef.current(d, { canAutoChangeMode, wasTapped, isInnerClick }); }, []); // handle mouse + touch changes const handleMouseUp = (0, _react.useCallback)(e => { if (!clock.current) { return; } clock.current.style.cursor = ''; const { offsetX, offsetY } = calcOffsetCache.current(e.clientX, e.clientY); calculatePoint(offsetX, offsetY, true); }, [calculatePoint, clock]); const handleTouchEnd = (0, _react.useCallback)(e => { const touch = e.targetTouches[0] || e.changedTouches[0]; if (touch && calcOffsetCache.current) { const { offsetX, offsetY } = calcOffsetCache.current(touch.clientX, touch.clientY); calculatePoint(offsetX, offsetY, true); } }, [calculatePoint]); const handleMouseDrag = (0, _react.useCallback)(e => { if (calcOffsetCache.current) { const { offsetX, offsetY } = calcOffsetCache.current(e.clientX, e.clientY); calculatePoint(offsetX, offsetY, false); } dragCount.current++; if (dragCount.current === 1 && clock.current) { clock.current.style.cursor = '-webkit-grabbing'; clock.current.style.cursor = 'grabbing'; } e.preventDefault(); return false; }, [calculatePoint, clock]); const handleTouchDrag = (0, _react.useCallback)(e => { if (calcOffsetCache.current) { const touch = e.targetTouches[0]; const { offsetX, offsetY } = calcOffsetCache.current(touch.clientX, touch.clientY); calculatePoint(offsetX, offsetY, false); } dragCount.current++; e.preventDefault(); return false; }, [calculatePoint]); // stop mouse + touch events const handleStopDrag = (0, _react.useCallback)(e => { cleanupRef.current(); if (e == null || clock.current == null) { return; } if (isMouseEventEnd(e)) { handleMouseUp(e); } else if (isTouchEventEnd(e)) { handleTouchEnd(e); } function isMouseEventEnd(e) { return e.type === 'mouseup'; } function isTouchEventEnd(e) { return e.type === 'touchcancel' || e.type === 'touchend'; } }, [handleMouseUp, handleTouchEnd, clock]); // mouse events const handleMouseDown = (0, _react.useCallback)(e => { dragCount.current = 0; // terminate if click is outside of clock radius, ie: // if clicking meridiem button which overlaps with clock if (clock.current) { calcOffsetCache.current = (0, _dom.calcOffset)(clock.current); const { offsetX, offsetY } = calcOffsetCache.current(e.clientX, e.clientY); const x = offsetX - _constants.CLOCK_RADIUS; const y = offsetY - _constants.CLOCK_RADIUS; if (!(0, _math.isWithinRadius)(x, y, _constants.CLOCK_RADIUS)) return; } // add listeners document.addEventListener('mousemove', handleMouseDrag, false); document.addEventListener('mouseup', handleStopDrag, false); wrapper.current && wrapper.current.addEventListener('mouseleave', handleStopDrag, false); // @ts-ignore handleMouseDrag(e); }, [clock, handleMouseDrag, handleStopDrag]); // touch events const handleTouchStart = (0, _react.useCallback)(e => { e.preventDefault(); dragCount.current = 0; // add listeners document.addEventListener('touchmove', handleTouchDrag, false); document.addEventListener('touchend', handleStopDrag, false); document.addEventListener('touchcancel', handleStopDrag, false); if (clock.current) { calcOffsetCache.current = (0, _dom.calcOffset)(clock.current); } }, [clock, handleStopDrag, handleTouchDrag]); // attach touchstart event manually to the clock to make it cancelable. (0, _react.useEffect)(() => { const currentTarget = clock.current; const type = 'touchstart'; if (currentTarget) { currentTarget.addEventListener(type, handleTouchStart, false); } return () => { if (currentTarget) { currentTarget.removeEventListener(type, handleTouchStart, false); } }; }, [clock, handleTouchStart]); /* deals with circular dependencies of callback functions; add listener function depends on remove listener function and vice versa on remove listener function which depends on the add listener cb */ (0, _react.useEffect)(() => { cleanupRef.current = () => { document.removeEventListener('mousemove', handleMouseDrag, false); document.removeEventListener('mouseup', handleStopDrag, false); wrapper.current && wrapper.current.removeEventListener('mouseleave', handleStopDrag, false); document.removeEventListener('touchmove', handleTouchDrag, false); document.removeEventListener('touchend', handleStopDrag, false); document.removeEventListener('touchcancel', handleStopDrag, false); }; }, [handleMouseDrag, handleStopDrag, handleTouchDrag]); // clean up (0, _react.useEffect)(() => { return cleanupRef.current; }, []); return { bind: { onMouseDown: handleMouseDown, ref: wrapper } }; }