source: imaps-frontend/node_modules/@use-gesture/core/src/engines/Engine.ts

main
Last change on this file was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

  • Property mode set to 100644
File size: 11.8 KB
Line 
1import { Controller } from '../Controller'
2import { getEventDetails } from '../utils/events'
3import { call } from '../utils/fn'
4import { V, computeRubberband } from '../utils/maths'
5import { GestureKey, IngKey, State, Vector2 } from '../types'
6import { NonUndefined } from '../types'
7
8/**
9 * The lib doesn't compute the kinematics on the last event of the gesture
10 * (i.e. for a drag gesture, the `pointerup` coordinates will generally match the
11 * last `pointermove` coordinates which would result in all drags ending with a
12 * `[0,0]` velocity). However, when the timestamp difference between the last
13 * event (ie pointerup) and the before last event (ie pointermove) is greater
14 * than BEFORE_LAST_KINEMATICS_DELAY, the kinematics are computed (which would
15 * mean that if you release your drag after stopping for more than
16 * BEFORE_LAST_KINEMATICS_DELAY, the velocity will be indeed 0).
17 *
18 * See https://github.com/pmndrs/use-gesture/issues/332 for more details.
19 */
20
21const BEFORE_LAST_KINEMATICS_DELAY = 32
22
23// eslint-disable-next-line @typescript-eslint/no-unused-vars
24export interface Engine<Key extends GestureKey> {
25 /**
26 * Function that some gestures can use to add initilization
27 * properties to the state when it is created.
28 */
29 init?(): void
30 /**
31 * Setup function that some gestures can use to set additional properties of
32 * the state when the gesture starts.
33 */
34 setup?(): void
35 /**
36 * Function used by some gestures to determine the intentionality of a
37 * a movement depending on thresholds. The intent function can change the
38 * `state._active` or `state._blocked` flags if the gesture isn't intentional.
39 * @param event
40 */
41 axisIntent?(event?: UIEvent): void
42
43 restrictToAxis?(movement: Vector2): void
44}
45
46export abstract class Engine<Key extends GestureKey> {
47 /**
48 * The Controller handling state.
49 */
50 ctrl: Controller
51 /**
52 * The gesture key ('drag' | 'pinch' | 'wheel' | 'scroll' | 'move' | 'hover')
53 */
54 readonly key: Key
55 /**
56 * The key representing the active state of the gesture in the shared state.
57 * ('dragging' | 'pinching' | 'wheeling' | 'scrolling' | 'moving' | 'hovering')
58 */
59 abstract readonly ingKey: IngKey
60 /**
61 * The arguments passed to the `bind` function.
62 */
63
64 /**
65 * State prop that aliases state values (`xy` or `da`).
66 */
67 abstract readonly aliasKey: string
68
69 args: any[]
70
71 constructor(ctrl: Controller, args: any[], key: Key) {
72 this.ctrl = ctrl
73 this.args = args
74 this.key = key
75
76 if (!this.state) {
77 this.state = {} as any
78 this.computeValues([0, 0])
79 this.computeInitial()
80
81 if (this.init) this.init()
82 this.reset()
83 }
84 }
85 /**
86 * Function implemented by gestures that compute the offset from the state
87 * movement.
88 */
89 abstract computeOffset(): void
90 /**
91 * Function implemented by the gestures that compute the movement from the
92 * corrected offset (after bounds and potential rubberbanding).
93 */
94 abstract computeMovement(): void
95 /**
96 * Executes the bind function so that listeners are properly set by the
97 * Controller.
98 * @param bindFunction
99 */
100 abstract bind(
101 bindFunction: (
102 device: string,
103 action: string,
104 handler: (event: any) => void,
105 options?: AddEventListenerOptions
106 ) => void
107 ): void
108
109 /**
110 * Shortcut to the gesture state read from the Controller.
111 */
112 get state() {
113 return this.ctrl.state[this.key]!
114 }
115 set state(state) {
116 this.ctrl.state[this.key] = state
117 }
118 /**
119 * Shortcut to the shared state read from the Controller
120 */
121 get shared() {
122 return this.ctrl.state.shared
123 }
124 /**
125 * Shortcut to the gesture event store read from the Controller.
126 */
127 get eventStore() {
128 return this.ctrl.gestureEventStores[this.key]!
129 }
130 /**
131 * Shortcut to the gesture timeout store read from the Controller.
132 */
133 get timeoutStore() {
134 return this.ctrl.gestureTimeoutStores[this.key]!
135 }
136 /**
137 * Shortcut to the gesture config read from the Controller.
138 */
139 get config() {
140 return this.ctrl.config[this.key]!
141 }
142 /**
143 * Shortcut to the shared config read from the Controller.
144 */
145 get sharedConfig() {
146 return this.ctrl.config.shared
147 }
148 /**
149 * Shortcut to the gesture handler read from the Controller.
150 */
151 get handler() {
152 return this.ctrl.handlers[this.key]!
153 }
154
155 reset() {
156 const { state, shared, ingKey, args } = this
157 shared[ingKey] = state._active = state.active = state._blocked = state._force = false
158 state._step = [false, false]
159 state.intentional = false
160 state._movement = [0, 0]
161 state._distance = [0, 0]
162 state._direction = [0, 0]
163 state._delta = [0, 0]
164 // prettier-ignore
165 state._bounds = [[-Infinity, Infinity], [-Infinity, Infinity]]
166 state.args = args
167 state.axis = undefined
168 state.memo = undefined
169 state.elapsedTime = state.timeDelta = 0
170 state.direction = [0, 0]
171 state.distance = [0, 0]
172 state.overflow = [0, 0]
173 state._movementBound = [false, false]
174 state.velocity = [0, 0]
175 state.movement = [0, 0]
176 state.delta = [0, 0]
177 state.timeStamp = 0
178 }
179 /**
180 * Function ran at the start of the gesture.
181 * @param event
182 */
183 start(event: NonUndefined<State[Key]>['event']) {
184 const state = this.state
185 const config = this.config
186 if (!state._active) {
187 this.reset()
188 this.computeInitial()
189
190 state._active = true
191 state.target = event.target!
192 state.currentTarget = event.currentTarget!
193 state.lastOffset = config.from ? call(config.from, state) : state.offset
194 state.offset = state.lastOffset
195 state.startTime = state.timeStamp = event.timeStamp
196 }
197 }
198
199 /**
200 * Assign raw values to `state._values` and transformed values to
201 * `state.values`.
202 * @param values
203 */
204 computeValues(values: Vector2) {
205 const state = this.state
206 state._values = values
207 // transforming values into user-defined coordinates (#402)
208 state.values = this.config.transform(values)
209 }
210
211 /**
212 * Assign `state._values` to `state._initial` and transformed `state.values` to
213 * `state.initial`.
214 * @param values
215 */
216 computeInitial() {
217 const state = this.state
218 state._initial = state._values
219 state.initial = state.values
220 }
221
222 /**
223 * Computes all sorts of state attributes, including kinematics.
224 * @param event
225 */
226 compute(event?: NonUndefined<State[Key]>['event']) {
227 const { state, config, shared } = this
228 state.args = this.args
229
230 let dt = 0
231
232 if (event) {
233 // sets the shared state with event properties
234 state.event = event
235 // if config.preventDefault is true, then preventDefault
236 if (config.preventDefault && event.cancelable) state.event.preventDefault()
237 state.type = event.type
238 shared.touches = this.ctrl.pointerIds.size || this.ctrl.touchIds.size
239 shared.locked = !!document.pointerLockElement
240 Object.assign(shared, getEventDetails(event))
241 shared.down = shared.pressed = shared.buttons % 2 === 1 || shared.touches > 0
242
243 // sets time stamps
244 dt = event.timeStamp - state.timeStamp
245 state.timeStamp = event.timeStamp
246 state.elapsedTime = state.timeStamp - state.startTime
247 }
248
249 // only compute _distance if the state is active otherwise we might compute it
250 // twice when the gesture ends because state._delta wouldn't have changed on
251 // the last frame.
252 if (state._active) {
253 const _absoluteDelta = state._delta.map(Math.abs) as Vector2
254 V.addTo(state._distance, _absoluteDelta)
255 }
256
257 // let's run intentionality check.
258 if (this.axisIntent) this.axisIntent(event)
259
260 // _movement is calculated by each gesture engine
261 const [_m0, _m1] = state._movement
262 const [t0, t1] = config.threshold
263
264 const { _step, values } = state
265
266 if (config.hasCustomTransform) {
267 // When the user is using a custom transform, we're using `_step` to store
268 // the first value passing the threshold.
269 if (_step[0] === false) _step[0] = Math.abs(_m0) >= t0 && values[0]
270 if (_step[1] === false) _step[1] = Math.abs(_m1) >= t1 && values[1]
271 } else {
272 // `_step` will hold the threshold at which point the gesture was triggered.
273 // The threshold is signed depending on which direction triggered it.
274 if (_step[0] === false) _step[0] = Math.abs(_m0) >= t0 && Math.sign(_m0) * t0
275 if (_step[1] === false) _step[1] = Math.abs(_m1) >= t1 && Math.sign(_m1) * t1
276 }
277
278 state.intentional = _step[0] !== false || _step[1] !== false
279
280 if (!state.intentional) return
281
282 const movement: Vector2 = [0, 0]
283
284 if (config.hasCustomTransform) {
285 const [v0, v1] = values
286 movement[0] = _step[0] !== false ? v0 - _step[0] : 0
287 movement[1] = _step[1] !== false ? v1 - _step[1] : 0
288 } else {
289 movement[0] = _step[0] !== false ? _m0 - _step[0] : 0
290 movement[1] = _step[1] !== false ? _m1 - _step[1] : 0
291 }
292
293 if (this.restrictToAxis && !state._blocked) this.restrictToAxis(movement)
294
295 const previousOffset = state.offset
296
297 const gestureIsActive = (state._active && !state._blocked) || state.active
298
299 if (gestureIsActive) {
300 state.first = state._active && !state.active
301 state.last = !state._active && state.active
302 state.active = shared[this.ingKey] = state._active
303
304 if (event) {
305 if (state.first) {
306 if ('bounds' in config) state._bounds = call(config.bounds, state)
307 if (this.setup) this.setup()
308 }
309
310 state.movement = movement
311 this.computeOffset()
312 }
313 }
314
315 const [ox, oy] = state.offset
316 const [[x0, x1], [y0, y1]] = state._bounds
317 state.overflow = [ox < x0 ? -1 : ox > x1 ? 1 : 0, oy < y0 ? -1 : oy > y1 ? 1 : 0]
318
319 // _movementBound will store the latest _movement value
320 // before it went off bounds.
321 state._movementBound[0] = state.overflow[0]
322 ? state._movementBound[0] === false
323 ? state._movement[0]
324 : state._movementBound[0]
325 : false
326
327 state._movementBound[1] = state.overflow[1]
328 ? state._movementBound[1] === false
329 ? state._movement[1]
330 : state._movementBound[1]
331 : false
332
333 // @ts-ignore
334 const rubberband: Vector2 = state._active ? config.rubberband || [0, 0] : [0, 0]
335 state.offset = computeRubberband(state._bounds, state.offset, rubberband)
336 state.delta = V.sub(state.offset, previousOffset)
337
338 this.computeMovement()
339
340 if (gestureIsActive && (!state.last || dt > BEFORE_LAST_KINEMATICS_DELAY)) {
341 state.delta = V.sub(state.offset, previousOffset)
342 const absoluteDelta = state.delta.map(Math.abs) as Vector2
343
344 V.addTo(state.distance, absoluteDelta)
345 state.direction = state.delta.map(Math.sign) as Vector2
346 state._direction = state._delta.map(Math.sign) as Vector2
347
348 // calculates kinematics unless the gesture starts or ends or if the
349 // dt === 0 (which can happen on high frame rate monitors, see issue #581)
350 // because of privacy protection:
351 // https://developer.mozilla.org/en-US/docs/Web/API/Event/timeStamp#reduced_time_precision
352 if (!state.first && dt > 0) {
353 state.velocity = [absoluteDelta[0] / dt, absoluteDelta[1] / dt]
354 state.timeDelta = dt
355 }
356 }
357 }
358 /**
359 * Fires the gesture handler.
360 */
361 emit() {
362 const state = this.state
363 const shared = this.shared
364 const config = this.config
365
366 if (!state._active) this.clean()
367
368 // we don't trigger the handler if the gesture is blocked or non intentional,
369 // unless the `_force` flag was set or the `triggerAllEvents` option was set
370 // to true in the config.
371 if ((state._blocked || !state.intentional) && !state._force && !config.triggerAllEvents) return
372
373 // @ts-ignore
374 const memo = this.handler({ ...shared, ...state, [this.aliasKey]: state.values })
375
376 // Sets memo to the returned value of the handler (unless it's undefined)
377 if (memo !== undefined) state.memo = memo
378 }
379 /**
380 * Cleans the gesture timeouts and event listeners.
381 */
382 clean() {
383 this.eventStore.clean()
384 this.timeoutStore.clean()
385 }
386}
Note: See TracBrowser for help on using the repository browser.