/
toucheventprovider.ts
161 lines (132 loc) · 5.67 KB
/
toucheventprovider.ts
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
/* spellchecker: disable */
import { Observable, ReplaySubject } from 'rxjs';
import { assert, bitInBitfield } from './auxiliaries';
/* spellchecker: enable */
export class TouchEventProvider {
/**
* HTML canvas element within the HTML5 document to register event listeners to.
*/
protected _element: HTMLCanvasElement;
/**
* Time frame for events to be buffered (windowTime in rxjs per ReplaySubject).
*/
protected _timeframe: number | undefined;
protected _startListener: { (event: TouchEvent): void };
protected _startSubject: ReplaySubject<TouchEvent>;
protected _endListener: { (event: TouchEvent): void };
protected _endSubject: ReplaySubject<TouchEvent>;
protected _moveListener: { (event: TouchEvent): void };
protected _moveSubject: ReplaySubject<TouchEvent>;
protected _cancelListener: { (event: TouchEvent): void };
protected _cancelSubject: ReplaySubject<TouchEvent>;
/**
* This mask saves for which types of events, event.preventDefault should be called.
* This is useful to disallow some kinds of standard events like scrolling or clicking on links.
*/
protected _preventDefaultMask: TouchEventProvider.Type;
constructor(element: HTMLCanvasElement, timeframe?: number) {
assert(element !== undefined, `expected valid canvas element on initialization, given ${element}`);
this._element = element;
this._timeframe = timeframe;
}
/**
* Checks whether or not to prevent the default handling of the given event. This depends on the internal
* `preventDefaultMask` which can be modified using `preventDefault` function @see{@link prevenDefault}.
* @param type - Internal event type of the incoming event.
* @param event - Actual event to prevent default handling on (if masked).
*/
protected preventDefaultOnEvent(type: TouchEventProvider.Type, event: TouchEvent): void {
if (bitInBitfield(this._preventDefaultMask, type)) {
event.preventDefault();
}
}
/**
* Prevent default event handling on specific event type (using prevenDefault on the event).
* @param types - Event types to prevent default handling on.
*/
preventDefault(...types: TouchEventProvider.Type[]): void {
for (const type of types) {
if (!bitInBitfield(this._preventDefaultMask, type)) {
this._preventDefaultMask |= type;
}
}
}
/**
* Allow default event handling on specific event type (not calling preventDefault on the event).
* @param types - Event types to allow default handling on.
*/
allowDefault(...types: TouchEventProvider.Type[]): void {
for (const type of types) {
if (bitInBitfield(this._preventDefaultMask, type)) {
this._preventDefaultMask &= ~type;
}
}
}
observable(type: TouchEventProvider.Type): Observable<TouchEvent> {
/* tslint:disable-next-line:switch-default */
switch (type) {
case TouchEventProvider.Type.Start:
return this.start$;
case TouchEventProvider.Type.End:
return this.end$;
case TouchEventProvider.Type.Move:
return this.move$;
case TouchEventProvider.Type.Cancel:
return this.cancel$;
}
assert(false, 'Encountered unknown touch event.');
return new Observable<TouchEvent>();
}
get start$(): Observable<TouchEvent> {
if (this._startSubject === undefined) {
this._startSubject = new ReplaySubject<TouchEvent>(undefined, this._timeframe);
this._startListener = (event: TouchEvent) => {
this.preventDefaultOnEvent(TouchEventProvider.Type.Start, event);
this._startSubject.next(event);
};
this._element.addEventListener('touchstart', this._startListener);
}
return this._startSubject.asObservable();
}
get end$(): Observable<TouchEvent> {
if (this._endSubject === undefined) {
this._endSubject = new ReplaySubject<TouchEvent>(undefined, this._timeframe);
this._endListener = (event: TouchEvent) => {
this.preventDefaultOnEvent(TouchEventProvider.Type.End, event);
this._endSubject.next(event);
};
this._element.addEventListener('touchend', this._endListener);
}
return this._endSubject.asObservable();
}
get move$(): Observable<TouchEvent> {
if (this._moveSubject === undefined) {
this._moveSubject = new ReplaySubject<TouchEvent>(undefined, this._timeframe);
this._moveListener = (event: TouchEvent) => {
this.preventDefaultOnEvent(TouchEventProvider.Type.Move, event);
this._moveSubject.next(event);
};
this._element.addEventListener('touchmove', this._moveListener);
}
return this._moveSubject.asObservable();
}
get cancel$(): Observable<TouchEvent> {
if (this._cancelSubject === undefined) {
this._cancelSubject = new ReplaySubject<TouchEvent>(undefined, this._timeframe);
this._cancelListener = (event: TouchEvent) => {
this.preventDefaultOnEvent(TouchEventProvider.Type.Cancel, event);
this._cancelSubject.next(event);
};
this._element.addEventListener('touchcancel', this._cancelListener);
}
return this._cancelSubject.asObservable();
}
}
export namespace TouchEventProvider {
export enum Type {
Start = 1 << 0,
End = 1 << 1,
Move = 1 << 2,
Cancel = 1 << 3,
}
}