1
+ /*!
2
+ * long-press-event - v@version@
3
+ * Pure JavaScript long-press-event
4
+ * https://github.com/john-doherty/long-press-event
5
+ * @author John Doherty <www.johndoherty.info>
6
+ * @license MIT
7
+ */
8
+ ( function ( window , document ) {
9
+
10
+ 'use strict' ;
11
+
12
+ // local timer object based on rAF
13
+ var timer = null ;
14
+
15
+ // check if we're using a touch screen
16
+ var hasPointerEvents = ( ( 'PointerEvent' in window ) || ( window . navigator && 'msPointerEnabled' in window . navigator ) ) ;
17
+ var isTouch = ( ( 'ontouchstart' in window ) || ( navigator . MaxTouchPoints > 0 ) || ( navigator . msMaxTouchPoints > 0 ) ) ;
18
+
19
+ // switch to pointer events or touch events if using a touch screen
20
+ var mouseDown = hasPointerEvents ? 'pointerdown' : isTouch ? 'touchstart' : 'mousedown' ;
21
+ var mouseUp = hasPointerEvents ? 'pointerup' : isTouch ? 'touchend' : 'mouseup' ;
22
+ var mouseMove = hasPointerEvents ? 'pointermove' : isTouch ? 'touchmove' : 'mousemove' ;
23
+ var mouseLeave = hasPointerEvents ? 'pointerleave' : isTouch ? 'touchleave' : 'mouseleave' ;
24
+
25
+ // track number of pixels the mouse moves during long press
26
+ var startX = 0 ; // mouse x position when timer started
27
+ var startY = 0 ; // mouse y position when timer started
28
+ var maxDiffX = 10 ; // max number of X pixels the mouse can move during long press before it is canceled
29
+ var maxDiffY = 10 ; // max number of Y pixels the mouse can move during long press before it is canceled
30
+
31
+ // patch CustomEvent to allow constructor creation (IE/Chrome)
32
+ if ( typeof window . CustomEvent !== 'function' ) {
33
+
34
+ window . CustomEvent = function ( event , params ) {
35
+
36
+ params = params || { bubbles : false , cancelable : false , detail : undefined } ;
37
+
38
+ var evt = document . createEvent ( 'CustomEvent' ) ;
39
+ evt . initCustomEvent ( event , params . bubbles , params . cancelable , params . detail ) ;
40
+ return evt ;
41
+ } ;
42
+
43
+ window . CustomEvent . prototype = window . Event . prototype ;
44
+ }
45
+
46
+ // requestAnimationFrame() shim by Paul Irish
47
+ window . requestAnimFrame = ( function ( ) {
48
+ return window . requestAnimationFrame ||
49
+ window . webkitRequestAnimationFrame ||
50
+ window . mozRequestAnimationFrame ||
51
+ window . oRequestAnimationFrame ||
52
+ window . msRequestAnimationFrame || function ( callback ) {
53
+ window . setTimeout ( callback , 1000 / 60 ) ;
54
+ } ;
55
+ } ) ( ) ;
56
+
57
+ /**
58
+ * Behaves the same as setTimeout except uses requestAnimationFrame() where possible for better performance
59
+ * @param {function } fn The callback function
60
+ * @param {int } delay The delay in milliseconds
61
+ * @returns {object } handle to the timeout object
62
+ */
63
+ function requestTimeout ( fn , delay ) {
64
+
65
+ if ( ! window . requestAnimationFrame && ! window . webkitRequestAnimationFrame &&
66
+ ! ( window . mozRequestAnimationFrame && window . mozCancelRequestAnimationFrame ) && // Firefox 5 ships without cancel support
67
+ ! window . oRequestAnimationFrame && ! window . msRequestAnimationFrame ) return window . setTimeout ( fn , delay ) ;
68
+
69
+ var start = new Date ( ) . getTime ( ) ;
70
+ var handle = { } ;
71
+
72
+ var loop = function ( ) {
73
+ var current = new Date ( ) . getTime ( ) ;
74
+ var delta = current - start ;
75
+
76
+ if ( delta >= delay ) {
77
+ fn . call ( ) ;
78
+ }
79
+ else {
80
+ handle . value = requestAnimFrame ( loop ) ;
81
+ }
82
+ } ;
83
+
84
+ handle . value = requestAnimFrame ( loop ) ;
85
+
86
+ return handle ;
87
+ }
88
+
89
+ /**
90
+ * Behaves the same as clearTimeout except uses cancelRequestAnimationFrame() where possible for better performance
91
+ * @param {object } handle The callback function
92
+ * @returns {void }
93
+ */
94
+ function clearRequestTimeout ( handle ) {
95
+ if ( handle ) {
96
+ window . cancelAnimationFrame ? window . cancelAnimationFrame ( handle . value ) :
97
+ window . webkitCancelAnimationFrame ? window . webkitCancelAnimationFrame ( handle . value ) :
98
+ window . webkitCancelRequestAnimationFrame ? window . webkitCancelRequestAnimationFrame ( handle . value ) : /* Support for legacy API */
99
+ window . mozCancelRequestAnimationFrame ? window . mozCancelRequestAnimationFrame ( handle . value ) :
100
+ window . oCancelRequestAnimationFrame ? window . oCancelRequestAnimationFrame ( handle . value ) :
101
+ window . msCancelRequestAnimationFrame ? window . msCancelRequestAnimationFrame ( handle . value ) :
102
+ clearTimeout ( handle ) ;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Fires the 'long-press' event on element
108
+ * @param {MouseEvent|PointerEvent|TouchEvent } originalEvent The original event being fired
109
+ * @returns {void }
110
+ */
111
+ function fireLongPressEvent ( originalEvent ) {
112
+
113
+ clearLongPressTimer ( ) ;
114
+
115
+ originalEvent = unifyEvent ( originalEvent ) ;
116
+
117
+ // fire the long-press event
118
+ var allowClickEvent = this . dispatchEvent ( new CustomEvent ( 'long-press' , {
119
+ bubbles : true ,
120
+ cancelable : true ,
121
+
122
+ // custom event data (legacy)
123
+ detail : {
124
+ clientX : originalEvent . clientX ,
125
+ clientY : originalEvent . clientY ,
126
+ offsetX : originalEvent . offsetX ,
127
+ offsetY : originalEvent . offsetY ,
128
+ pageX : originalEvent . pageX ,
129
+ pageY : originalEvent . pageY ,
130
+ originalEvent : originalEvent ,
131
+ } ,
132
+
133
+ // add coordinate and pointer data that would typically acompany a touch/click event
134
+ clientX : originalEvent . clientX ,
135
+ clientY : originalEvent . clientY ,
136
+ offsetX : originalEvent . offsetX ,
137
+ offsetY : originalEvent . offsetY ,
138
+ pageX : originalEvent . pageX ,
139
+ pageY : originalEvent . pageY ,
140
+ screenX : originalEvent . screenX ,
141
+ screenY : originalEvent . screenY ,
142
+ } ) ) ;
143
+
144
+ if ( ! allowClickEvent ) {
145
+ // suppress the next click event if e.preventDefault() was called in long-press handler
146
+ document . addEventListener ( 'click' , function suppressEvent ( e ) {
147
+ document . removeEventListener ( 'click' , suppressEvent , true ) ;
148
+ cancelEvent ( e ) ;
149
+ } , true ) ;
150
+ }
151
+ }
152
+
153
+ /**
154
+ * consolidates mouse, touch, and Pointer events
155
+ * @param {MouseEvent|PointerEvent|TouchEvent } e The original event being fired
156
+ * @returns {MouseEvent|PointerEvent|Touch }
157
+ */
158
+ function unifyEvent ( e ) {
159
+ if ( e . changedTouches !== undefined ) {
160
+ return e . changedTouches [ 0 ] ;
161
+ }
162
+ return e ;
163
+ }
164
+
165
+ /**
166
+ * method responsible for starting the long press timer
167
+ * @param {event } e - event object
168
+ * @returns {void }
169
+ */
170
+ function startLongPressTimer ( e ) {
171
+
172
+ clearLongPressTimer ( e ) ;
173
+
174
+ var el = e . target ;
175
+
176
+ // get delay from html attribute if it exists, otherwise default to 1500
177
+ var longPressDelayInMs = parseInt ( getNearestAttribute ( el , 'data-long-press-delay' , '1500' ) , 10 ) ; // default 1500
178
+
179
+ // start the timer
180
+ timer = requestTimeout ( fireLongPressEvent . bind ( el , e ) , longPressDelayInMs ) ;
181
+ }
182
+
183
+ /**
184
+ * method responsible for clearing a pending long press timer
185
+ * @param {event } e - event object
186
+ * @returns {void }
187
+ */
188
+ function clearLongPressTimer ( e ) {
189
+ clearRequestTimeout ( timer ) ;
190
+ timer = null ;
191
+ }
192
+
193
+ /**
194
+ * Cancels the current event
195
+ * @param {object } e - browser event object
196
+ * @returns {void }
197
+ */
198
+ function cancelEvent ( e ) {
199
+ e . stopImmediatePropagation ( ) ;
200
+ e . preventDefault ( ) ;
201
+ e . stopPropagation ( ) ;
202
+ }
203
+
204
+ /**
205
+ * Starts the timer on mouse down and logs current position
206
+ * @param {object } e - browser event object
207
+ * @returns {void }
208
+ */
209
+ function mouseDownHandler ( e ) {
210
+ startX = e . clientX ;
211
+ startY = e . clientY ;
212
+ startLongPressTimer ( e ) ;
213
+ }
214
+
215
+ /**
216
+ * If the mouse moves n pixels during long-press, cancel the timer
217
+ * @param {object } e - browser event object
218
+ * @returns {void }
219
+ */
220
+ function mouseMoveHandler ( e ) {
221
+
222
+ // calculate total number of pixels the pointer has moved
223
+ var diffX = Math . abs ( startX - e . clientX ) ;
224
+ var diffY = Math . abs ( startY - e . clientY ) ;
225
+
226
+ // if pointer has moved more than allowed, cancel the long-press timer and therefore the event
227
+ if ( diffX >= maxDiffX || diffY >= maxDiffY ) {
228
+ clearLongPressTimer ( e ) ;
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Gets attribute off HTML element or nearest parent
234
+ * @param {object } el - HTML element to retrieve attribute from
235
+ * @param {string } attributeName - name of the attribute
236
+ * @param {any } defaultValue - default value to return if no match found
237
+ * @returns {any } attribute value or defaultValue
238
+ */
239
+ function getNearestAttribute ( el , attributeName , defaultValue ) {
240
+
241
+ // walk up the dom tree looking for data-action and data-trigger
242
+ while ( el && el !== document . documentElement ) {
243
+
244
+ var attributeValue = el . getAttribute ( attributeName ) ;
245
+
246
+ if ( attributeValue ) {
247
+ return attributeValue ;
248
+ }
249
+
250
+ el = el . parentNode ;
251
+ }
252
+
253
+ return defaultValue ;
254
+ }
255
+
256
+ // hook events that clear a pending long press event
257
+ document . addEventListener ( mouseUp , clearLongPressTimer , true ) ;
258
+ document . addEventListener ( mouseLeave , clearLongPressTimer , true ) ;
259
+ document . addEventListener ( mouseMove , mouseMoveHandler , true ) ;
260
+ document . addEventListener ( 'wheel' , clearLongPressTimer , true ) ;
261
+ document . addEventListener ( 'scroll' , clearLongPressTimer , true ) ;
262
+
263
+ // hook events that can trigger a long press event
264
+ document . addEventListener ( mouseDown , mouseDownHandler , true ) ; // <- start
265
+
266
+ } ( window , document ) ) ;
0 commit comments