gesturx 1.0.0 → 1.2.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/README.md +17 -1
- package/dist/index.d.ts +192 -0
- package/dist/index.js +627 -0
- package/dist/index.js.map +1 -0
- package/dist/index.umd.cjs +2 -0
- package/dist/index.umd.cjs.map +1 -0
- package/package.json +12 -1
- package/.github/workflows/publish.yml +0 -17
- package/.prettierrc.json +0 -3
- package/eslint.config.js +0 -18
- package/index.html +0 -13
- package/src/core/index.ts +0 -4
- package/src/core/manager.ts +0 -46
- package/src/core/pan/PanRecognizer.ts +0 -192
- package/src/core/pinch/PinchRecognizer.ts +0 -131
- package/src/core/recognizer/Recognizer.ts +0 -54
- package/src/core/recognizer/RecognizerEvent.ts +0 -39
- package/src/core/recognizer/mapToRecognizerEvent.ts +0 -142
- package/src/core/rotate/RotateRecognizer.ts +0 -115
- package/src/core/swipe/SwipeRecognizer.test.ts +0 -160
- package/src/core/swipe/SwipeRecognizer.ts +0 -82
- package/src/core/swipe/isRecognizedAsSwipe.ts +0 -13
- package/src/core/tap/TapRecognizer.test.ts +0 -567
- package/src/core/tap/TapRecognizer.ts +0 -172
- package/src/core/utils/events.ts +0 -186
- package/src/core/utils/geometry.ts +0 -115
- package/src/core/utils/utils.test.ts +0 -26
- package/src/core/utils/utils.ts +0 -58
- package/src/demo/App.tsx +0 -171
- package/src/demo/DebugBox.tsx +0 -10
- package/src/demo/useManager.ts +0 -40
- package/src/demo/useTap.tsx +0 -87
- package/src/index.css +0 -66
- package/src/main.tsx +0 -14
- package/src/theme.ts +0 -11
- package/src/vite-env.d.ts +0 -1
- package/tsconfig.json +0 -26
- package/tsconfig.node.json +0 -11
- package/vite.config.ts +0 -38
- package/vitest.config.ts +0 -8
- /package/{public → dist}/vite.svg +0 -0
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Observable,
|
|
3
|
-
buffer,
|
|
4
|
-
combineLatest,
|
|
5
|
-
debounceTime,
|
|
6
|
-
exhaustMap,
|
|
7
|
-
filter,
|
|
8
|
-
first,
|
|
9
|
-
ignoreElements,
|
|
10
|
-
map,
|
|
11
|
-
merge,
|
|
12
|
-
of,
|
|
13
|
-
share,
|
|
14
|
-
shareReplay,
|
|
15
|
-
switchMap,
|
|
16
|
-
takeUntil,
|
|
17
|
-
timer,
|
|
18
|
-
withLatestFrom,
|
|
19
|
-
} from "rxjs"
|
|
20
|
-
import {
|
|
21
|
-
fromFailWith,
|
|
22
|
-
filterNotEmpty,
|
|
23
|
-
isOutsidePosThreshold,
|
|
24
|
-
} from "../utils/utils"
|
|
25
|
-
import { Recognizer, RecognizerOptions } from "../recognizer/Recognizer"
|
|
26
|
-
import { RecognizerEvent } from "../recognizer/RecognizerEvent"
|
|
27
|
-
import { mapToRecognizerEvent } from "../recognizer/mapToRecognizerEvent"
|
|
28
|
-
import {
|
|
29
|
-
fromPointerDown,
|
|
30
|
-
getPointerEvents,
|
|
31
|
-
trackFingers,
|
|
32
|
-
} from "../utils/events"
|
|
33
|
-
|
|
34
|
-
export interface TapEvent extends RecognizerEvent {
|
|
35
|
-
type: "tap"
|
|
36
|
-
taps: number
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
interface Options extends RecognizerOptions {
|
|
40
|
-
// Maximum time in ms between multiple taps.
|
|
41
|
-
multiTapThreshold?: number
|
|
42
|
-
// Maximum press time in ms.
|
|
43
|
-
maximumPressTime?: number
|
|
44
|
-
maxTaps?: number
|
|
45
|
-
posThreshold?: number
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export class TapRecognizer extends Recognizer<Options> {
|
|
49
|
-
public events$: Observable<TapEvent>
|
|
50
|
-
|
|
51
|
-
constructor(protected options: Options) {
|
|
52
|
-
super(options)
|
|
53
|
-
|
|
54
|
-
this.events$ = this.validConfig$.pipe(
|
|
55
|
-
switchMap((config) => {
|
|
56
|
-
const { container, afterEventReceived } = config
|
|
57
|
-
const {
|
|
58
|
-
multiTapThreshold = 200,
|
|
59
|
-
maximumPressTime = 250,
|
|
60
|
-
maxTaps = 2,
|
|
61
|
-
// threshold should be high because of fingers size
|
|
62
|
-
// and potential margin due to it. clicks are nearly perfect
|
|
63
|
-
// not fingers.
|
|
64
|
-
posThreshold = 10,
|
|
65
|
-
failWith,
|
|
66
|
-
} = config.options ?? {}
|
|
67
|
-
|
|
68
|
-
const { pointerUp$, pointerLeave$, pointerCancel$, pointerMove$ } =
|
|
69
|
-
getPointerEvents({
|
|
70
|
-
container,
|
|
71
|
-
afterEventReceived,
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
const pointerDown$ = fromPointerDown({ container, afterEventReceived })
|
|
75
|
-
const activePointers$ = pointerDown$.pipe(
|
|
76
|
-
trackFingers({
|
|
77
|
-
pointerCancel$,
|
|
78
|
-
pointerLeave$,
|
|
79
|
-
pointerMove$,
|
|
80
|
-
pointerUp$,
|
|
81
|
-
trackMove: false,
|
|
82
|
-
}),
|
|
83
|
-
shareReplay(1),
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
const hasMoreThanOneActivePointer$ = activePointers$.pipe(
|
|
87
|
-
filter((pointers) => pointers.length > 1),
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
const onlyOnePointer = <T>(stream: Observable<T>) =>
|
|
91
|
-
stream.pipe(
|
|
92
|
-
withLatestFrom(activePointers$),
|
|
93
|
-
filter(([, pointers]) => pointers.length === 1),
|
|
94
|
-
map(([pointer]) => pointer),
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
const bufferPointerDowns = (stream: Observable<PointerEvent>) =>
|
|
98
|
-
stream.pipe(
|
|
99
|
-
buffer(pointerUp$.pipe(debounceTime(multiTapThreshold))),
|
|
100
|
-
first(),
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
const failWith$ = fromFailWith(failWith)
|
|
104
|
-
|
|
105
|
-
const tap$ = merge(
|
|
106
|
-
pointerDown$,
|
|
107
|
-
activePointers$.pipe(ignoreElements()),
|
|
108
|
-
).pipe(
|
|
109
|
-
onlyOnePointer,
|
|
110
|
-
exhaustMap((initialPointerEvent) => {
|
|
111
|
-
const pointerDowns$ = merge(of(initialPointerEvent), pointerDown$)
|
|
112
|
-
const subsequentPointerEvents$ = merge(
|
|
113
|
-
pointerUp$,
|
|
114
|
-
pointerLeave$,
|
|
115
|
-
pointerCancel$,
|
|
116
|
-
pointerMove$,
|
|
117
|
-
)
|
|
118
|
-
const pointerDownsBuffered$ = pointerDowns$.pipe(bufferPointerDowns)
|
|
119
|
-
const subsequentPointersOutOfPositionThreshold$ =
|
|
120
|
-
subsequentPointerEvents$.pipe(
|
|
121
|
-
filter((event) =>
|
|
122
|
-
isOutsidePosThreshold(
|
|
123
|
-
initialPointerEvent,
|
|
124
|
-
event,
|
|
125
|
-
posThreshold,
|
|
126
|
-
),
|
|
127
|
-
),
|
|
128
|
-
first(),
|
|
129
|
-
)
|
|
130
|
-
const waitedTooLong$ = activePointers$.pipe(
|
|
131
|
-
switchMap(() => timer(maximumPressTime)),
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
const takeUntil$ = merge(
|
|
135
|
-
failWith$,
|
|
136
|
-
pointerCancel$,
|
|
137
|
-
hasMoreThanOneActivePointer$,
|
|
138
|
-
subsequentPointersOutOfPositionThreshold$,
|
|
139
|
-
waitedTooLong$,
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
const rawEvent$ = pointerDownsBuffered$.pipe(
|
|
143
|
-
filterNotEmpty,
|
|
144
|
-
filter((events) => events.length <= maxTaps),
|
|
145
|
-
map((events) => ({
|
|
146
|
-
type: "tap" as const,
|
|
147
|
-
taps: events.length,
|
|
148
|
-
latestActivePointers: events,
|
|
149
|
-
event: events[0],
|
|
150
|
-
})),
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
return combineLatest([
|
|
154
|
-
rawEvent$.pipe(mapToRecognizerEvent),
|
|
155
|
-
rawEvent$,
|
|
156
|
-
]).pipe(
|
|
157
|
-
map(([recognizerEvent, { type, taps }]) => ({
|
|
158
|
-
...recognizerEvent,
|
|
159
|
-
type,
|
|
160
|
-
taps,
|
|
161
|
-
})),
|
|
162
|
-
takeUntil(takeUntil$),
|
|
163
|
-
)
|
|
164
|
-
}),
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
return tap$
|
|
168
|
-
}),
|
|
169
|
-
share(),
|
|
170
|
-
)
|
|
171
|
-
}
|
|
172
|
-
}
|
package/src/core/utils/events.ts
DELETED
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
NEVER,
|
|
3
|
-
Observable,
|
|
4
|
-
defer,
|
|
5
|
-
endWith,
|
|
6
|
-
filter,
|
|
7
|
-
fromEvent,
|
|
8
|
-
map,
|
|
9
|
-
merge,
|
|
10
|
-
mergeMap,
|
|
11
|
-
of,
|
|
12
|
-
scan,
|
|
13
|
-
takeUntil,
|
|
14
|
-
} from "rxjs"
|
|
15
|
-
import { isDefined } from "./utils"
|
|
16
|
-
|
|
17
|
-
export function matchPointer(pointer: PointerEvent) {
|
|
18
|
-
return (stream: Observable<PointerEvent>) =>
|
|
19
|
-
stream.pipe(
|
|
20
|
-
filter((newPointer) => newPointer.pointerId === pointer.pointerId),
|
|
21
|
-
)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const getPointerEvents = ({
|
|
25
|
-
container,
|
|
26
|
-
afterEventReceived = (event) => event,
|
|
27
|
-
}: {
|
|
28
|
-
container: HTMLElement
|
|
29
|
-
afterEventReceived?: (event: PointerEvent) => PointerEvent
|
|
30
|
-
}) => {
|
|
31
|
-
const pointerDown$ = fromPointerDown({ afterEventReceived, container })
|
|
32
|
-
const pointerUp$ = fromEvent<PointerEvent>(container, "pointerup").pipe(
|
|
33
|
-
map(afterEventReceived),
|
|
34
|
-
)
|
|
35
|
-
const pointerMove$ = fromEvent<PointerEvent>(container, "pointermove").pipe(
|
|
36
|
-
map(afterEventReceived),
|
|
37
|
-
)
|
|
38
|
-
/**
|
|
39
|
-
* Mostly used to handle cursor leaving window on mouse pointers.
|
|
40
|
-
*/
|
|
41
|
-
const pointerLeave$ = fromEvent<PointerEvent>(container, "pointerleave").pipe(
|
|
42
|
-
map(afterEventReceived),
|
|
43
|
-
)
|
|
44
|
-
/**
|
|
45
|
-
* Handle things such as contextual menu popup or
|
|
46
|
-
* native swipe navigation conflict.
|
|
47
|
-
*/
|
|
48
|
-
const pointerCancel$ = fromEvent<PointerEvent>(
|
|
49
|
-
container,
|
|
50
|
-
"pointercancel",
|
|
51
|
-
).pipe(map(afterEventReceived))
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
pointerDown$,
|
|
55
|
-
pointerCancel$,
|
|
56
|
-
pointerLeave$,
|
|
57
|
-
pointerUp$,
|
|
58
|
-
pointerMove$,
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export const fromPointerDown = ({
|
|
63
|
-
container,
|
|
64
|
-
afterEventReceived = (event) => event,
|
|
65
|
-
}: {
|
|
66
|
-
container: HTMLElement
|
|
67
|
-
afterEventReceived?: (event: PointerEvent) => PointerEvent
|
|
68
|
-
}) =>
|
|
69
|
-
fromEvent<PointerEvent>(container, "pointerdown").pipe(
|
|
70
|
-
map(afterEventReceived),
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* @todo test the strict stability
|
|
75
|
-
* @important
|
|
76
|
-
* Stable function, will not emit if the pointers don't change
|
|
77
|
-
*/
|
|
78
|
-
export const trackActivePointers = ({
|
|
79
|
-
container,
|
|
80
|
-
}: {
|
|
81
|
-
container: HTMLElement
|
|
82
|
-
}) => {
|
|
83
|
-
const pointerUp$ = fromEvent<PointerEvent>(container, "pointerup")
|
|
84
|
-
const pointerLeave$ = fromEvent<PointerEvent>(container, "pointerleave")
|
|
85
|
-
const pointerCancel$ = fromEvent<PointerEvent>(container, "pointercancel")
|
|
86
|
-
|
|
87
|
-
return fromEvent<PointerEvent>(container, "pointerdown").pipe(
|
|
88
|
-
mergeMap((pointerDown) => {
|
|
89
|
-
const pointerDownRelease$ = merge(
|
|
90
|
-
pointerUp$,
|
|
91
|
-
pointerLeave$,
|
|
92
|
-
pointerCancel$,
|
|
93
|
-
).pipe(matchPointer(pointerDown))
|
|
94
|
-
const pointerDown$ = defer(() => of(pointerDown))
|
|
95
|
-
|
|
96
|
-
return pointerDown$.pipe(
|
|
97
|
-
mergeMap(() => merge(pointerDown$, NEVER)),
|
|
98
|
-
map((event) => ({ id: event.pointerId, event })),
|
|
99
|
-
takeUntil(pointerDownRelease$),
|
|
100
|
-
endWith({ id: pointerDown.pointerId, event: undefined }),
|
|
101
|
-
)
|
|
102
|
-
}),
|
|
103
|
-
scan(
|
|
104
|
-
(acc, { event, id }) => {
|
|
105
|
-
if (!event) {
|
|
106
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
107
|
-
const { [id]: _deleted, ...rest } = acc
|
|
108
|
-
|
|
109
|
-
return rest
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return {
|
|
113
|
-
...acc,
|
|
114
|
-
[id]: event,
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
{} as Record<number, PointerEvent | undefined>,
|
|
118
|
-
),
|
|
119
|
-
map((events) => Object.values(events).filter(isDefined)),
|
|
120
|
-
)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Track all pointerEvent for active fingers
|
|
125
|
-
*/
|
|
126
|
-
export const trackFingers =
|
|
127
|
-
({
|
|
128
|
-
pointerCancel$,
|
|
129
|
-
pointerLeave$,
|
|
130
|
-
pointerUp$,
|
|
131
|
-
pointerMove$,
|
|
132
|
-
trackMove,
|
|
133
|
-
}: {
|
|
134
|
-
pointerUp$: Observable<PointerEvent>
|
|
135
|
-
pointerLeave$: Observable<PointerEvent>
|
|
136
|
-
pointerCancel$: Observable<PointerEvent>
|
|
137
|
-
pointerMove$: Observable<PointerEvent>
|
|
138
|
-
/**
|
|
139
|
-
* Setting this value to false will not update the list
|
|
140
|
-
* on pointer move. Use it if you only need to track number of active
|
|
141
|
-
* fingers.
|
|
142
|
-
*/
|
|
143
|
-
trackMove: boolean
|
|
144
|
-
}) =>
|
|
145
|
-
(stream: Observable<PointerEvent>) => {
|
|
146
|
-
return stream.pipe(
|
|
147
|
-
mergeMap((pointerDown) => {
|
|
148
|
-
const pointerDownRelease$ = merge(
|
|
149
|
-
pointerUp$,
|
|
150
|
-
pointerLeave$,
|
|
151
|
-
pointerCancel$,
|
|
152
|
-
).pipe(matchPointer(pointerDown))
|
|
153
|
-
|
|
154
|
-
const pointerDown$ = defer(() => of(pointerDown))
|
|
155
|
-
|
|
156
|
-
const tracking$ = merge(
|
|
157
|
-
pointerDown$,
|
|
158
|
-
trackMove ? pointerMove$ : NEVER,
|
|
159
|
-
).pipe(
|
|
160
|
-
matchPointer(pointerDown),
|
|
161
|
-
map((event) => ({ id: event.pointerId, event })),
|
|
162
|
-
takeUntil(pointerDownRelease$),
|
|
163
|
-
endWith({ id: pointerDown.pointerId, event: undefined }),
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
return tracking$
|
|
167
|
-
}),
|
|
168
|
-
scan(
|
|
169
|
-
(acc, { event, id }) => {
|
|
170
|
-
if (!event) {
|
|
171
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
172
|
-
const { [id]: _deleted, ...rest } = acc
|
|
173
|
-
|
|
174
|
-
return rest
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return {
|
|
178
|
-
...acc,
|
|
179
|
-
[id]: event,
|
|
180
|
-
}
|
|
181
|
-
},
|
|
182
|
-
{} as Record<number, PointerEvent | undefined>,
|
|
183
|
-
),
|
|
184
|
-
map((events) => Object.values(events).filter(isDefined)),
|
|
185
|
-
)
|
|
186
|
-
}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
type Point = { x: number; y: number }
|
|
2
|
-
|
|
3
|
-
export function calculateDistance(pointA: Point, pointB: Point): number {
|
|
4
|
-
return Math.sqrt(
|
|
5
|
-
Math.pow(pointB.x - pointA.x, 2) + Math.pow(pointB.y - pointA.y, 2),
|
|
6
|
-
)
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function calculateAverageDistance(pointerEvents: Point[]): number {
|
|
10
|
-
const n = pointerEvents.length
|
|
11
|
-
if (n < 2) return 0 // No distance if fewer than 2 pointers
|
|
12
|
-
|
|
13
|
-
const totalDistance = pointerEvents.reduce((sum, event1, i) => {
|
|
14
|
-
return (
|
|
15
|
-
sum +
|
|
16
|
-
pointerEvents.slice(i + 1).reduce((innerSum, event2) => {
|
|
17
|
-
return innerSum + calculateDistance(event1, event2)
|
|
18
|
-
}, 0)
|
|
19
|
-
)
|
|
20
|
-
}, 0)
|
|
21
|
-
|
|
22
|
-
// Number of pairs is n choose 2: n * (n - 1) / 2
|
|
23
|
-
const numberOfPairs = (n * (n - 1)) / 2
|
|
24
|
-
return totalDistance / numberOfPairs
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function calculateRadianAngleBetweenPoints(
|
|
28
|
-
pointerA: Point,
|
|
29
|
-
pointerB: Point,
|
|
30
|
-
): number {
|
|
31
|
-
// Calculate the differences in coordinates
|
|
32
|
-
const deltaX = pointerB.x - pointerA.x
|
|
33
|
-
const deltaY = pointerB.y - pointerA.y
|
|
34
|
-
|
|
35
|
-
// Calculate the angle in radians
|
|
36
|
-
const angleInRadians = Math.atan2(deltaY, deltaX)
|
|
37
|
-
|
|
38
|
-
// Return the angle in degrees
|
|
39
|
-
return angleInRadians
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function calculateDegreeAngleBetweenPoints(
|
|
43
|
-
pointerA: Point,
|
|
44
|
-
pointerB: Point,
|
|
45
|
-
): number {
|
|
46
|
-
// Calculate the angle in radians
|
|
47
|
-
const angleInRadians = calculateRadianAngleBetweenPoints(pointerA, pointerB)
|
|
48
|
-
|
|
49
|
-
// Convert the angle to degrees (optional)
|
|
50
|
-
const angleInDegrees = angleInRadians * (180 / Math.PI)
|
|
51
|
-
|
|
52
|
-
// Return the angle in degrees
|
|
53
|
-
return angleInDegrees
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function calculateCentroid(points: Point[]) {
|
|
57
|
-
const sum = points.reduce(
|
|
58
|
-
(acc, point) => {
|
|
59
|
-
acc.x += point.x
|
|
60
|
-
acc.y += point.y
|
|
61
|
-
|
|
62
|
-
return acc
|
|
63
|
-
},
|
|
64
|
-
{ x: 0, y: 0 },
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
const numPoints = points.length || 1
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
x: sum.x / numPoints,
|
|
71
|
-
y: sum.y / numPoints,
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function calculateAngleDelta(
|
|
76
|
-
initialPoints: Point[],
|
|
77
|
-
newPoints: Point[],
|
|
78
|
-
) {
|
|
79
|
-
if (initialPoints.length !== newPoints.length) {
|
|
80
|
-
throw new Error("Initial and new points arrays must have the same length.")
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const initialCentroid = calculateCentroid(initialPoints)
|
|
84
|
-
const newCentroid = calculateCentroid(newPoints)
|
|
85
|
-
|
|
86
|
-
let totalAngleDelta = 0
|
|
87
|
-
|
|
88
|
-
for (let i = 0; i < initialPoints.length; i++) {
|
|
89
|
-
const initialAngle = calculateRadianAngleBetweenPoints(
|
|
90
|
-
initialCentroid,
|
|
91
|
-
initialPoints[i]!,
|
|
92
|
-
)
|
|
93
|
-
const newAngle = calculateRadianAngleBetweenPoints(
|
|
94
|
-
newCentroid,
|
|
95
|
-
newPoints[i]!,
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
let angleDelta = newAngle - initialAngle
|
|
99
|
-
|
|
100
|
-
// Normalize the angle delta to be within [-PI, PI]
|
|
101
|
-
if (angleDelta > Math.PI) {
|
|
102
|
-
angleDelta -= 2 * Math.PI
|
|
103
|
-
} else if (angleDelta < -Math.PI) {
|
|
104
|
-
angleDelta += 2 * Math.PI
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
totalAngleDelta += angleDelta
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Return the average angle delta
|
|
111
|
-
const radianDelta = totalAngleDelta / initialPoints.length
|
|
112
|
-
const degreesDelta = radianDelta * (180 / Math.PI)
|
|
113
|
-
|
|
114
|
-
return { radianDelta, degreesDelta }
|
|
115
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest"
|
|
2
|
-
import { calculateCentroid } from "./geometry"
|
|
3
|
-
|
|
4
|
-
describe("calculateCentroid", () => {
|
|
5
|
-
it("calculates the correct center for a list of pointer events", () => {
|
|
6
|
-
const events = [
|
|
7
|
-
{ x: 0, y: 0 },
|
|
8
|
-
{ x: 2, y: 2 },
|
|
9
|
-
{ x: 4, y: 6 },
|
|
10
|
-
]
|
|
11
|
-
const center = calculateCentroid(events as any)
|
|
12
|
-
expect(center).toEqual({ x: 2, y: 2.6666666666666665 })
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
it("returns origin when no events are present", () => {
|
|
16
|
-
const events: any[] = []
|
|
17
|
-
const center = calculateCentroid(events)
|
|
18
|
-
expect(center).toEqual({ x: 0, y: 0 })
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it("handles single point correctly", () => {
|
|
22
|
-
const events = [{ x: 5, y: 5 }]
|
|
23
|
-
const center = calculateCentroid(events as any)
|
|
24
|
-
expect(center).toEqual({ x: 5, y: 5 })
|
|
25
|
-
})
|
|
26
|
-
})
|
package/src/core/utils/utils.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
NEVER,
|
|
3
|
-
Observable,
|
|
4
|
-
filter,
|
|
5
|
-
merge,
|
|
6
|
-
} from "rxjs"
|
|
7
|
-
import { calculateCentroid } from "./geometry"
|
|
8
|
-
|
|
9
|
-
export function isDefined<T>(
|
|
10
|
-
arg: T | null | undefined,
|
|
11
|
-
): arg is T extends null | undefined ? never : T {
|
|
12
|
-
return arg !== null && arg !== undefined
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const hasAtLeastOneItem = <T>(events: T[]): events is [T, ...T[]] =>
|
|
16
|
-
events.length > 0
|
|
17
|
-
|
|
18
|
-
export const filterNotEmpty = <T>(
|
|
19
|
-
stream: Observable<T[]>,
|
|
20
|
-
): Observable<[T, ...T[]]> => stream.pipe(filter(hasAtLeastOneItem))
|
|
21
|
-
|
|
22
|
-
export function isOutsidePosThreshold(
|
|
23
|
-
startEvent: PointerEvent,
|
|
24
|
-
endEvent: PointerEvent,
|
|
25
|
-
posThreshold: number,
|
|
26
|
-
) {
|
|
27
|
-
const start = calculateCentroid([startEvent])
|
|
28
|
-
const end = calculateCentroid([endEvent])
|
|
29
|
-
|
|
30
|
-
// Determines if the movement qualifies as a drag
|
|
31
|
-
return (
|
|
32
|
-
Math.abs(end.x - start.x) > posThreshold ||
|
|
33
|
-
Math.abs(end.y - start.y) > posThreshold
|
|
34
|
-
)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export const fromFailWith = (failWith?: { start$: Observable<unknown> }[]) =>
|
|
38
|
-
!failWith?.length
|
|
39
|
-
? NEVER
|
|
40
|
-
: merge(...(failWith?.map(({ start$ }) => start$) ?? []))
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Avoid division by zero
|
|
44
|
-
* Calculate velocity in pixels per second
|
|
45
|
-
*/
|
|
46
|
-
export const calculateVelocity = (
|
|
47
|
-
delay: number,
|
|
48
|
-
deltaX: number,
|
|
49
|
-
deltaY: number,
|
|
50
|
-
) => {
|
|
51
|
-
const velocityX = delay > 0 ? deltaX / delay : 0
|
|
52
|
-
const velocityY = delay > 0 ? deltaY / delay : 0
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
velocityX,
|
|
56
|
-
velocityY,
|
|
57
|
-
}
|
|
58
|
-
}
|