gesturx 0.0.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierrc.json +3 -1
- package/eslint.config.js +18 -9
- package/index.html +2 -2
- package/package.json +16 -3
- package/src/core/index.ts +4 -0
- package/src/core/manager.ts +36 -35
- package/src/core/pan/PanRecognizer.ts +192 -0
- package/src/core/pinch/PinchRecognizer.ts +131 -0
- package/src/core/recognizer/Recognizer.ts +54 -0
- package/src/core/recognizer/RecognizerEvent.ts +39 -0
- package/src/core/recognizer/mapToRecognizerEvent.ts +142 -0
- package/src/core/rotate/RotateRecognizer.ts +115 -0
- package/src/core/swipe/SwipeRecognizer.test.ts +160 -0
- package/src/core/swipe/SwipeRecognizer.ts +82 -0
- package/src/core/swipe/isRecognizedAsSwipe.ts +13 -0
- package/src/core/{tapRecognizer.test.ts → tap/TapRecognizer.test.ts} +162 -137
- package/src/core/tap/TapRecognizer.ts +172 -0
- package/src/core/utils/events.ts +186 -0
- package/src/core/utils/geometry.ts +115 -0
- package/src/core/utils/utils.test.ts +26 -0
- package/src/core/utils/utils.ts +58 -0
- package/src/demo/App.tsx +171 -0
- package/src/demo/DebugBox.tsx +10 -0
- package/src/demo/useManager.ts +40 -0
- package/src/demo/useTap.tsx +87 -0
- package/src/index.css +66 -0
- package/src/main.tsx +14 -0
- package/src/theme.ts +11 -0
- package/tsconfig.json +5 -2
- package/tsconfig.node.json +11 -0
- package/vite.config.ts +34 -29
- package/vitest.config.ts +2 -2
- package/src/core/tapRecognizer.ts +0 -143
- package/src/core/types.ts +0 -3
- package/src/core/utils.ts +0 -211
- package/src/main.ts +0 -19
- package/src/style.css +0 -29
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Observable, map, scan } from "rxjs"
|
|
2
|
+
import { RecognizerEvent } from "./RecognizerEvent"
|
|
3
|
+
import { calculateVelocity } from "../utils/utils"
|
|
4
|
+
import {
|
|
5
|
+
calculateAngleDelta,
|
|
6
|
+
calculateAverageDistance,
|
|
7
|
+
calculateCentroid,
|
|
8
|
+
} from "../utils/geometry"
|
|
9
|
+
|
|
10
|
+
export interface RecognizerEventInput {
|
|
11
|
+
event: PointerEvent
|
|
12
|
+
latestActivePointers: PointerEvent[]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface RecognizerEventState extends RecognizerEventInput {
|
|
16
|
+
startTime: number
|
|
17
|
+
center: { x: number; y: number }
|
|
18
|
+
deltaX: number
|
|
19
|
+
deltaY: number
|
|
20
|
+
deltaPointersAngle: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const calculateNewDelta = (
|
|
24
|
+
newCenter: { x: number; y: number },
|
|
25
|
+
prevCenter: { x: number; y: number },
|
|
26
|
+
prevDeltaX: number,
|
|
27
|
+
prevDeltaY: number,
|
|
28
|
+
) => {
|
|
29
|
+
const deltaX = newCenter.x - prevCenter.x + prevDeltaX
|
|
30
|
+
const deltaY = newCenter.y - prevCenter.y + prevDeltaY
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
deltaX,
|
|
34
|
+
deltaY,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const mapToRecognizerEvent = <T extends RecognizerEventInput>(
|
|
39
|
+
stream: Observable<T>,
|
|
40
|
+
): Observable<RecognizerEvent> => {
|
|
41
|
+
const getCenterFromData = (data: T) =>
|
|
42
|
+
calculateCentroid(
|
|
43
|
+
!data.latestActivePointers.length
|
|
44
|
+
? [data.event]
|
|
45
|
+
: data.latestActivePointers,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
return stream.pipe(
|
|
49
|
+
scan<
|
|
50
|
+
T,
|
|
51
|
+
RecognizerEventState,
|
|
52
|
+
Pick<
|
|
53
|
+
RecognizerEventState,
|
|
54
|
+
"deltaX" | "center" | "deltaY" | "latestActivePointers"
|
|
55
|
+
>
|
|
56
|
+
>(
|
|
57
|
+
(acc, curr) => {
|
|
58
|
+
const newCenter = getCenterFromData(curr)
|
|
59
|
+
const prevCenter = acc.center
|
|
60
|
+
const prevDeltaX = acc.deltaX
|
|
61
|
+
const prevDeltaY = acc.deltaY
|
|
62
|
+
const prevActivePointersNumber = acc.latestActivePointers?.length ?? 0
|
|
63
|
+
const currActivePointersNumber = curr.latestActivePointers?.length ?? 0
|
|
64
|
+
|
|
65
|
+
const hasAddedOrRemovedAFinger =
|
|
66
|
+
[
|
|
67
|
+
"pointerdown",
|
|
68
|
+
"pointercancel",
|
|
69
|
+
"pointerleave",
|
|
70
|
+
"pointerup",
|
|
71
|
+
].includes(curr.event.type) &&
|
|
72
|
+
prevActivePointersNumber !== currActivePointersNumber
|
|
73
|
+
const hasAtLeastOneFinger = currActivePointersNumber > 0
|
|
74
|
+
|
|
75
|
+
const { deltaX, deltaY } =
|
|
76
|
+
hasAddedOrRemovedAFinger && hasAtLeastOneFinger
|
|
77
|
+
? calculateNewDelta(newCenter, newCenter, prevDeltaX, prevDeltaY)
|
|
78
|
+
: calculateNewDelta(newCenter, prevCenter, prevDeltaX, prevDeltaY)
|
|
79
|
+
|
|
80
|
+
const { degreesDelta: deltaPointersAngle } =
|
|
81
|
+
prevActivePointersNumber === currActivePointersNumber &&
|
|
82
|
+
prevActivePointersNumber >= 2 &&
|
|
83
|
+
currActivePointersNumber >= 2
|
|
84
|
+
? calculateAngleDelta(
|
|
85
|
+
acc.latestActivePointers ?? [],
|
|
86
|
+
curr.latestActivePointers ?? [],
|
|
87
|
+
)
|
|
88
|
+
: { degreesDelta: 0 }
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
startTime: new Date().getTime(),
|
|
92
|
+
...acc,
|
|
93
|
+
...curr,
|
|
94
|
+
center: newCenter,
|
|
95
|
+
deltaX,
|
|
96
|
+
deltaY,
|
|
97
|
+
deltaPointersAngle,
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
deltaX: 0,
|
|
102
|
+
deltaY: 0,
|
|
103
|
+
center: { x: 0, y: 0 },
|
|
104
|
+
latestActivePointers: [],
|
|
105
|
+
},
|
|
106
|
+
),
|
|
107
|
+
map((data) => {
|
|
108
|
+
const {
|
|
109
|
+
deltaX,
|
|
110
|
+
deltaY,
|
|
111
|
+
latestActivePointers,
|
|
112
|
+
startTime,
|
|
113
|
+
center,
|
|
114
|
+
event,
|
|
115
|
+
deltaPointersAngle,
|
|
116
|
+
} = data
|
|
117
|
+
const events = latestActivePointers ?? []
|
|
118
|
+
|
|
119
|
+
// Calculate the change in time
|
|
120
|
+
const now = Date.now()
|
|
121
|
+
const delay = now - startTime
|
|
122
|
+
|
|
123
|
+
const { velocityX, velocityY } = calculateVelocity(delay, deltaX, deltaY)
|
|
124
|
+
const pointersAverageDistance =
|
|
125
|
+
calculateAverageDistance(latestActivePointers)
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
deltaX,
|
|
129
|
+
deltaY,
|
|
130
|
+
velocityX,
|
|
131
|
+
velocityY,
|
|
132
|
+
delay,
|
|
133
|
+
center,
|
|
134
|
+
pointers: events,
|
|
135
|
+
startTime,
|
|
136
|
+
event,
|
|
137
|
+
deltaPointersAngle,
|
|
138
|
+
pointersAverageDistance,
|
|
139
|
+
} satisfies RecognizerEvent
|
|
140
|
+
}),
|
|
141
|
+
)
|
|
142
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Observable,
|
|
3
|
+
combineLatest,
|
|
4
|
+
distinctUntilChanged,
|
|
5
|
+
filter,
|
|
6
|
+
first,
|
|
7
|
+
map,
|
|
8
|
+
merge,
|
|
9
|
+
mergeMap,
|
|
10
|
+
scan,
|
|
11
|
+
switchMap,
|
|
12
|
+
takeUntil,
|
|
13
|
+
} from "rxjs"
|
|
14
|
+
import { Recognizer } from "../recognizer/Recognizer"
|
|
15
|
+
import { PanEvent, PanRecognizer } from "../pan/PanRecognizer"
|
|
16
|
+
import { RecognizerEvent } from "../recognizer/RecognizerEvent"
|
|
17
|
+
|
|
18
|
+
export interface RotateEvent extends RecognizerEvent {
|
|
19
|
+
type: "rotate" | "rotateStart" | "rotateEnd"
|
|
20
|
+
/**
|
|
21
|
+
* Current rotation angle
|
|
22
|
+
*/
|
|
23
|
+
angle: number
|
|
24
|
+
/**
|
|
25
|
+
* Delta angle between events
|
|
26
|
+
*/
|
|
27
|
+
deltaAngle: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type Options = {
|
|
31
|
+
posThreshold?: number
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class RotateRecognizer extends Recognizer<Options> {
|
|
35
|
+
public events$: Observable<RotateEvent>
|
|
36
|
+
|
|
37
|
+
constructor(protected options: Options = {}) {
|
|
38
|
+
super(options)
|
|
39
|
+
|
|
40
|
+
this.events$ = this.config$.pipe(
|
|
41
|
+
switchMap((initializedWith) => {
|
|
42
|
+
const { posThreshold } = initializedWith.options ?? {}
|
|
43
|
+
|
|
44
|
+
const panRecognizer = new PanRecognizer({ posThreshold })
|
|
45
|
+
|
|
46
|
+
panRecognizer.initialize(initializedWith)
|
|
47
|
+
|
|
48
|
+
const hasLessThanTwoFinger$ = panRecognizer.events$.pipe(
|
|
49
|
+
filter(({ pointers }) => pointers.length < 2),
|
|
50
|
+
distinctUntilChanged(),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
const hasMoreThanOneFinger$ = panRecognizer.events$.pipe(
|
|
54
|
+
map((event) => [event, event.pointers.length > 1] as const),
|
|
55
|
+
distinctUntilChanged(
|
|
56
|
+
(
|
|
57
|
+
[_, previousHasMoreThanOneFinger],
|
|
58
|
+
[__, currentHasMoreThanOneFinger],
|
|
59
|
+
) => previousHasMoreThanOneFinger === currentHasMoreThanOneFinger,
|
|
60
|
+
),
|
|
61
|
+
filter(([_, hasMoreThanOneFinger]) => hasMoreThanOneFinger),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
const start$ = hasMoreThanOneFinger$.pipe(
|
|
65
|
+
map(([event]) => ({
|
|
66
|
+
...event,
|
|
67
|
+
type: "rotateStart" as const,
|
|
68
|
+
angle: 0,
|
|
69
|
+
deltaAngle: 0,
|
|
70
|
+
})),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
const rotate$ = start$.pipe(
|
|
74
|
+
mergeMap(() =>
|
|
75
|
+
panRecognizer.events$.pipe(
|
|
76
|
+
scan<
|
|
77
|
+
PanEvent,
|
|
78
|
+
RotateEvent,
|
|
79
|
+
Pick<RotateEvent, "angle" | "deltaAngle">
|
|
80
|
+
>(
|
|
81
|
+
(acc, current) => {
|
|
82
|
+
const angle = acc.angle + current.deltaPointersAngle
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
...acc,
|
|
86
|
+
...current,
|
|
87
|
+
type: "rotate" as const,
|
|
88
|
+
angle,
|
|
89
|
+
deltaAngle: current.deltaPointersAngle,
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
{ angle: 0, deltaAngle: 0 },
|
|
93
|
+
),
|
|
94
|
+
takeUntil(hasLessThanTwoFinger$),
|
|
95
|
+
),
|
|
96
|
+
),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
const end$ = start$.pipe(
|
|
100
|
+
mergeMap(() =>
|
|
101
|
+
combineLatest([rotate$, hasLessThanTwoFinger$] as const).pipe(
|
|
102
|
+
first(),
|
|
103
|
+
map(([event]) => ({
|
|
104
|
+
...event,
|
|
105
|
+
type: "rotateEnd" as const,
|
|
106
|
+
})),
|
|
107
|
+
),
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
return merge(start$, rotate$, end$)
|
|
112
|
+
}),
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "vitest"
|
|
2
|
+
import { buffer, first, lastValueFrom, tap, timer } from "rxjs"
|
|
3
|
+
import { SwipeRecognizer } from "./SwipeRecognizer"
|
|
4
|
+
|
|
5
|
+
const waitFor = (time: number) =>
|
|
6
|
+
new Promise((resolve) => setTimeout(resolve, time))
|
|
7
|
+
|
|
8
|
+
describe("SwipeRecognizer", () => {
|
|
9
|
+
let container = document.createElement("div")
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
container = document.createElement("div")
|
|
13
|
+
document.body.appendChild(container)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
function createPointerEvent({
|
|
17
|
+
type,
|
|
18
|
+
x,
|
|
19
|
+
y,
|
|
20
|
+
identifier,
|
|
21
|
+
}: {
|
|
22
|
+
type: string
|
|
23
|
+
x: number
|
|
24
|
+
y: number
|
|
25
|
+
identifier: number
|
|
26
|
+
}) {
|
|
27
|
+
const event = new PointerEvent(type, {
|
|
28
|
+
clientX: x,
|
|
29
|
+
clientY: y,
|
|
30
|
+
pointerId: identifier,
|
|
31
|
+
pointerType: "touch",
|
|
32
|
+
bubbles: true,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// @ts-ignore
|
|
36
|
+
event.x = x
|
|
37
|
+
// @ts-ignore
|
|
38
|
+
event.y = y
|
|
39
|
+
|
|
40
|
+
return event
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function sendPointerEvent({
|
|
44
|
+
type,
|
|
45
|
+
x,
|
|
46
|
+
y,
|
|
47
|
+
container,
|
|
48
|
+
identifier,
|
|
49
|
+
}: {
|
|
50
|
+
container: HTMLElement
|
|
51
|
+
type: string
|
|
52
|
+
x: number
|
|
53
|
+
y: number
|
|
54
|
+
identifier: number
|
|
55
|
+
}) {
|
|
56
|
+
const event = createPointerEvent({
|
|
57
|
+
type,
|
|
58
|
+
x,
|
|
59
|
+
y,
|
|
60
|
+
identifier,
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
sendPointerEventFromEvent({ event, container })
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function sendPointerEventFromEvent({
|
|
67
|
+
event,
|
|
68
|
+
container,
|
|
69
|
+
}: {
|
|
70
|
+
event: PointerEvent
|
|
71
|
+
container: HTMLElement
|
|
72
|
+
}) {
|
|
73
|
+
container.dispatchEvent(event)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ~0.8 velocityX
|
|
77
|
+
const createVelocityOfOnePanMoving = async () => {
|
|
78
|
+
await waitFor(1)
|
|
79
|
+
|
|
80
|
+
sendPointerEvent({
|
|
81
|
+
container,
|
|
82
|
+
identifier: 1,
|
|
83
|
+
type: "pointerdown",
|
|
84
|
+
x: 0,
|
|
85
|
+
y: 0,
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
await waitFor(10)
|
|
89
|
+
|
|
90
|
+
sendPointerEvent({
|
|
91
|
+
container,
|
|
92
|
+
identifier: 1,
|
|
93
|
+
type: "pointermove",
|
|
94
|
+
x: 10,
|
|
95
|
+
y: 0,
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
await waitFor(10)
|
|
99
|
+
|
|
100
|
+
sendPointerEvent({
|
|
101
|
+
container,
|
|
102
|
+
identifier: 1,
|
|
103
|
+
type: "pointerup",
|
|
104
|
+
x: 20,
|
|
105
|
+
y: 0,
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
it("should detect a swipe if above threshold", async () => {
|
|
110
|
+
const waitLongEnough$ = timer(100)
|
|
111
|
+
const recognizer = new SwipeRecognizer({
|
|
112
|
+
escapeVelocity: 0.4,
|
|
113
|
+
posThreshold: 5
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
recognizer.initialize({
|
|
117
|
+
container,
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
const values = await lastValueFrom(
|
|
121
|
+
recognizer.events$.pipe(
|
|
122
|
+
tap({
|
|
123
|
+
subscribe: async () => {
|
|
124
|
+
await createVelocityOfOnePanMoving()
|
|
125
|
+
},
|
|
126
|
+
}),
|
|
127
|
+
buffer(waitLongEnough$),
|
|
128
|
+
first(),
|
|
129
|
+
),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
expect(values).toMatchObject([{ type: "swipe" }])
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it("should not detect a swipe if below threshold", async () => {
|
|
136
|
+
const waitLongEnough$ = timer(100)
|
|
137
|
+
const recognizer = new SwipeRecognizer({
|
|
138
|
+
escapeVelocity: 10,
|
|
139
|
+
posThreshold: 5
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
recognizer.initialize({
|
|
143
|
+
container,
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const values = await lastValueFrom(
|
|
147
|
+
recognizer.events$.pipe(
|
|
148
|
+
tap({
|
|
149
|
+
subscribe: async () => {
|
|
150
|
+
await createVelocityOfOnePanMoving()
|
|
151
|
+
},
|
|
152
|
+
}),
|
|
153
|
+
buffer(waitLongEnough$),
|
|
154
|
+
first(),
|
|
155
|
+
),
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
expect(values).toMatchObject([])
|
|
159
|
+
})
|
|
160
|
+
})
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Observable,
|
|
3
|
+
exhaustMap,
|
|
4
|
+
filter,
|
|
5
|
+
first,
|
|
6
|
+
map,
|
|
7
|
+
share,
|
|
8
|
+
switchMap,
|
|
9
|
+
takeUntil,
|
|
10
|
+
} from "rxjs"
|
|
11
|
+
import { Recognizer } from "../recognizer/Recognizer"
|
|
12
|
+
import { PanRecognizer } from "../pan/PanRecognizer"
|
|
13
|
+
import { RecognizerEvent } from "../recognizer/RecognizerEvent"
|
|
14
|
+
import { calculateDegreeAngleBetweenPoints } from "../utils/geometry"
|
|
15
|
+
import { isRecognizedAsSwipe } from "./isRecognizedAsSwipe"
|
|
16
|
+
|
|
17
|
+
export interface SwipeEvent extends RecognizerEvent {
|
|
18
|
+
type: "swipe"
|
|
19
|
+
/**
|
|
20
|
+
* Angle between first pointer and last one in degree
|
|
21
|
+
*
|
|
22
|
+
* (-90)
|
|
23
|
+
* (+/-180) <- | -> (+/-0)
|
|
24
|
+
* (+90)
|
|
25
|
+
*/
|
|
26
|
+
angle: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type Options = {
|
|
30
|
+
/**
|
|
31
|
+
* The minimum velocity (px/ms) that the gesture has to obtain by the end event.
|
|
32
|
+
*/
|
|
33
|
+
escapeVelocity?: number
|
|
34
|
+
// @todo
|
|
35
|
+
numInputs?: number
|
|
36
|
+
// @todo
|
|
37
|
+
maxRestTime?: number
|
|
38
|
+
posThreshold?: number
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class SwipeRecognizer extends Recognizer<Options> {
|
|
42
|
+
public events$: Observable<SwipeEvent>
|
|
43
|
+
|
|
44
|
+
constructor(protected options: Options = {}) {
|
|
45
|
+
super(options)
|
|
46
|
+
|
|
47
|
+
this.events$ = this.validConfig$.pipe(
|
|
48
|
+
switchMap((initializedWith) => {
|
|
49
|
+
const { escapeVelocity = 0.9, posThreshold } =
|
|
50
|
+
initializedWith.options ?? {}
|
|
51
|
+
const panRecognizer = new PanRecognizer({ posThreshold })
|
|
52
|
+
|
|
53
|
+
panRecognizer.initialize(initializedWith)
|
|
54
|
+
|
|
55
|
+
const hasMoreThanOneFinger$ = panRecognizer.events$.pipe(
|
|
56
|
+
filter(({ pointers }) => pointers.length > 1),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return panRecognizer.start$.pipe(
|
|
60
|
+
exhaustMap((startEvent) => {
|
|
61
|
+
return panRecognizer.end$.pipe(
|
|
62
|
+
first(),
|
|
63
|
+
isRecognizedAsSwipe(escapeVelocity),
|
|
64
|
+
map(({ type, ...rest }) => {
|
|
65
|
+
return {
|
|
66
|
+
type: "swipe" as const,
|
|
67
|
+
angle: calculateDegreeAngleBetweenPoints(
|
|
68
|
+
startEvent.event,
|
|
69
|
+
rest.event,
|
|
70
|
+
),
|
|
71
|
+
...rest,
|
|
72
|
+
}
|
|
73
|
+
}),
|
|
74
|
+
takeUntil(hasMoreThanOneFinger$),
|
|
75
|
+
)
|
|
76
|
+
}),
|
|
77
|
+
share(),
|
|
78
|
+
)
|
|
79
|
+
}),
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Observable, filter } from "rxjs"
|
|
2
|
+
import { PanEvent } from "../pan/PanRecognizer"
|
|
3
|
+
|
|
4
|
+
export const isRecognizedAsSwipe =
|
|
5
|
+
(escapeVelocity: number) => (stream: Observable<PanEvent>) =>
|
|
6
|
+
stream.pipe(
|
|
7
|
+
filter((event) => {
|
|
8
|
+
return (
|
|
9
|
+
Math.abs(event.velocityX) >= escapeVelocity ||
|
|
10
|
+
Math.abs(event.velocityY) >= escapeVelocity
|
|
11
|
+
)
|
|
12
|
+
}),
|
|
13
|
+
)
|