@@ -27,177 +27,225 @@ export default function useClockEvents(
2727 const wrapper = useRef < HTMLDivElement | null > ( null )
2828 const calcOffsetCache : React . MutableRefObject < null | CalcOffsetFn > = useRef ( null )
2929 const dragCount = useRef ( 0 )
30- const cleanup = useCallback ( _removeHandlers , [ ] )
31- const disableMouse = useRef ( false )
30+ const cleanupRef = useRef < ( ) => void > ( ( ) => { } )
3231
33- // mouse events
34- function handleMouseDown ( e : React . MouseEvent < HTMLElement > ) {
35- if ( disableMouse . current ) {
36- return
37- }
38- dragCount . current = 0
32+ // avoid recomputing all the event listeners, prolly unnecessary...
33+ const handleChangeRef = useRef ( handleChange )
34+ useEffect ( ( ) => {
35+ handleChangeRef . current = handleChange
36+ } , [ handleChange ] )
37+
38+ const calculatePoint = useCallback (
39+ (
40+ offsetX : number ,
41+ offsetY : number ,
42+ // determines if change is due to mouseup/touchend in order to
43+ // automatically change unit (eg: hour -> minute) if enabled
44+ // prevents changing unit if dragging along clock
45+ canAutoChangeMode : boolean ,
46+ ) => {
47+ // if user just clicks/taps a number (drag count < 2), then just assume it's a rough tap
48+ // and force a rounded/coarse number (ie: 1, 2, 3, 4 is tapped, assume 0 or 5)
49+ const wasTapped = dragCount . current < 2
3950
40- // terminate if click is outside of clock radius, ie:
41- // if clicking meridiem button which overlaps with clock
42- if ( clock . current ) {
43- calcOffsetCache . current = calcOffset ( clock . current )
44- const { offsetX, offsetY } = calcOffsetCache . current ! ( e . clientX , e . clientY )
4551 const x = offsetX - CLOCK_RADIUS
46- const y = offsetY - CLOCK_RADIUS
47- if ( ! isWithinRadius ( x , y , CLOCK_RADIUS ) ) return
48- }
52+ const y = - offsetY + CLOCK_RADIUS
53+
54+ const a = atan2 ( y , x )
55+ let d = 90 - deg ( a )
56+ if ( d < 0 ) {
57+ d = 360 + d
58+ }
59+
60+ // ensure touch doesn't bleed outside of clock radius
61+ if ( ! isWithinRadius ( x , y , CLOCK_RADIUS ) && wasTapped ) {
62+ return false
63+ }
64+ const isInnerClick = isWithinRadius ( x , y , INNER_NUMBER_RADIUS )
65+
66+ // update time on main
67+ handleChangeRef . current ( d , { canAutoChangeMode, wasTapped, isInnerClick } )
68+ } ,
69+ [ ] ,
70+ )
4971
50- // add listeners
51- document . addEventListener ( 'mousemove' , handleMouseDrag , false )
52- document . addEventListener ( 'mouseup' , handleStopDrag , false )
53- wrapper . current &&
54- wrapper . current . addEventListener ( 'mouseleave' , handleStopDrag , false )
72+ // handle mouse + touch changes
73+ const handleMouseUp = useCallback (
74+ ( e : MouseEvent ) => {
75+ if ( ! clock . current ) {
76+ return
77+ }
78+ clock . current . style . cursor = ''
5579
56- // @ts -ignore
57- handleMouseDrag ( e )
58- }
59- function handleMouseDrag ( e : MouseEvent ) {
60- if ( calcOffsetCache . current ) {
61- const { offsetX, offsetY } = calcOffsetCache . current ( e . clientX , e . clientY )
62- calculatePoint ( offsetX , offsetY , false )
63- }
64- dragCount . current ++
80+ const { offsetX, offsetY } = calcOffsetCache . current ! ( e . clientX , e . clientY )
81+ calculatePoint ( offsetX , offsetY , true )
82+ } ,
83+ [ calculatePoint , clock ] ,
84+ )
85+ const handleTouchEnd = useCallback (
86+ ( e : TouchEvent ) => {
87+ const touch = e . targetTouches [ 0 ] || e . changedTouches [ 0 ]
88+ if ( touch && calcOffsetCache . current ) {
89+ const { offsetX, offsetY } = calcOffsetCache . current (
90+ touch . clientX ,
91+ touch . clientY ,
92+ )
93+ calculatePoint ( offsetX , offsetY , true )
94+ }
95+ } ,
96+ [ calculatePoint ] ,
97+ )
98+
99+ const handleMouseDrag = useCallback (
100+ ( e : MouseEvent ) => {
101+ if ( calcOffsetCache . current ) {
102+ const { offsetX, offsetY } = calcOffsetCache . current ( e . clientX , e . clientY )
103+ calculatePoint ( offsetX , offsetY , false )
104+ }
105+ dragCount . current ++
106+
107+ if ( dragCount . current === 1 && clock . current ) {
108+ clock . current . style . cursor = '-webkit-grabbing'
109+ clock . current . style . cursor = 'grabbing'
110+ }
111+
112+ e . preventDefault ( )
113+ return false
114+ } ,
115+ [ calculatePoint , clock ] ,
116+ )
117+ const handleTouchDrag = useCallback (
118+ ( e : TouchEvent ) => {
119+ if ( calcOffsetCache . current ) {
120+ const touch = e . targetTouches [ 0 ]
121+ const { offsetX, offsetY } = calcOffsetCache . current (
122+ touch . clientX ,
123+ touch . clientY ,
124+ )
125+ calculatePoint ( offsetX , offsetY , false )
126+ }
127+ dragCount . current ++
128+
129+ e . preventDefault ( )
130+ return false
131+ } ,
132+ [ calculatePoint ] ,
133+ )
65134
66- if ( dragCount . current === 1 && clock . current ) {
67- clock . current . style . cursor = '-webkit-grabbing'
68- clock . current . style . cursor = 'grabbing'
69- }
135+ // stop mouse + touch events
136+ const handleStopDrag = useCallback (
137+ ( e : MouseEvent | TouchEvent ) => {
138+ cleanupRef . current ( )
139+
140+ if ( e == null || clock . current == null ) {
141+ return
142+ }
143+
144+ if ( isMouseEventEnd ( e ) ) {
145+ handleMouseUp ( e )
146+ } else if ( isTouchEventEnd ( e ) ) {
147+ handleTouchEnd ( e )
148+ }
149+
150+ function isMouseEventEnd ( e : MouseEvent | TouchEvent ) : e is MouseEvent {
151+ return e . type === 'mouseup'
152+ }
153+ function isTouchEventEnd ( e : MouseEvent | TouchEvent ) : e is TouchEvent {
154+ return e . type === 'touchcancel' || e . type === 'touchend'
155+ }
156+ } ,
157+ [ handleMouseUp , handleTouchEnd , clock ] ,
158+ )
70159
71- e . preventDefault ( )
72- return false
73- }
160+ // mouse events
161+ const handleMouseDown = useCallback (
162+ ( e : React . MouseEvent < HTMLElement > ) => {
163+ dragCount . current = 0
164+
165+ // terminate if click is outside of clock radius, ie:
166+ // if clicking meridiem button which overlaps with clock
167+ if ( clock . current ) {
168+ calcOffsetCache . current = calcOffset ( clock . current )
169+ const { offsetX, offsetY } = calcOffsetCache . current ! (
170+ e . clientX ,
171+ e . clientY ,
172+ )
173+ const x = offsetX - CLOCK_RADIUS
174+ const y = offsetY - CLOCK_RADIUS
175+ if ( ! isWithinRadius ( x , y , CLOCK_RADIUS ) ) return
176+ }
177+
178+ // add listeners
179+ document . addEventListener ( 'mousemove' , handleMouseDrag , false )
180+ document . addEventListener ( 'mouseup' , handleStopDrag , false )
181+ wrapper . current &&
182+ wrapper . current . addEventListener ( 'mouseleave' , handleStopDrag , false )
183+
184+ // @ts -ignore
185+ handleMouseDrag ( e )
186+ } ,
187+ [ clock , handleMouseDrag , handleStopDrag ] ,
188+ )
74189
75190 // touch events
76- function handleTouchStart ( ) {
77- // disables mouse events during touch events
78- disableMouse . current = true
79- dragCount . current = 0
80-
81- // add listeners
82- document . addEventListener ( 'touchmove' , touchDragHandler , false )
83- document . addEventListener ( 'touchend' , handleStopDrag , false )
84- document . addEventListener ( 'touchcancel' , handleStopDrag , false )
85-
86- if ( clock . current ) {
87- calcOffsetCache . current = calcOffset ( clock . current )
88- }
89- }
90- function touchDragHandler ( e : TouchEvent ) {
91- if ( calcOffsetCache . current ) {
92- const touch = e . targetTouches [ 0 ]
93- const { offsetX, offsetY } = calcOffsetCache . current (
94- touch . clientX ,
95- touch . clientY ,
96- )
97- calculatePoint ( offsetX , offsetY , false )
98- }
99- dragCount . current ++
100-
101- e . preventDefault ( )
102- return false
103- }
104-
105- // stop mouse + touch events
106- function handleStopDrag ( e : MouseEvent | TouchEvent ) {
107- _removeHandlers ( )
108-
109- if ( e == null || clock . current == null ) {
110- return
111- }
191+ const handleTouchStart = useCallback (
192+ ( e : TouchEvent ) => {
193+ e . preventDefault ( )
112194
113- if ( isMouseEventEnd ( e ) ) {
114- _handleMouseUp ( e )
115- } else if ( isTouchEventEnd ( e ) ) {
116- _handleTouchEnd ( e )
117- }
195+ dragCount . current = 0
118196
119- function isMouseEventEnd ( e : MouseEvent | TouchEvent ) : e is MouseEvent {
120- return e . type === 'mouseup'
121- }
122- function isTouchEventEnd ( e : MouseEvent | TouchEvent ) : e is TouchEvent {
123- return e . type === 'touchcancel' || e . type === 'touchend'
124- }
125- }
126- function _removeHandlers ( ) {
127- document . removeEventListener ( 'mousemove' , handleMouseDrag , false )
128- document . removeEventListener ( 'mouseup' , handleStopDrag , false )
129- wrapper . current &&
130- wrapper . current . removeEventListener ( 'mouseleave' , handleStopDrag , false )
131-
132- document . removeEventListener ( 'touchmove' , touchDragHandler , false )
133- document . removeEventListener ( 'touchend' , handleStopDrag , false )
134- document . removeEventListener ( 'touchcancel' , handleStopDrag , false )
135- }
197+ // add listeners
198+ document . addEventListener ( 'touchmove' , handleTouchDrag , false )
199+ document . addEventListener ( 'touchend' , handleStopDrag , false )
200+ document . addEventListener ( 'touchcancel' , handleStopDrag , false )
136201
137- // handle mouse + touch changes
138- function _handleMouseUp ( e : MouseEvent ) {
139- if ( ! clock . current ) {
140- return
141- }
142- clock . current . style . cursor = ''
202+ if ( clock . current ) {
203+ calcOffsetCache . current = calcOffset ( clock . current )
204+ }
205+ } ,
206+ [ clock , handleStopDrag , handleTouchDrag ] ,
207+ )
143208
144- const { offsetX, offsetY } = calcOffsetCache . current ! ( e . clientX , e . clientY )
145- calculatePoint ( offsetX , offsetY , true )
146- }
147- function _handleTouchEnd ( e : TouchEvent ) {
148- const touch = e . targetTouches [ 0 ] || e . changedTouches [ 0 ]
149- if ( touch && calcOffsetCache . current ) {
150- const { offsetX, offsetY } = calcOffsetCache . current (
151- touch . clientX ,
152- touch . clientY ,
153- )
154- calculatePoint ( offsetX , offsetY , true )
209+ // attach touchstart event manually to the clock to make it cancelable.
210+ useEffect ( ( ) => {
211+ const currentTarget = clock . current
212+ const type = 'touchstart'
213+ if ( currentTarget ) {
214+ currentTarget . addEventListener ( type , handleTouchStart , false )
155215 }
156-
157- setTimeout ( ( ) => {
158- disableMouse . current = false
159- } , 10 )
160- }
161- function calculatePoint (
162- offsetX : number ,
163- offsetY : number ,
164- // determines if change is due to mouseup/touchend in order to
165- // automatically change unit (eg: hour -> minute) if enabled
166- // prevents changing unit if dragging along clock
167- canAutoChangeMode : boolean ,
168- ) {
169- // if user just clicks/taps a number (drag count < 2), then just assume it's a rough tap
170- // and force a rounded/coarse number (ie: 1, 2, 3, 4 is tapped, assume 0 or 5)
171- const wasTapped = dragCount . current < 2
172-
173- const x = offsetX - CLOCK_RADIUS
174- const y = - offsetY + CLOCK_RADIUS
175-
176- const a = atan2 ( y , x )
177- let d = 90 - deg ( a )
178- if ( d < 0 ) {
179- d = 360 + d
216+ return ( ) => {
217+ if ( currentTarget ) {
218+ currentTarget . removeEventListener ( type , handleTouchStart , false )
219+ }
180220 }
221+ } , [ clock , handleTouchStart ] )
181222
182- // ensure touch doesn't bleed outside of clock radius
183- if ( ! isWithinRadius ( x , y , CLOCK_RADIUS ) && wasTapped ) {
184- return false
223+ /*
224+ deals with circular dependencies of callback functions; add listener function
225+ depends on remove listener function and vice versa
226+ on remove listener function which depends on the add listener cb
227+ */
228+ useEffect ( ( ) => {
229+ cleanupRef . current = ( ) => {
230+ document . removeEventListener ( 'mousemove' , handleMouseDrag , false )
231+ document . removeEventListener ( 'mouseup' , handleStopDrag , false )
232+ wrapper . current &&
233+ wrapper . current . removeEventListener ( 'mouseleave' , handleStopDrag , false )
234+
235+ document . removeEventListener ( 'touchmove' , handleTouchDrag , false )
236+ document . removeEventListener ( 'touchend' , handleStopDrag , false )
237+ document . removeEventListener ( 'touchcancel' , handleStopDrag , false )
185238 }
186- const isInnerClick = isWithinRadius ( x , y , INNER_NUMBER_RADIUS )
187-
188- // update time on main
189- handleChange ( d , { canAutoChangeMode, wasTapped, isInnerClick } )
190- }
239+ } , [ handleMouseDrag , handleStopDrag , handleTouchDrag ] )
191240
192241 // clean up
193242 useEffect ( ( ) => {
194- return cleanup
195- } , [ cleanup ] )
243+ return cleanupRef . current
244+ } , [ ] )
196245
197246 return {
198247 bind : {
199248 onMouseDown : handleMouseDown ,
200- onTouchStart : handleTouchStart ,
201249 ref : wrapper ,
202250 } ,
203251 }
0 commit comments