@cleartrip/ct-design-base-dragger 2.0.0-TEST.0 → 4.0.0-SNAPSHOT-test.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 +73 -0
- package/dist/BaseDragger.d.ts +0 -1
- package/dist/BaseDragger.d.ts.map +1 -1
- package/dist/BaseDraggerHeader.d.ts.map +1 -1
- package/dist/context/BaseDraggerContext.d.ts.map +1 -1
- package/dist/ct-design-base-dragger.browser.cjs.js +1 -1
- package/dist/ct-design-base-dragger.browser.cjs.js.map +1 -1
- package/dist/ct-design-base-dragger.browser.esm.js +1 -1
- package/dist/ct-design-base-dragger.browser.esm.js.map +1 -1
- package/dist/ct-design-base-dragger.cjs.js +143 -136
- package/dist/ct-design-base-dragger.cjs.js.map +1 -1
- package/dist/ct-design-base-dragger.esm.js +143 -136
- package/dist/ct-design-base-dragger.esm.js.map +1 -1
- package/dist/ct-design-base-dragger.umd.js +143 -166
- package/dist/ct-design-base-dragger.umd.js.map +1 -1
- package/dist/useDrag.d.ts.map +1 -1
- package/package.json +16 -8
- package/src/BaseDragger.tsx +243 -0
- package/src/BaseDraggerHeader.tsx +21 -0
- package/src/context/BaseDraggerContext.tsx +39 -0
- package/src/index.ts +4 -0
- package/src/type.ts +28 -0
- package/src/useDrag.ts +176 -0
package/package.json
CHANGED
|
@@ -1,25 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cleartrip/ct-design-base-dragger",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0-SNAPSHOT-test.0",
|
|
4
4
|
"description": "A Base Component which can be wrapper to make a component draggable",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
|
-
"main": "dist/ct-design-base-dragger.cjs.js",
|
|
6
|
+
"main": "./dist/ct-design-base-dragger.cjs.js",
|
|
7
7
|
"jsnext:main": "dist/ct-design-base-dragger.esm.js",
|
|
8
8
|
"module": "dist/ct-design-base-dragger.esm.js",
|
|
9
9
|
"sideEffects": false,
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/ct-design-base-dragger.d.ts",
|
|
13
|
+
"import": "./dist/ct-design-base-dragger.esm.js",
|
|
14
|
+
"default": "./dist/ct-design-base-dragger.cjs.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
10
17
|
"browser": {
|
|
11
18
|
"./dist/ct-design-base-dragger.esm.js": "./dist/ct-design-base-dragger.browser.esm.js",
|
|
12
19
|
"./dist/ct-design-base-dragger.cjs.js": "./dist/ct-design-base-dragger.browser.cjs.js"
|
|
13
20
|
},
|
|
14
21
|
"files": [
|
|
15
|
-
"dist"
|
|
22
|
+
"dist",
|
|
23
|
+
"src"
|
|
16
24
|
],
|
|
17
25
|
"dependencies": {
|
|
18
|
-
"@cleartrip/ct-design-container": "4.0.0-
|
|
19
|
-
|
|
20
|
-
"devDependencies": {
|
|
21
|
-
"@cleartrip/ct-design-theme": "4.0.0-TEST.0"
|
|
26
|
+
"@cleartrip/ct-design-container": "4.0.0-SNAPSHOT-test.0",
|
|
27
|
+
"@cleartrip/ct-design-theme": "4.0.0-SNAPSHOT-test.0"
|
|
22
28
|
},
|
|
29
|
+
"devDependencies": {},
|
|
23
30
|
"peerDependencies": {
|
|
24
31
|
"react": ">=16.8.0",
|
|
25
32
|
"react-dom": ">=16.8.0"
|
|
@@ -33,6 +40,7 @@
|
|
|
33
40
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
34
41
|
"watch-package": "rollup -c -w",
|
|
35
42
|
"build-package": "rollup -c",
|
|
36
|
-
"build-package:clean": "rm -rf dist && rollup -c"
|
|
43
|
+
"build-package:clean": "rm -rf dist && rollup -c",
|
|
44
|
+
"publish-package:local": "yalc publish"
|
|
37
45
|
}
|
|
38
46
|
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/* eslint-disable local-rules/disallow-style-config-inline */
|
|
2
|
+
import { useRef, useState, memo, useLayoutEffect, useCallback } from 'react';
|
|
3
|
+
|
|
4
|
+
import { Container } from '@cleartrip/ct-design-container';
|
|
5
|
+
|
|
6
|
+
import { useDrag } from './useDrag';
|
|
7
|
+
import BaseDraggerContext from './context/BaseDraggerContext';
|
|
8
|
+
|
|
9
|
+
import { IBaseDraggerProps, IDraggerDefaultState } from './type';
|
|
10
|
+
|
|
11
|
+
export const DRAGGABLE_CONTANTS = {
|
|
12
|
+
THRESHOLD_DIVIDER: 2,
|
|
13
|
+
ORIGIN: 1500,
|
|
14
|
+
VELOCITY: 0.4,
|
|
15
|
+
OFFSET: 250,
|
|
16
|
+
VELOCITY_DELTA_X: 10,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function BaseDragger({
|
|
20
|
+
isVisible,
|
|
21
|
+
children,
|
|
22
|
+
minDraggableHeight = 300,
|
|
23
|
+
minDraggableHeaderHeight,
|
|
24
|
+
maxDraggableHeight,
|
|
25
|
+
isClosable = false,
|
|
26
|
+
isDraggable = true,
|
|
27
|
+
onDragging: onDraggingCb,
|
|
28
|
+
onDragEnd: onDragEndCb,
|
|
29
|
+
isWrappedWithTransitionGroup = false,
|
|
30
|
+
}: IBaseDraggerProps) {
|
|
31
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
32
|
+
const [defaultStyles, setDefaultStyles] = useState<IDraggerDefaultState | null>(null);
|
|
33
|
+
const draggableHeaderRef = useRef<(HTMLDivElement | null)[]>([]);
|
|
34
|
+
|
|
35
|
+
const debouncedStart = useRef(false);
|
|
36
|
+
|
|
37
|
+
const calculateDefaultStyles = useCallback(() => {
|
|
38
|
+
const windowInnerHeight = window.innerHeight;
|
|
39
|
+
|
|
40
|
+
const { height = 0 } = ref.current?.getBoundingClientRect() ?? {};
|
|
41
|
+
const manualTop = windowInnerHeight - height;
|
|
42
|
+
|
|
43
|
+
const maximumPossibleHeight = Math.min(
|
|
44
|
+
maxDraggableHeight ?? ref.current?.scrollHeight ?? 0,
|
|
45
|
+
(92 * windowInnerHeight) / 100,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const draggerHeaderHeight = draggableHeaderRef.current?.reduce(
|
|
49
|
+
(h, element) => h + (element?.getBoundingClientRect().height ?? 0),
|
|
50
|
+
0,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const minimumHeightPossible =
|
|
54
|
+
minDraggableHeaderHeight ?? (draggableHeaderRef.current ? (draggerHeaderHeight ?? 0) : 0);
|
|
55
|
+
const currentHeight = maxDraggableHeight ?? height;
|
|
56
|
+
|
|
57
|
+
const maximumDeltaThatCanBeAddedToTop = currentHeight - maximumPossibleHeight;
|
|
58
|
+
const maximumDeltaCanBeAddedToBottom = currentHeight - minimumHeightPossible;
|
|
59
|
+
|
|
60
|
+
const deltaPoints = [
|
|
61
|
+
maximumDeltaThatCanBeAddedToTop + DRAGGABLE_CONTANTS.ORIGIN,
|
|
62
|
+
DRAGGABLE_CONTANTS.ORIGIN,
|
|
63
|
+
maximumDeltaCanBeAddedToBottom + DRAGGABLE_CONTANTS.ORIGIN,
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
if (isClosable) {
|
|
67
|
+
deltaPoints.push(manualTop + DRAGGABLE_CONTANTS.ORIGIN);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
setDefaultStyles({
|
|
71
|
+
maximumDeltaCanBeAddedToBottom,
|
|
72
|
+
maximumDeltaThatCanBeAddedToTop,
|
|
73
|
+
deltaPoints,
|
|
74
|
+
initialHeight: height ?? 0,
|
|
75
|
+
});
|
|
76
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
77
|
+
}, [isVisible, maxDraggableHeight, isDraggable, isClosable]);
|
|
78
|
+
|
|
79
|
+
useLayoutEffect(() => {
|
|
80
|
+
const getStylesInFrames = () => {
|
|
81
|
+
if (ref.current) {
|
|
82
|
+
const initialTransition = ref.current.style.transition;
|
|
83
|
+
|
|
84
|
+
requestAnimationFrame(() => {
|
|
85
|
+
if (ref.current) {
|
|
86
|
+
const originalTransition = ref.current.style.transition;
|
|
87
|
+
const originalTransform = ref.current.style.transform;
|
|
88
|
+
|
|
89
|
+
ref.current.style.transform = '';
|
|
90
|
+
ref.current.style.opacity = '0';
|
|
91
|
+
ref.current.style.transition = '';
|
|
92
|
+
|
|
93
|
+
requestAnimationFrame(() => {
|
|
94
|
+
calculateDefaultStyles();
|
|
95
|
+
|
|
96
|
+
if (ref.current) {
|
|
97
|
+
ref.current.style.transform = originalTransform;
|
|
98
|
+
ref.current.style.transition = initialTransition;
|
|
99
|
+
|
|
100
|
+
requestAnimationFrame(() => {
|
|
101
|
+
if (ref.current) {
|
|
102
|
+
ref.current.style.opacity = '1';
|
|
103
|
+
ref.current.style.transition = originalTransition;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
if (isDraggable && !isWrappedWithTransitionGroup) {
|
|
114
|
+
getStylesInFrames();
|
|
115
|
+
}
|
|
116
|
+
}, [isDraggable, isWrappedWithTransitionGroup, calculateDefaultStyles]);
|
|
117
|
+
|
|
118
|
+
const onReactTransitionGroupEnd = useCallback(() => {
|
|
119
|
+
calculateDefaultStyles();
|
|
120
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
121
|
+
}, []);
|
|
122
|
+
|
|
123
|
+
const { onDragStart, onDragging, onDragEnd, onCustomPositionChange } = useDrag({
|
|
124
|
+
onDragging(event) {
|
|
125
|
+
if (event && defaultStyles && !debouncedStart.current) {
|
|
126
|
+
const { deltaY: dy, initialDxDy, dir } = event;
|
|
127
|
+
|
|
128
|
+
if (dir === 'Left' || dir === 'Right') {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const deltaY =
|
|
133
|
+
dy + initialDxDy[1] > 0
|
|
134
|
+
? Math.min(defaultStyles.maximumDeltaCanBeAddedToBottom, dy + initialDxDy[1])
|
|
135
|
+
: -Math.min(Math.abs(defaultStyles.maximumDeltaThatCanBeAddedToTop), Math.abs(dy + initialDxDy[1]));
|
|
136
|
+
|
|
137
|
+
if (ref.current) {
|
|
138
|
+
ref.current.style.transform = `translateY(${deltaY}px) translateZ(0px)`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (onDraggingCb) {
|
|
142
|
+
onDraggingCb({ viewableHeight: defaultStyles.initialHeight + -deltaY });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
onDragEnd(event) {
|
|
147
|
+
if (defaultStyles) {
|
|
148
|
+
const { deltaY: dy, initialDxDy, dir, deltaX, velocity, absX } = event;
|
|
149
|
+
const { deltaPoints } = defaultStyles;
|
|
150
|
+
|
|
151
|
+
const takeVelocityInConsideration = absX <= DRAGGABLE_CONTANTS.VELOCITY_DELTA_X;
|
|
152
|
+
|
|
153
|
+
if (initialDxDy) {
|
|
154
|
+
const deltaOrigin = dy + initialDxDy[1] + DRAGGABLE_CONTANTS.ORIGIN;
|
|
155
|
+
let destination = deltaPoints[0];
|
|
156
|
+
|
|
157
|
+
if (takeVelocityInConsideration && velocity >= DRAGGABLE_CONTANTS.VELOCITY) {
|
|
158
|
+
const jumpBy = Math.floor(velocity / DRAGGABLE_CONTANTS.VELOCITY);
|
|
159
|
+
let currentDeltaIndex = -1;
|
|
160
|
+
|
|
161
|
+
deltaPoints.forEach((x, index) => {
|
|
162
|
+
const change = dy + initialDxDy[1] + DRAGGABLE_CONTANTS.ORIGIN;
|
|
163
|
+
|
|
164
|
+
if (change >= x && dir === 'Down') {
|
|
165
|
+
currentDeltaIndex = index;
|
|
166
|
+
} else if (x >= change && dir === 'Up') {
|
|
167
|
+
currentDeltaIndex = index;
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
if (dir === 'Up') {
|
|
172
|
+
destination = deltaPoints[Math.max(0, currentDeltaIndex - jumpBy)];
|
|
173
|
+
} else {
|
|
174
|
+
destination = deltaPoints[Math.min(deltaPoints.length - 1, currentDeltaIndex + jumpBy)];
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
for (let i = 0; i < deltaPoints.length - 1; ++i) {
|
|
178
|
+
const l = deltaPoints[i],
|
|
179
|
+
r = deltaPoints[i + 1];
|
|
180
|
+
|
|
181
|
+
const upperLimit = l + (r - l) / DRAGGABLE_CONTANTS.THRESHOLD_DIVIDER;
|
|
182
|
+
|
|
183
|
+
if (deltaOrigin >= l && deltaOrigin <= r) {
|
|
184
|
+
if (dir === 'Up') {
|
|
185
|
+
destination = r;
|
|
186
|
+
|
|
187
|
+
if (deltaOrigin <= upperLimit) {
|
|
188
|
+
destination = l;
|
|
189
|
+
}
|
|
190
|
+
} else {
|
|
191
|
+
destination = l;
|
|
192
|
+
|
|
193
|
+
if (upperLimit <= deltaOrigin) {
|
|
194
|
+
destination = r;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (deltaOrigin >= deltaPoints[deltaPoints.length - 1]) {
|
|
201
|
+
destination = deltaPoints[deltaPoints.length - 1];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
onCustomPositionChange([deltaX, destination - DRAGGABLE_CONTANTS.ORIGIN]);
|
|
206
|
+
|
|
207
|
+
if (ref.current) {
|
|
208
|
+
ref.current.style.transform = `translateY(${destination - DRAGGABLE_CONTANTS.ORIGIN}px) translateZ(0px)`;
|
|
209
|
+
}
|
|
210
|
+
if (onDragEndCb) {
|
|
211
|
+
onDragEndCb({ viewableHeight: defaultStyles.initialHeight + -(destination - DRAGGABLE_CONTANTS.ORIGIN) });
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
debouncedStart.current = true;
|
|
215
|
+
setTimeout(() => {
|
|
216
|
+
debouncedStart.current = false;
|
|
217
|
+
}, 200);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<Container
|
|
225
|
+
styleConfig={{
|
|
226
|
+
root: [
|
|
227
|
+
{
|
|
228
|
+
height: minDraggableHeight,
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
}}
|
|
232
|
+
>
|
|
233
|
+
<BaseDraggerContext
|
|
234
|
+
{...(defaultStyles ? { onDragStart, onDragEnd, onDragging } : {})}
|
|
235
|
+
draggerHeaderRef={draggableHeaderRef}
|
|
236
|
+
>
|
|
237
|
+
{children({ ref, onReactTransitionGroupEnd })}
|
|
238
|
+
</BaseDraggerContext>
|
|
239
|
+
</Container>
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export default memo(BaseDragger);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { PropsWithChildren, memo, useCallback } from 'react';
|
|
2
|
+
import { useDraggableHeader } from './context/BaseDraggerContext';
|
|
3
|
+
|
|
4
|
+
export function BaseDraggerHeader({ children }: PropsWithChildren) {
|
|
5
|
+
const { draggerHeaderRef, onDragEnd, onDragStart, onDragging } = useDraggableHeader();
|
|
6
|
+
|
|
7
|
+
const refCallback = useCallback((node: HTMLDivElement) => {
|
|
8
|
+
if (node) {
|
|
9
|
+
draggerHeaderRef.current?.push(node);
|
|
10
|
+
}
|
|
11
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
12
|
+
}, []);
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div onTouchStart={onDragStart} onTouchMove={onDragging} onTouchEnd={onDragEnd} ref={refCallback}>
|
|
16
|
+
{children}
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default memo(BaseDraggerHeader);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { PropsWithChildren, createContext, memo, useContext, useMemo, RefObject } from 'react';
|
|
2
|
+
import { DragEvent } from '../useDrag';
|
|
3
|
+
|
|
4
|
+
const BaseDraggerProvider = createContext<IBaseDraggerConsumerValue>({} as IBaseDraggerConsumerValue);
|
|
5
|
+
|
|
6
|
+
export interface IBaseDraggerConsumerValue {
|
|
7
|
+
onDragStart?: DragEvent;
|
|
8
|
+
onDragging?: DragEvent;
|
|
9
|
+
onDragEnd?: DragEvent;
|
|
10
|
+
draggerHeaderRef: RefObject<(HTMLDivElement | null)[]>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface IBaseDraggerContextProps extends PropsWithChildren, IBaseDraggerConsumerValue {}
|
|
14
|
+
|
|
15
|
+
function BaseDraggerContext({
|
|
16
|
+
children,
|
|
17
|
+
onDragEnd,
|
|
18
|
+
onDragStart,
|
|
19
|
+
onDragging,
|
|
20
|
+
draggerHeaderRef,
|
|
21
|
+
}: IBaseDraggerContextProps) {
|
|
22
|
+
const value = useMemo(() => {
|
|
23
|
+
return {
|
|
24
|
+
onDragStart,
|
|
25
|
+
onDragging,
|
|
26
|
+
onDragEnd,
|
|
27
|
+
draggerHeaderRef,
|
|
28
|
+
};
|
|
29
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
30
|
+
}, [onDragEnd, onDragging, onDragStart]);
|
|
31
|
+
|
|
32
|
+
return <BaseDraggerProvider.Provider value={value}>{children}</BaseDraggerProvider.Provider>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function useDraggableHeader() {
|
|
36
|
+
return useContext(BaseDraggerProvider);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default memo(BaseDraggerContext);
|
package/src/index.ts
ADDED
package/src/type.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { MutableRefObject, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface IBaseDraggerProps {
|
|
4
|
+
children: (props: {
|
|
5
|
+
ref: MutableRefObject<HTMLDivElement | null>;
|
|
6
|
+
onReactTransitionGroupEnd?: () => void;
|
|
7
|
+
}) => ReactNode;
|
|
8
|
+
isVisible?: boolean;
|
|
9
|
+
isDraggable?: boolean;
|
|
10
|
+
isClosable?: boolean;
|
|
11
|
+
maxDraggableHeight?: number;
|
|
12
|
+
minDraggableHeight?: number;
|
|
13
|
+
minDraggableHeaderHeight?: number;
|
|
14
|
+
onDragging?: (event: IDragCallbackArgs) => void;
|
|
15
|
+
onDragEnd?: (event: IDragCallbackArgs) => void;
|
|
16
|
+
isWrappedWithTransitionGroup?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface IDragCallbackArgs {
|
|
20
|
+
viewableHeight: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface IDraggerDefaultState {
|
|
24
|
+
maximumDeltaThatCanBeAddedToTop: number;
|
|
25
|
+
maximumDeltaCanBeAddedToBottom: number;
|
|
26
|
+
deltaPoints: number[];
|
|
27
|
+
initialHeight: number;
|
|
28
|
+
}
|
package/src/useDrag.ts
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
2
|
+
import { TouchEvent, useCallback, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
export type HandledEvents = TouchEvent<HTMLDivElement>;
|
|
5
|
+
|
|
6
|
+
const DRAGGER_INITIAL_STATE = {
|
|
7
|
+
initial: [0, 0],
|
|
8
|
+
start: 0,
|
|
9
|
+
xy: [0, 0],
|
|
10
|
+
dragging: false,
|
|
11
|
+
initialDxDy: [0, 0],
|
|
12
|
+
lastDxDy: [0, 0],
|
|
13
|
+
eventData: {} as EventData,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const LEFT = 'Left';
|
|
17
|
+
export const RIGHT = 'Right';
|
|
18
|
+
export const UP = 'Up';
|
|
19
|
+
export const DOWN = 'Down';
|
|
20
|
+
|
|
21
|
+
export type SwipeDirections = typeof LEFT | typeof RIGHT | typeof UP | typeof DOWN;
|
|
22
|
+
|
|
23
|
+
export type EventData = {
|
|
24
|
+
absX: number;
|
|
25
|
+
absY: number;
|
|
26
|
+
deltaX: number;
|
|
27
|
+
deltaY: number;
|
|
28
|
+
dir: SwipeDirections;
|
|
29
|
+
event: HandledEvents;
|
|
30
|
+
initial: number[];
|
|
31
|
+
velocity: number;
|
|
32
|
+
clientY: number;
|
|
33
|
+
initialDxDy: number[];
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function getDirection(absX: number, absY: number, deltaX: number, deltaY: number): SwipeDirections {
|
|
37
|
+
if (absX > absY) {
|
|
38
|
+
if (deltaX > 0) {
|
|
39
|
+
return RIGHT;
|
|
40
|
+
}
|
|
41
|
+
return LEFT;
|
|
42
|
+
} else if (deltaY > 0) {
|
|
43
|
+
return DOWN;
|
|
44
|
+
}
|
|
45
|
+
return UP;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type DragEvent = (e: HandledEvents) => void;
|
|
49
|
+
|
|
50
|
+
export interface IDragProps {
|
|
51
|
+
onDragStart?: (e: EventData) => void;
|
|
52
|
+
onDragging?: (e: EventData) => void;
|
|
53
|
+
onDragEnd?: (e: EventData) => void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function useDrag(events: IDragProps = {}) {
|
|
57
|
+
const [state, setState] = useState(DRAGGER_INITIAL_STATE);
|
|
58
|
+
|
|
59
|
+
const onDragStart = useCallback((event: HandledEvents) => {
|
|
60
|
+
const isTouch = 'touches' in event || 'changedTouches' in event;
|
|
61
|
+
|
|
62
|
+
if (isTouch && (event.touches ?? event.changedTouches).length > 1) return;
|
|
63
|
+
|
|
64
|
+
setState((previous) => {
|
|
65
|
+
const { clientX, clientY } = isTouch ? (event.touches[0] ?? event.changedTouches[0]) : event;
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
...previous,
|
|
69
|
+
...DRAGGER_INITIAL_STATE,
|
|
70
|
+
initialDxDy: previous.initialDxDy,
|
|
71
|
+
lastDxDy: previous.initialDxDy,
|
|
72
|
+
xy: [clientX, clientY],
|
|
73
|
+
start: event.timeStamp || 0,
|
|
74
|
+
dragging: true,
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
}, []);
|
|
78
|
+
|
|
79
|
+
const onDragging = useCallback(
|
|
80
|
+
(event: HandledEvents) => {
|
|
81
|
+
setState((state) => {
|
|
82
|
+
const isTouch = 'touches' in event || 'changedTouches' in event;
|
|
83
|
+
|
|
84
|
+
if (isTouch && (event.touches ?? event.changedTouches).length > 1) {
|
|
85
|
+
return state;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
event.stopPropagation();
|
|
89
|
+
event.preventDefault();
|
|
90
|
+
|
|
91
|
+
const { clientX, clientY } = isTouch ? (event.touches[0] ?? event.changedTouches[0]) : event;
|
|
92
|
+
|
|
93
|
+
const deltaX = clientX - state.xy[0];
|
|
94
|
+
const deltaY = clientY - state.xy[1];
|
|
95
|
+
const absX = Math.abs(deltaX);
|
|
96
|
+
const absY = Math.abs(deltaY);
|
|
97
|
+
const time = (event.timeStamp || 0) - state.start;
|
|
98
|
+
const velocity = Math.sqrt(absX * absX + absY * absY) / (time || 1);
|
|
99
|
+
const dir = getDirection(absX, absY, deltaX, deltaY);
|
|
100
|
+
|
|
101
|
+
const lastDxDy = [state.initialDxDy[0] + deltaX, state.initialDxDy[1] + deltaY];
|
|
102
|
+
|
|
103
|
+
const eventData = {
|
|
104
|
+
absX,
|
|
105
|
+
absY,
|
|
106
|
+
deltaX,
|
|
107
|
+
deltaY,
|
|
108
|
+
clientY,
|
|
109
|
+
dir,
|
|
110
|
+
event,
|
|
111
|
+
initial: state.initial,
|
|
112
|
+
velocity,
|
|
113
|
+
initialDxDy: state.initialDxDy,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
if (events.onDragging) events.onDragging(eventData);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
...state,
|
|
120
|
+
eventData,
|
|
121
|
+
lastDxDy: lastDxDy,
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
},
|
|
125
|
+
[events],
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const onCustomPositionChange = useCallback((positions: number[]) => {
|
|
129
|
+
setState((state) => {
|
|
130
|
+
return {
|
|
131
|
+
...state,
|
|
132
|
+
initialDxDy: positions,
|
|
133
|
+
lastDxDy: positions,
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
}, []);
|
|
137
|
+
|
|
138
|
+
const onDragEnd = useCallback(
|
|
139
|
+
(event: HandledEvents) => {
|
|
140
|
+
const eventData = state.eventData;
|
|
141
|
+
const distanceX = event.changedTouches[0].clientX - state.xy[0];
|
|
142
|
+
const distanceY = event.changedTouches[0].clientY - state.xy[1];
|
|
143
|
+
|
|
144
|
+
let swipeDirection;
|
|
145
|
+
|
|
146
|
+
if (Math.abs(distanceX) > Math.abs(distanceY)) {
|
|
147
|
+
// Horizontal swipe
|
|
148
|
+
swipeDirection = distanceX > 0 ? RIGHT : LEFT;
|
|
149
|
+
} else {
|
|
150
|
+
// Vertical swipe
|
|
151
|
+
swipeDirection = distanceY > 0 ? DOWN : UP;
|
|
152
|
+
}
|
|
153
|
+
if (events.onDragEnd) {
|
|
154
|
+
// @ts-ignore
|
|
155
|
+
events.onDragEnd({ absX: Math.abs(distanceX), absY: Math.abs(distanceY), dir: swipeDirection, ...eventData });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
setState((state) => {
|
|
159
|
+
return {
|
|
160
|
+
...state,
|
|
161
|
+
initialDxDy: state.lastDxDy,
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
},
|
|
165
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
166
|
+
[events],
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
state,
|
|
171
|
+
onDragStart,
|
|
172
|
+
onDragEnd,
|
|
173
|
+
onDragging,
|
|
174
|
+
onCustomPositionChange,
|
|
175
|
+
};
|
|
176
|
+
}
|