react-native-gesture-image-viewer 1.6.6 → 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.
@@ -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() ?? 0;
371
+ const currentIndex = manager?.getState().currentIndex;
252
372
 
253
373
  if (realIndex !== currentIndex && realIndex >= 0 && realIndex < dataLength) {
254
374
  if (manager) {
@@ -297,8 +417,8 @@ export const useGestureViewer = <T = any>({
297
417
  translateY.value = event.translationY / resistance;
298
418
  })
299
419
  .onEnd((event) => {
300
- if (canDismiss && event.translationY > dismissThreshold && onDismiss) {
301
- runOnJS(onDismiss)();
420
+ if (canDismiss && event.translationY > dismissThreshold && handleDismiss) {
421
+ runOnJS(handleDismiss)();
302
422
  return;
303
423
  }
304
424
 
@@ -307,7 +427,7 @@ export const useGestureViewer = <T = any>({
307
427
  stiffness: 150,
308
428
  });
309
429
  });
310
- }, [translateY, dismissThreshold, enableDismissGesture, onDismiss, resistance, isZoomed]);
430
+ }, [translateY, dismissThreshold, enableDismissGesture, handleDismiss, resistance, isZoomed]);
311
431
 
312
432
  const zoomPinchGesture = useMemo(() => {
313
433
  return Gesture.Pinch()
@@ -478,7 +598,12 @@ export const useGestureViewer = <T = any>({
478
598
 
479
599
  const animatedStyle = useAnimatedStyle(() => {
480
600
  return {
601
+ opacity: triggerOpacity.value,
481
602
  transform: [
603
+ { translateX: triggerTranslateX.value },
604
+ { translateY: triggerTranslateY.value },
605
+ { scale: triggerScale.value },
606
+
482
607
  { translateY: translateY.value },
483
608
  { translateX: translateX.value },
484
609
  { scale: scale.value },
@@ -488,13 +613,15 @@ export const useGestureViewer = <T = any>({
488
613
  });
489
614
 
490
615
  const backdropStyle = useAnimatedStyle(() => {
616
+ const baseOpacity = triggerOpacity.value;
617
+
491
618
  if (!animateBackdrop || scale.value !== 1) {
492
- return { opacity: 1 };
619
+ return { opacity: baseOpacity };
493
620
  }
494
621
 
495
- const opacity = interpolate(translateY.value, [0, 200], [1, 0], 'clamp');
622
+ const dismissOpacity = interpolate(translateY.value, [0, 200], [1, 0], 'clamp');
496
623
 
497
- return { opacity };
624
+ return { opacity: dismissOpacity * baseOpacity };
498
625
  }, [animateBackdrop]);
499
626
 
500
627
  const onScrollBeginDrag = useCallback(() => {
@@ -503,7 +630,6 @@ export const useGestureViewer = <T = any>({
503
630
 
504
631
  return {
505
632
  dataLength,
506
- translateY,
507
633
  listRef,
508
634
  isZoomed,
509
635
  isRotated,
@@ -512,6 +638,7 @@ export const useGestureViewer = <T = any>({
512
638
 
513
639
  onMomentumScrollEnd,
514
640
  onScrollBeginDrag,
641
+ handleDismiss,
515
642
 
516
643
  animatedStyle,
517
644
  backdropStyle,