react-native-gesture-image-viewer 1.6.5 → 1.7.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 +39 -18
- package/lib/module/GestureTrigger.js +83 -0
- package/lib/module/GestureTrigger.js.map +1 -0
- package/lib/module/GestureViewer.js +8 -2
- package/lib/module/GestureViewer.js.map +1 -1
- package/lib/module/GestureViewerRegistry.js +15 -0
- package/lib/module/GestureViewerRegistry.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/useGestureViewer.js +102 -12
- package/lib/module/useGestureViewer.js.map +1 -1
- package/lib/typescript/src/GestureTrigger.d.ts +55 -0
- package/lib/typescript/src/GestureTrigger.d.ts.map +1 -0
- package/lib/typescript/src/GestureViewer.d.ts.map +1 -1
- package/lib/typescript/src/GestureViewerRegistry.d.ts +5 -0
- package/lib/typescript/src/GestureViewerRegistry.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +56 -1
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/useGestureViewer.d.ts +9 -8
- package/lib/typescript/src/useGestureViewer.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/GestureTrigger.tsx +90 -0
- package/src/GestureViewer.tsx +8 -2
- package/src/GestureViewerRegistry.ts +20 -0
- package/src/index.tsx +1 -0
- package/src/types.ts +56 -3
- package/src/useGestureViewer.ts +141 -12
package/src/useGestureViewer.ts
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
} from 'react-native-reanimated';
|
|
19
19
|
import type GestureViewerManager from './GestureViewerManager';
|
|
20
20
|
import { registry } from './GestureViewerRegistry';
|
|
21
|
-
import type { GestureViewerProps } from './types';
|
|
21
|
+
import type { GestureViewerProps, TriggerRect } from './types';
|
|
22
22
|
import { createBoundsConstraint, createScrollAction, getLoopAdjustedIndex } from './utils';
|
|
23
23
|
|
|
24
24
|
type UseGestureViewerProps<T = any> = Omit<
|
|
@@ -34,8 +34,6 @@ export const useGestureViewer = <T = any>({
|
|
|
34
34
|
width: customWidth,
|
|
35
35
|
dismissThreshold = 80,
|
|
36
36
|
resistance = 2,
|
|
37
|
-
// swipeThreshold = 0.5,
|
|
38
|
-
// velocityThreshold = 200,
|
|
39
37
|
animateBackdrop = true,
|
|
40
38
|
enableDismissGesture = true,
|
|
41
39
|
enableSwipeGesture = true,
|
|
@@ -47,6 +45,8 @@ export const useGestureViewer = <T = any>({
|
|
|
47
45
|
itemSpacing = 0,
|
|
48
46
|
useSnap = false,
|
|
49
47
|
id = 'default',
|
|
48
|
+
onDismissStart,
|
|
49
|
+
triggerAnimation,
|
|
50
50
|
}: UseGestureViewerProps<T>) => {
|
|
51
51
|
const { width: screenWidth, height: screenHeight } = useWindowDimensions();
|
|
52
52
|
const width = useSnap ? customWidth || screenWidth : screenWidth;
|
|
@@ -54,11 +54,14 @@ export const useGestureViewer = <T = any>({
|
|
|
54
54
|
const [isZoomed, setIsZoomed] = useState(false);
|
|
55
55
|
const [isRotated, setIsRotated] = useState(false);
|
|
56
56
|
const [manager, setManager] = useState<GestureViewerManager | null>(null);
|
|
57
|
+
const [shouldStartTriggerAnimation, setShouldStartTriggerAnimation] = useState(false);
|
|
57
58
|
|
|
58
59
|
const listRef = useRef<any>(null);
|
|
59
60
|
const unsubscribeRef = useRef<(() => void) | null>(null);
|
|
60
61
|
const onIndexChangeRef = useRef<((index: number) => void) | null>(null);
|
|
61
62
|
const lastEmittedIndexRef = useRef<number>(null);
|
|
63
|
+
const triggerRectRef = useRef<TriggerRect | null>(null);
|
|
64
|
+
const onAnimationCompleteRef = useRef(triggerAnimation?.onAnimationComplete);
|
|
62
65
|
|
|
63
66
|
const initialTranslateY = useSharedValue(0);
|
|
64
67
|
const initialTranslateX = useSharedValue(0);
|
|
@@ -70,8 +73,22 @@ export const useGestureViewer = <T = any>({
|
|
|
70
73
|
const backdropOpacity = useSharedValue(1);
|
|
71
74
|
const rotation = useSharedValue(0);
|
|
72
75
|
|
|
76
|
+
const triggerScale = useSharedValue(1);
|
|
77
|
+
const triggerTranslateX = useSharedValue(0);
|
|
78
|
+
const triggerTranslateY = useSharedValue(0);
|
|
79
|
+
const triggerOpacity = useSharedValue(1);
|
|
80
|
+
|
|
73
81
|
const dataLength = data?.length || 0;
|
|
74
82
|
|
|
83
|
+
const animationConfig = useMemo(
|
|
84
|
+
() => ({
|
|
85
|
+
duration: triggerAnimation?.duration ?? 300,
|
|
86
|
+
easing: triggerAnimation?.easing ?? Easing.bezier(0.25, 0.1, 0.25, 1.0),
|
|
87
|
+
reduceMotion: triggerAnimation?.reduceMotion,
|
|
88
|
+
}),
|
|
89
|
+
[triggerAnimation?.duration, triggerAnimation?.easing, triggerAnimation?.reduceMotion],
|
|
90
|
+
);
|
|
91
|
+
|
|
75
92
|
const adjustedInitialIndex = useMemo(() => {
|
|
76
93
|
if (enableLoop && data.length > 1) {
|
|
77
94
|
return initialIndex + 1;
|
|
@@ -85,6 +102,10 @@ export const useGestureViewer = <T = any>({
|
|
|
85
102
|
[width, screenHeight],
|
|
86
103
|
);
|
|
87
104
|
|
|
105
|
+
const onAnimationComplete = useCallback(() => {
|
|
106
|
+
onAnimationCompleteRef.current?.();
|
|
107
|
+
}, []);
|
|
108
|
+
|
|
88
109
|
const scrollTo = useCallback(
|
|
89
110
|
(index: number, animated: boolean) => {
|
|
90
111
|
const scrollAction = createScrollAction(listRef.current, width + itemSpacing);
|
|
@@ -140,6 +161,68 @@ export const useGestureViewer = <T = any>({
|
|
|
140
161
|
};
|
|
141
162
|
}, []);
|
|
142
163
|
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
onAnimationCompleteRef.current = triggerAnimation?.onAnimationComplete;
|
|
166
|
+
}, [triggerAnimation?.onAnimationComplete]);
|
|
167
|
+
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
if (shouldStartTriggerAnimation && triggerRectRef.current) {
|
|
170
|
+
const startX = triggerRectRef.current.x + triggerRectRef.current.width / 2 - screenWidth / 2;
|
|
171
|
+
const startY = triggerRectRef.current.y + triggerRectRef.current.height / 2 - screenHeight / 2;
|
|
172
|
+
const initialScaleFromTrigger = Math.min(
|
|
173
|
+
triggerRectRef.current.width / screenWidth,
|
|
174
|
+
triggerRectRef.current.height / screenHeight,
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
triggerScale.value = initialScaleFromTrigger;
|
|
178
|
+
triggerTranslateX.value = startX;
|
|
179
|
+
triggerTranslateY.value = startY;
|
|
180
|
+
triggerOpacity.value = 0;
|
|
181
|
+
|
|
182
|
+
triggerScale.value = withTiming(1, animationConfig, (finished) => {
|
|
183
|
+
if (finished) {
|
|
184
|
+
runOnJS(onAnimationComplete)();
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
triggerTranslateX.value = withTiming(0, animationConfig);
|
|
188
|
+
triggerTranslateY.value = withTiming(0, animationConfig);
|
|
189
|
+
triggerOpacity.value = withTiming(1, {
|
|
190
|
+
duration: animationConfig.duration / 2,
|
|
191
|
+
easing: animationConfig.easing,
|
|
192
|
+
reduceMotion: animationConfig.reduceMotion,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
setShouldStartTriggerAnimation(false);
|
|
196
|
+
}
|
|
197
|
+
}, [
|
|
198
|
+
shouldStartTriggerAnimation,
|
|
199
|
+
animationConfig,
|
|
200
|
+
screenWidth,
|
|
201
|
+
screenHeight,
|
|
202
|
+
triggerOpacity,
|
|
203
|
+
triggerScale,
|
|
204
|
+
triggerTranslateX,
|
|
205
|
+
triggerTranslateY,
|
|
206
|
+
onAnimationComplete,
|
|
207
|
+
]);
|
|
208
|
+
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
const node = registry.getTriggerNode(id);
|
|
211
|
+
|
|
212
|
+
if (node && typeof node.measure === 'function') {
|
|
213
|
+
node.measure((_x, _y, width, height, pageX, pageY) => {
|
|
214
|
+
triggerRectRef.current = { x: pageX, y: pageY, width, height };
|
|
215
|
+
triggerOpacity.value = 0;
|
|
216
|
+
setShouldStartTriggerAnimation(true);
|
|
217
|
+
registry.clearTriggerNode(id);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return () => {
|
|
222
|
+
triggerRectRef.current = null;
|
|
223
|
+
};
|
|
224
|
+
}, [id, triggerOpacity]);
|
|
225
|
+
|
|
143
226
|
useEffect(() => {
|
|
144
227
|
const handleManagerChange = (manager: GestureViewerManager | null) => {
|
|
145
228
|
unsubscribeRef.current?.();
|
|
@@ -227,6 +310,43 @@ export const useGestureViewer = <T = any>({
|
|
|
227
310
|
};
|
|
228
311
|
}, [adjustedInitialIndex, translateY, backdropOpacity, translateX, scale, startScale, rotation, scrollTo]);
|
|
229
312
|
|
|
313
|
+
const handleDismiss = useCallback(() => {
|
|
314
|
+
onDismissStart?.();
|
|
315
|
+
|
|
316
|
+
if (triggerRectRef.current) {
|
|
317
|
+
const endX = triggerRectRef.current.x + triggerRectRef.current.width / 2 - screenWidth / 2;
|
|
318
|
+
const endY = triggerRectRef.current.y + triggerRectRef.current.height / 2 - screenHeight / 2;
|
|
319
|
+
const endScale = Math.min(
|
|
320
|
+
triggerRectRef.current.width / screenWidth,
|
|
321
|
+
triggerRectRef.current.height / screenHeight,
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
triggerScale.value = withTiming(endScale, animationConfig);
|
|
325
|
+
triggerTranslateX.value = withTiming(endX, animationConfig);
|
|
326
|
+
triggerTranslateY.value = withTiming(endY, animationConfig);
|
|
327
|
+
triggerOpacity.value = withTiming(0, animationConfig, (finished) => {
|
|
328
|
+
if (finished && onDismiss) {
|
|
329
|
+
runOnJS(onDismiss)();
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (onDismiss) {
|
|
336
|
+
runOnJS(onDismiss)();
|
|
337
|
+
}
|
|
338
|
+
}, [
|
|
339
|
+
animationConfig,
|
|
340
|
+
onDismiss,
|
|
341
|
+
onDismissStart,
|
|
342
|
+
screenWidth,
|
|
343
|
+
screenHeight,
|
|
344
|
+
triggerTranslateX,
|
|
345
|
+
triggerScale,
|
|
346
|
+
triggerTranslateY,
|
|
347
|
+
triggerOpacity,
|
|
348
|
+
]);
|
|
349
|
+
|
|
230
350
|
const onMomentumScrollEnd = useCallback(
|
|
231
351
|
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
232
352
|
if (!enableSwipeGesture) {
|
|
@@ -248,7 +368,7 @@ export const useGestureViewer = <T = any>({
|
|
|
248
368
|
scrollTo(jumpToIndex, false);
|
|
249
369
|
}
|
|
250
370
|
|
|
251
|
-
const currentIndex = manager?.getState()
|
|
371
|
+
const currentIndex = manager?.getState().currentIndex;
|
|
252
372
|
|
|
253
373
|
if (realIndex !== currentIndex && realIndex >= 0 && realIndex < dataLength) {
|
|
254
374
|
if (manager) {
|
|
@@ -284,19 +404,21 @@ export const useGestureViewer = <T = any>({
|
|
|
284
404
|
);
|
|
285
405
|
|
|
286
406
|
const dismissGesture = useMemo(() => {
|
|
407
|
+
const canDismiss = !isZoomed && enableDismissGesture;
|
|
408
|
+
|
|
287
409
|
return Gesture.Pan()
|
|
288
410
|
.minDistance(10)
|
|
289
411
|
.averageTouches(true)
|
|
290
412
|
.activeCursor('grabbing')
|
|
291
413
|
.activeOffsetY([-10, 10])
|
|
292
414
|
.failOffsetX([-10, 10])
|
|
293
|
-
.enabled(
|
|
415
|
+
.enabled(canDismiss)
|
|
294
416
|
.onUpdate((event) => {
|
|
295
417
|
translateY.value = event.translationY / resistance;
|
|
296
418
|
})
|
|
297
419
|
.onEnd((event) => {
|
|
298
|
-
if (event.translationY > dismissThreshold &&
|
|
299
|
-
runOnJS(
|
|
420
|
+
if (canDismiss && event.translationY > dismissThreshold && handleDismiss) {
|
|
421
|
+
runOnJS(handleDismiss)();
|
|
300
422
|
return;
|
|
301
423
|
}
|
|
302
424
|
|
|
@@ -305,7 +427,7 @@ export const useGestureViewer = <T = any>({
|
|
|
305
427
|
stiffness: 150,
|
|
306
428
|
});
|
|
307
429
|
});
|
|
308
|
-
}, [translateY, dismissThreshold, enableDismissGesture,
|
|
430
|
+
}, [translateY, dismissThreshold, enableDismissGesture, handleDismiss, resistance, isZoomed]);
|
|
309
431
|
|
|
310
432
|
const zoomPinchGesture = useMemo(() => {
|
|
311
433
|
return Gesture.Pinch()
|
|
@@ -476,7 +598,12 @@ export const useGestureViewer = <T = any>({
|
|
|
476
598
|
|
|
477
599
|
const animatedStyle = useAnimatedStyle(() => {
|
|
478
600
|
return {
|
|
601
|
+
opacity: triggerOpacity.value,
|
|
479
602
|
transform: [
|
|
603
|
+
{ translateX: triggerTranslateX.value },
|
|
604
|
+
{ translateY: triggerTranslateY.value },
|
|
605
|
+
{ scale: triggerScale.value },
|
|
606
|
+
|
|
480
607
|
{ translateY: translateY.value },
|
|
481
608
|
{ translateX: translateX.value },
|
|
482
609
|
{ scale: scale.value },
|
|
@@ -486,13 +613,15 @@ export const useGestureViewer = <T = any>({
|
|
|
486
613
|
});
|
|
487
614
|
|
|
488
615
|
const backdropStyle = useAnimatedStyle(() => {
|
|
616
|
+
const baseOpacity = triggerOpacity.value;
|
|
617
|
+
|
|
489
618
|
if (!animateBackdrop || scale.value !== 1) {
|
|
490
|
-
return { opacity:
|
|
619
|
+
return { opacity: baseOpacity };
|
|
491
620
|
}
|
|
492
621
|
|
|
493
|
-
const
|
|
622
|
+
const dismissOpacity = interpolate(translateY.value, [0, 200], [1, 0], 'clamp');
|
|
494
623
|
|
|
495
|
-
return { opacity };
|
|
624
|
+
return { opacity: dismissOpacity * baseOpacity };
|
|
496
625
|
}, [animateBackdrop]);
|
|
497
626
|
|
|
498
627
|
const onScrollBeginDrag = useCallback(() => {
|
|
@@ -501,7 +630,6 @@ export const useGestureViewer = <T = any>({
|
|
|
501
630
|
|
|
502
631
|
return {
|
|
503
632
|
dataLength,
|
|
504
|
-
translateY,
|
|
505
633
|
listRef,
|
|
506
634
|
isZoomed,
|
|
507
635
|
isRotated,
|
|
@@ -510,6 +638,7 @@ export const useGestureViewer = <T = any>({
|
|
|
510
638
|
|
|
511
639
|
onMomentumScrollEnd,
|
|
512
640
|
onScrollBeginDrag,
|
|
641
|
+
handleDismiss,
|
|
513
642
|
|
|
514
643
|
animatedStyle,
|
|
515
644
|
backdropStyle,
|