-
Notifications
You must be signed in to change notification settings - Fork 332
Expand file tree
/
Copy pathController.ts
More file actions
188 lines (177 loc) · 6.27 KB
/
Controller.ts
File metadata and controls
188 lines (177 loc) · 6.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
import { EngineMap } from './actions'
import { parse } from './config/resolver'
import { isTouch, parseProp, toHandlerProp, touchIds } from './utils/events'
import { EventStore } from './EventStore'
import { TimeoutStore } from './TimeoutStore'
import { chain } from './utils/fn'
import { GestureKey, InternalConfig, InternalHandlers, NativeHandlers, State, UserGestureConfig } from './types'
/**
* The Controller class is responsible for managing the state of gestures.
*/
export class Controller {
/**
* The list of gestures handled by the Controller.
*/
public gestures = new Set<GestureKey>()
/**
* The event store that keeps track of the config.target listeners.
*/
private _targetEventStore = new EventStore(this)
/**
* Object that keeps track of all gesture event listeners.
*/
public gestureEventStores: { [key in GestureKey]?: EventStore } = {}
public gestureTimeoutStores: { [key in GestureKey]?: TimeoutStore } = {}
public handlers: InternalHandlers = {}
private nativeHandlers?: NativeHandlers
public config = {} as InternalConfig
public pointerIds = new Set<number>()
public touchIds = new Set<number>()
public state = {
shared: {
shiftKey: false,
metaKey: false,
ctrlKey: false,
altKey: false
}
} as State
constructor(handlers: InternalHandlers) {
resolveGestures(this, handlers)
}
/**
* Sets pointer or touch ids based on the event.
* @param event
*/
setEventIds(event: TouchEvent | PointerEvent) {
if (isTouch(event)) {
this.touchIds = new Set(touchIds(event as TouchEvent))
return this.touchIds
} else if ('pointerId' in event) {
if (event.type === 'pointerup' || event.type === 'pointercancel') this.pointerIds.delete(event.pointerId)
else if (event.type === 'pointerdown') this.pointerIds.add(event.pointerId)
return this.pointerIds
}
}
/**
* Attaches handlers to the controller.
* @param handlers
* @param nativeHandlers
*/
applyHandlers(handlers: InternalHandlers, nativeHandlers?: NativeHandlers) {
this.handlers = handlers
this.nativeHandlers = nativeHandlers
}
/**
* Compute and attaches a config to the controller.
* @param config
* @param gestureKey
*/
applyConfig(config: UserGestureConfig, gestureKey?: GestureKey) {
this.config = parse(config, gestureKey, this.config)
}
/**
* Cleans all side effects (listeners, timeouts). When the gesture is
* destroyed (in React, when the component is unmounted.)
*/
clean() {
this._targetEventStore.clean()
for (const key of this.gestures) {
this.gestureEventStores[key]!.clean()
this.gestureTimeoutStores[key]!.clean()
}
}
/**
* Executes side effects (attaching listeners to a `config.target`). Ran on
* each render.
*/
effect() {
if (this.config.shared.target) this.bind()
return () => this._targetEventStore.clean()
}
/**
* The bind function that can be returned by the gesture handler (a hook in
* React for example.)
* @param args
*/
bind(...args: any[]) {
const sharedConfig = this.config.shared
const props: any = {}
let target
if (sharedConfig.target) {
target = sharedConfig.target()
// if target is undefined let's stop
if (!target) return
}
if (sharedConfig.enabled) {
// Adding gesture handlers
for (const gestureKey of this.gestures) {
const gestureConfig = this.config[gestureKey]!
const bindFunction = bindToProps(props, gestureConfig.eventOptions, !!target)
if (gestureConfig.enabled) {
const Engine = EngineMap.get(gestureKey)!
// @ts-ignore
new Engine(this, args, gestureKey).bind(bindFunction)
}
}
// Adding native handlers
const nativeBindFunction = bindToProps(props, sharedConfig.eventOptions, !!target)
for (const eventKey in this.nativeHandlers) {
nativeBindFunction(
eventKey,
'',
// @ts-ignore
(event) => this.nativeHandlers[eventKey]({ ...this.state.shared, event, args }),
undefined,
true
)
}
}
// If target isn't set, we return an object that contains gesture handlers
// mapped to props handler event keys.
for (const handlerProp in props) {
props[handlerProp] = chain(...props[handlerProp])
}
// When target isn't specified then return hanlder props.
if (!target) return props
// When target is specified, then add listeners to the controller target
// store.
for (const handlerProp in props) {
const { device, capture, passive } = parseProp(handlerProp)
this._targetEventStore.add(target, device, '', props[handlerProp], { capture, passive })
}
}
}
function setupGesture(ctrl: Controller, gestureKey: GestureKey) {
ctrl.gestures.add(gestureKey)
ctrl.gestureEventStores[gestureKey] = new EventStore(ctrl, gestureKey)
ctrl.gestureTimeoutStores[gestureKey] = new TimeoutStore()
}
function resolveGestures(ctrl: Controller, internalHandlers: InternalHandlers) {
// make sure hover handlers are added first to prevent bugs such as #322
// where the hover pointerLeave handler is removed before the move
// pointerLeave, which prevents hovering: false to be fired.
if (internalHandlers.drag) setupGesture(ctrl, 'drag')
if (internalHandlers.wheel) setupGesture(ctrl, 'wheel')
if (internalHandlers.scroll) setupGesture(ctrl, 'scroll')
if (internalHandlers.move) setupGesture(ctrl, 'move')
if (internalHandlers.pinch) setupGesture(ctrl, 'pinch')
if (internalHandlers.hover) setupGesture(ctrl, 'hover')
if (internalHandlers.tap) setupGesture(ctrl, 'tap')
}
const bindToProps =
(props: any, eventOptions: AddEventListenerOptions, withPassiveOption: boolean) =>
(
device: string,
action: string,
handler: (event: any) => void,
options: AddEventListenerOptions = {},
isNative = false
) => {
const capture = options.capture ?? eventOptions.capture
const passive = options.passive ?? eventOptions.passive
// a native handler is already passed as a prop like "onMouseDown"
let handlerProp = isNative ? device : toHandlerProp(device, action, capture)
if (withPassiveOption && passive) handlerProp += 'Passive'
props[handlerProp] = props[handlerProp] || []
props[handlerProp].push(handler)
}