react-native-gesture-image-viewer 2.1.1 → 2.1.3

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.
Files changed (40) hide show
  1. package/README.md +1 -1
  2. package/lib/module/GestureTrigger.js.map +1 -1
  3. package/lib/module/GestureViewer.js +37 -30
  4. package/lib/module/GestureViewer.js.map +1 -1
  5. package/lib/module/GestureViewerManager.js.map +1 -1
  6. package/lib/module/GestureViewerRegistry.js.map +1 -1
  7. package/lib/module/WebPagingFixStyle.js.map +1 -1
  8. package/lib/module/useGestureViewer.js +172 -169
  9. package/lib/module/useGestureViewer.js.map +1 -1
  10. package/lib/module/useGestureViewerController.js.map +1 -1
  11. package/lib/module/useGestureViewerEvent.js.map +1 -1
  12. package/lib/module/useGestureViewerState.js.map +1 -1
  13. package/lib/module/utils/index.js +14 -16
  14. package/lib/module/utils/index.js.map +1 -1
  15. package/lib/typescript/src/GestureTrigger.d.ts +1 -1
  16. package/lib/typescript/src/GestureTrigger.d.ts.map +1 -1
  17. package/lib/typescript/src/GestureViewer.d.ts.map +1 -1
  18. package/lib/typescript/src/GestureViewerManager.d.ts.map +1 -1
  19. package/lib/typescript/src/GestureViewerRegistry.d.ts.map +1 -1
  20. package/lib/typescript/src/WebPagingFixStyle.d.ts.map +1 -1
  21. package/lib/typescript/src/types.d.ts.map +1 -1
  22. package/lib/typescript/src/useGestureViewer.d.ts +11 -10
  23. package/lib/typescript/src/useGestureViewer.d.ts.map +1 -1
  24. package/lib/typescript/src/useGestureViewerController.d.ts.map +1 -1
  25. package/lib/typescript/src/useGestureViewerEvent.d.ts.map +1 -1
  26. package/lib/typescript/src/useGestureViewerState.d.ts.map +1 -1
  27. package/lib/typescript/src/utils/index.d.ts +1 -1
  28. package/lib/typescript/src/utils/index.d.ts.map +1 -1
  29. package/package.json +86 -80
  30. package/src/GestureTrigger.tsx +9 -2
  31. package/src/GestureViewer.tsx +46 -21
  32. package/src/GestureViewerManager.ts +33 -6
  33. package/src/GestureViewerRegistry.ts +1 -0
  34. package/src/WebPagingFixStyle.tsx +1 -0
  35. package/src/types.ts +44 -32
  36. package/src/useGestureViewer.ts +272 -213
  37. package/src/useGestureViewerController.ts +1 -0
  38. package/src/useGestureViewerEvent.ts +5 -1
  39. package/src/useGestureViewerState.ts +1 -0
  40. package/src/utils/index.ts +31 -15
@@ -5,7 +5,7 @@ import {
5
5
  type NativeSyntheticEvent,
6
6
  useWindowDimensions,
7
7
  } from 'react-native';
8
- import { Gesture } from 'react-native-gesture-handler';
8
+ import { Gesture, type GestureType } from 'react-native-gesture-handler';
9
9
  import {
10
10
  Easing,
11
11
  interpolate,
@@ -16,6 +16,7 @@ import {
16
16
  withTiming,
17
17
  } from 'react-native-reanimated';
18
18
  import { scheduleOnRN } from 'react-native-worklets';
19
+
19
20
  import type GestureViewerManager from './GestureViewerManager';
20
21
  import { registry } from './GestureViewerRegistry';
21
22
  import type { GestureViewerProps, TriggerRect } from './types';
@@ -56,6 +57,8 @@ export const useGestureViewer = <ItemT, LC>({
56
57
  const width = customWidth || screenWidth;
57
58
  const height = customHeight || screenHeight;
58
59
 
60
+ const dismissGestureRef = useRef<GestureType>(undefined);
61
+
59
62
  const [isZoomed, setIsZoomed] = useState(false);
60
63
  const [isRotated, setIsRotated] = useState(false);
61
64
  const [isPinching, setIsPinching] = useState(false);
@@ -82,25 +85,29 @@ export const useGestureViewer = <ItemT, LC>({
82
85
  const triggerTranslateY = useSharedValue(0);
83
86
  const triggerOpacity = useSharedValue(1);
84
87
 
88
+ const lastFocalX = useSharedValue(0);
89
+ const lastFocalY = useSharedValue(0);
90
+
85
91
  const dataLength = data?.length || 0;
86
92
 
87
93
  const animationConfig = useMemo(
88
94
  () => ({
89
95
  duration: triggerAnimation?.duration ?? 300,
90
- easing: triggerAnimation?.easing ?? Easing.bezier(0.25, 0.1, 0.25, 1.0),
96
+ easing: triggerAnimation?.easing ?? Easing.bezier(0.25, 0.1, 0.25, 1),
91
97
  reduceMotion: triggerAnimation?.reduceMotion,
92
98
  }),
93
99
  [triggerAnimation?.duration, triggerAnimation?.easing, triggerAnimation?.reduceMotion],
94
100
  );
95
101
 
96
- const dismissOptions = useMemo(() => {
97
- return {
102
+ const dismissOptions = useMemo(
103
+ () => ({
98
104
  enabled: dismiss?.enabled ?? true,
99
- threshold: dismiss?.threshold ?? 80,
100
- resistance: dismiss?.resistance ?? 2,
101
105
  fadeBackdrop: dismiss?.fadeBackdrop ?? true,
102
- };
103
- }, [dismiss?.enabled, dismiss?.threshold, dismiss?.resistance, dismiss?.fadeBackdrop]);
106
+ resistance: dismiss?.resistance ?? 2,
107
+ threshold: dismiss?.threshold ?? 80,
108
+ }),
109
+ [dismiss?.enabled, dismiss?.threshold, dismiss?.resistance, dismiss?.fadeBackdrop],
110
+ );
104
111
 
105
112
  const adjustedInitialIndex = useMemo(() => {
106
113
  if (enableLoop && dataLength > 1) {
@@ -110,7 +117,10 @@ export const useGestureViewer = <ItemT, LC>({
110
117
  return initialIndex;
111
118
  }, [enableLoop, dataLength, initialIndex]);
112
119
 
113
- const constrainTranslation = useMemo(() => createBoundsConstraint({ width, height }), [width, height]);
120
+ const constrainTranslation = useMemo(
121
+ () => createBoundsConstraint({ height, width }),
122
+ [width, height],
123
+ );
114
124
 
115
125
  const scrollTo = useCallback(
116
126
  (index: number, animated: boolean) => {
@@ -236,7 +246,16 @@ export const useGestureViewer = <ItemT, LC>({
236
246
  return () => {
237
247
  runAfterInteractions?.cancel();
238
248
  };
239
- }, [adjustedInitialIndex, translateY, backdropOpacity, translateX, scale, startScale, rotation, scrollTo]);
249
+ }, [
250
+ adjustedInitialIndex,
251
+ translateY,
252
+ backdropOpacity,
253
+ translateX,
254
+ scale,
255
+ startScale,
256
+ rotation,
257
+ scrollTo,
258
+ ]);
240
259
 
241
260
  useEffect(() => {
242
261
  if (
@@ -265,7 +284,16 @@ export const useGestureViewer = <ItemT, LC>({
265
284
  }, intervalMs);
266
285
 
267
286
  return () => clearInterval(interval);
268
- }, [autoPlay, autoPlayInterval, manager, dataLength, currentIndex, enableLoop, isZoomed, isRotated]);
287
+ }, [
288
+ autoPlay,
289
+ autoPlayInterval,
290
+ manager,
291
+ dataLength,
292
+ currentIndex,
293
+ enableLoop,
294
+ isZoomed,
295
+ isRotated,
296
+ ]);
269
297
 
270
298
  useEffect(() => {
271
299
  onAnimationCompleteRef.current = triggerAnimation?.onAnimationComplete;
@@ -317,7 +345,7 @@ export const useGestureViewer = <ItemT, LC>({
317
345
 
318
346
  if (node && typeof node.measure === 'function') {
319
347
  node.measure((_x, _y, width, height, pageX, pageY) => {
320
- triggerRectRef.current = { x: pageX, y: pageY, width, height };
348
+ triggerRectRef.current = { height, width, x: pageX, y: pageY };
321
349
  triggerOpacity.value = 0;
322
350
  setShouldStartTriggerAnimation(true);
323
351
  registry.clearTriggerNode(id);
@@ -335,7 +363,10 @@ export const useGestureViewer = <ItemT, LC>({
335
363
  if (triggerRectRef.current) {
336
364
  const endX = triggerRectRef.current.x + triggerRectRef.current.width / 2 - width / 2;
337
365
  const endY = triggerRectRef.current.y + triggerRectRef.current.height / 2 - height / 2;
338
- const endScale = Math.min(triggerRectRef.current.width / width, triggerRectRef.current.height / height);
366
+ const endScale = Math.min(
367
+ triggerRectRef.current.width / width,
368
+ triggerRectRef.current.height / height,
369
+ );
339
370
 
340
371
  triggerScale.value = withTiming(endScale, animationConfig);
341
372
  triggerTranslateX.value = withTiming(endX, animationConfig);
@@ -369,7 +400,7 @@ export const useGestureViewer = <ItemT, LC>({
369
400
  return;
370
401
  }
371
402
 
372
- const contentOffset = event.nativeEvent.contentOffset;
403
+ const { contentOffset } = event.nativeEvent;
373
404
  const scrollIndex = Math.round(contentOffset.x / (width + itemSpacing));
374
405
 
375
406
  const isLoopHandled = manager?.handleMomentumScrollEnd(scrollIndex);
@@ -378,7 +409,11 @@ export const useGestureViewer = <ItemT, LC>({
378
409
  return;
379
410
  }
380
411
 
381
- const { realIndex, needsJump, jumpToIndex } = getLoopAdjustedIndex(scrollIndex, dataLength, enableLoop);
412
+ const { realIndex, needsJump, jumpToIndex } = getLoopAdjustedIndex(
413
+ scrollIndex,
414
+ dataLength,
415
+ enableLoop,
416
+ );
382
417
 
383
418
  if (needsJump && jumpToIndex !== undefined) {
384
419
  scrollTo(jumpToIndex, false);
@@ -428,6 +463,7 @@ export const useGestureViewer = <ItemT, LC>({
428
463
  .activeCursor('grabbing')
429
464
  .activeOffsetY([-10, 10])
430
465
  .failOffsetX([-10, 10])
466
+ .withRef(dismissGestureRef)
431
467
  .enabled(canDismiss)
432
468
  .onUpdate((event) => {
433
469
  translateY.value = event.translationY / dismissOptions.resistance;
@@ -439,212 +475,230 @@ export const useGestureViewer = <ItemT, LC>({
439
475
  }
440
476
 
441
477
  translateY.value = withSpring(0, {
442
- stiffness: 600,
443
478
  damping: 50,
479
+ energyThreshold: 6e-9,
444
480
  mass: 4,
445
481
  overshootClamping: false,
446
- energyThreshold: 6e-9,
482
+ stiffness: 600,
447
483
  });
448
484
  });
449
485
  }, [translateY, dismissOptions, handleDismiss, isZoomed]);
450
486
 
451
- const zoomPinchGesture = useMemo(() => {
452
- return Gesture.Pinch()
453
- .enabled(enablePinchZoom)
454
- .onTouchesDown((event) => {
455
- if (event.numberOfTouches === 2) {
456
- scheduleOnRN(setIsPinching, true);
457
- }
458
- })
459
- .onBegin(() => {
460
- startScale.value = scale.value;
461
- initialTranslateX.value = translateX.value;
462
- initialTranslateY.value = translateY.value;
463
- })
464
- .onUpdate((event) => {
465
- const newScale = startScale.value * event.scale;
487
+ const zoomPinchGesture = useMemo(
488
+ () =>
489
+ Gesture.Pinch()
490
+ .enabled(enablePinchZoom)
491
+ .onTouchesDown((event) => {
492
+ if (event.numberOfTouches === 2) {
493
+ scheduleOnRN(setIsPinching, true);
494
+ }
495
+ })
496
+ .onBegin((event) => {
497
+ startScale.value = scale.value;
498
+ initialTranslateX.value = translateX.value;
499
+ initialTranslateY.value = translateY.value;
500
+ lastFocalX.value = event.focalX;
501
+ lastFocalY.value = event.focalY;
502
+ })
503
+ .onUpdate((event) => {
504
+ const newScale = startScale.value * event.scale;
505
+
506
+ scale.value = newScale;
507
+
508
+ if (newScale <= 1) {
509
+ translateX.value = withTiming(0);
510
+ translateY.value = withTiming(0);
511
+ return;
512
+ }
513
+
514
+ const focalDeltaX = Math.abs(event.focalX - lastFocalX.value);
515
+ const focalDeltaY = Math.abs(event.focalY - lastFocalY.value);
516
+ const threshold = 50;
517
+
518
+ if (focalDeltaX < threshold && focalDeltaY < threshold) {
519
+ lastFocalX.value = event.focalX;
520
+ lastFocalY.value = event.focalY;
521
+ }
522
+
523
+ const deltaScale = newScale - startScale.value;
524
+ const centerX = lastFocalX.value - width / 2;
525
+ const centerY = lastFocalY.value - height / 2;
526
+
527
+ // NOTE 새로운 이동값 = 기존 이동값 - (중심점 거리 × 스케일 변화량) / 원래 스케일 (중심점이 화면 중심에서 멀수록, 확대 배율이 클수록 더 많이 이동)
528
+ const newTranslateX = initialTranslateX.value - (centerX * deltaScale) / startScale.value;
529
+ const newTranslateY = initialTranslateY.value - (centerY * deltaScale) / startScale.value;
530
+
531
+ const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } =
532
+ constrainTranslation({
533
+ scale: newScale,
534
+ translateX: newTranslateX,
535
+ translateY: newTranslateY,
536
+ });
466
537
 
467
- scale.value = newScale;
468
-
469
- if (event.numberOfPointers !== 2) {
470
- return;
471
- }
472
-
473
- if (newScale <= 1) {
474
- translateX.value = withTiming(0);
475
- translateY.value = withTiming(0);
476
- return;
477
- }
478
-
479
- const deltaScale = newScale - startScale.value;
480
- const centerX = event.focalX - width / 2;
481
- const centerY = event.focalY - height / 2;
482
-
483
- // NOTE 새로운 이동값 = 기존 이동값 - (중심점 거리 × 스케일 변화량) / 원래 스케일 (중심점이 화면 중심에서 멀수록, 확대 배율이 클수록 더 많이 이동)
484
- const newTranslateX = initialTranslateX.value - (centerX * deltaScale) / startScale.value;
485
- const newTranslateY = initialTranslateY.value - (centerY * deltaScale) / startScale.value;
486
-
487
- const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } = constrainTranslation({
488
- translateX: newTranslateX,
489
- translateY: newTranslateY,
490
- scale: newScale,
491
- });
492
-
493
- translateX.value = constrainedTranslateX;
494
- translateY.value = constrainedTranslateY;
495
- })
496
- .onEnd(() => {
497
- if (scale.value > maxZoomScale) {
498
- scale.value = withTiming(maxZoomScale, {
499
- duration: 300,
500
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
501
- });
502
-
503
- const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } = constrainTranslation({
504
- translateX: translateX.value,
505
- translateY: translateY.value,
506
- scale: maxZoomScale,
507
- });
538
+ translateX.value = constrainedTranslateX;
539
+ translateY.value = constrainedTranslateY;
540
+ })
541
+ .onEnd(() => {
542
+ if (scale.value > maxZoomScale) {
543
+ scale.value = withTiming(maxZoomScale, {
544
+ duration: 300,
545
+ easing: Easing.bezier(0.25, 0.1, 0.25, 1),
546
+ });
547
+
548
+ const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } =
549
+ constrainTranslation({
550
+ scale: maxZoomScale,
551
+ translateX: translateX.value,
552
+ translateY: translateY.value,
553
+ });
554
+
555
+ translateX.value = withTiming(constrainedTranslateX);
556
+ translateY.value = withTiming(constrainedTranslateY);
557
+
558
+ return;
559
+ }
560
+
561
+ if (scale.value < 1) {
562
+ scale.value = withTiming(1, {
563
+ duration: 300,
564
+ easing: Easing.bezier(0.25, 0.1, 0.25, 1),
565
+ });
566
+ translateX.value = withTiming(0);
567
+ translateY.value = withTiming(0);
568
+ initialTranslateX.value = withTiming(0);
569
+ initialTranslateY.value = withTiming(0);
570
+ return;
571
+ }
572
+
573
+ const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } =
574
+ constrainTranslation({
575
+ scale: scale.value,
576
+ translateX: translateX.value,
577
+ translateY: translateY.value,
578
+ });
508
579
 
509
580
  translateX.value = withTiming(constrainedTranslateX);
510
581
  translateY.value = withTiming(constrainedTranslateY);
582
+ })
583
+ .onTouchesUp(() => {
584
+ scheduleOnRN(setIsPinching, false);
585
+ })
586
+ .onFinalize(() => {
587
+ scheduleOnRN(setIsPinching, false);
588
+ }),
589
+ [
590
+ scale,
591
+ enablePinchZoom,
592
+ maxZoomScale,
593
+ translateX,
594
+ translateY,
595
+ startScale,
596
+ initialTranslateX,
597
+ initialTranslateY,
598
+ width,
599
+ height,
600
+ constrainTranslation,
601
+ lastFocalX,
602
+ lastFocalY,
603
+ ],
604
+ );
511
605
 
512
- return;
513
- }
606
+ const zoomPanGesture = useMemo(
607
+ () =>
608
+ Gesture.Pan()
609
+ .enabled(enablePanWhenZoomed && isZoomed)
610
+ .activeCursor('grabbing')
611
+ .averageTouches(true)
612
+ .onBegin(() => {
613
+ initialTranslateX.value = translateX.value;
614
+ initialTranslateY.value = translateY.value;
615
+ })
616
+ .onUpdate((event) => {
617
+ if (scale.value > 1) {
618
+ const newTranslateX = initialTranslateX.value + event.translationX;
619
+ const newTranslateY = initialTranslateY.value + event.translationY;
620
+
621
+ const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } =
622
+ constrainTranslation({
623
+ scale: scale.value,
624
+ translateX: newTranslateX,
625
+ translateY: newTranslateY,
626
+ });
627
+
628
+ translateX.value = constrainedTranslateX;
629
+ translateY.value = constrainedTranslateY;
630
+ }
631
+ }),
632
+ [
633
+ translateX,
634
+ translateY,
635
+ enablePanWhenZoomed,
636
+ isZoomed,
637
+ scale,
638
+ initialTranslateX,
639
+ initialTranslateY,
640
+ constrainTranslation,
641
+ ],
642
+ );
514
643
 
515
- if (scale.value < 1) {
516
- scale.value = withTiming(1, {
644
+ const doubleTapGesture = useMemo(
645
+ () =>
646
+ Gesture.Tap()
647
+ .enabled(enableDoubleTapZoom)
648
+ .numberOfTaps(2)
649
+ .onEnd((event) => {
650
+ const nextScale = scale.value > 1 ? 1 : maxZoomScale;
651
+
652
+ if (nextScale > 1) {
653
+ const centerX = event.x - width / 2;
654
+ const centerY = event.y - height / 2;
655
+
656
+ // NOTE 확대로 밀려난 거리만큼 반대로 이동해서 탭 지점을 제자리에 유지
657
+ translateX.value = withTiming(-centerX * (nextScale - 1), {
658
+ duration: 300,
659
+ easing: Easing.bezier(0.25, 0.1, 0.25, 1),
660
+ });
661
+ translateY.value = withTiming(-centerY * (nextScale - 1), {
662
+ duration: 300,
663
+ easing: Easing.bezier(0.25, 0.1, 0.25, 1),
664
+ });
665
+ } else {
666
+ translateX.value = withTiming(0, {
667
+ duration: 300,
668
+ easing: Easing.bezier(0.25, 0.1, 0.25, 1),
669
+ });
670
+ translateY.value = withTiming(0, {
671
+ duration: 300,
672
+ easing: Easing.bezier(0.25, 0.1, 0.25, 1),
673
+ });
674
+ }
675
+
676
+ scale.value = withTiming(nextScale, {
517
677
  duration: 300,
518
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
678
+ easing: Easing.bezier(0.25, 0.1, 0.25, 1),
519
679
  });
520
- translateX.value = withTiming(0);
521
- translateY.value = withTiming(0);
522
- initialTranslateX.value = withTiming(0);
523
- initialTranslateY.value = withTiming(0);
524
- return;
525
- }
526
-
527
- const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } = constrainTranslation({
528
- translateX: translateX.value,
529
- translateY: translateY.value,
530
- scale: scale.value,
531
- });
532
-
533
- translateX.value = withTiming(constrainedTranslateX);
534
- translateY.value = withTiming(constrainedTranslateY);
535
- })
536
- .onTouchesUp(() => {
537
- scheduleOnRN(setIsPinching, false);
538
- })
539
- .onFinalize(() => {
540
- scheduleOnRN(setIsPinching, false);
541
- });
542
- }, [
543
- scale,
544
- enablePinchZoom,
545
- maxZoomScale,
546
- translateX,
547
- translateY,
548
- startScale,
549
- initialTranslateX,
550
- initialTranslateY,
551
- width,
552
- height,
553
- constrainTranslation,
554
- ]);
555
-
556
- const zoomPanGesture = useMemo(() => {
557
- return Gesture.Pan()
558
- .enabled(enablePanWhenZoomed && isZoomed)
559
- .activeCursor('grabbing')
560
- .averageTouches(true)
561
- .onBegin(() => {
562
- initialTranslateX.value = translateX.value;
563
- initialTranslateY.value = translateY.value;
564
- })
565
- .onUpdate((event) => {
566
- if (scale.value > 1) {
567
- const newTranslateX = initialTranslateX.value + event.translationX;
568
- const newTranslateY = initialTranslateY.value + event.translationY;
569
-
570
- const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } = constrainTranslation({
571
- translateX: newTranslateX,
572
- translateY: newTranslateY,
573
- scale: scale.value,
574
- });
575
-
576
- translateX.value = constrainedTranslateX;
577
- translateY.value = constrainedTranslateY;
578
- }
579
- });
580
- }, [
581
- translateX,
582
- translateY,
583
- enablePanWhenZoomed,
584
- isZoomed,
585
- scale,
586
- initialTranslateX,
587
- initialTranslateY,
588
- constrainTranslation,
589
- ]);
590
-
591
- const doubleTapGesture = useMemo(() => {
592
- return Gesture.Tap()
593
- .enabled(enableDoubleTapZoom)
594
- .numberOfTaps(2)
595
- .onEnd((event) => {
596
- const nextScale = scale.value > 1 ? 1 : maxZoomScale;
597
-
598
- if (nextScale > 1) {
599
- const centerX = event.x - width / 2;
600
- const centerY = event.y - height / 2;
680
+ }),
681
+ [scale, enableDoubleTapZoom, maxZoomScale, translateX, translateY, width, height],
682
+ );
601
683
 
602
- // NOTE 확대로 밀려난 거리만큼 반대로 이동해서 탭 지점을 제자리에 유지
603
- translateX.value = withTiming(-centerX * (nextScale - 1), {
604
- duration: 300,
605
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
606
- });
607
- translateY.value = withTiming(-centerY * (nextScale - 1), {
608
- duration: 300,
609
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
610
- });
611
- } else {
612
- translateX.value = withTiming(0, {
613
- duration: 300,
614
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
615
- });
616
- translateY.value = withTiming(0, {
617
- duration: 300,
618
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
619
- });
620
- }
684
+ const zoomGesture = useMemo(
685
+ () => Gesture.Race(zoomPinchGesture, Gesture.Exclusive(zoomPanGesture, doubleTapGesture)),
686
+ [zoomPinchGesture, zoomPanGesture, doubleTapGesture],
687
+ );
621
688
 
622
- scale.value = withTiming(nextScale, {
623
- duration: 300,
624
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
625
- });
626
- });
627
- }, [scale, enableDoubleTapZoom, maxZoomScale, translateX, translateY, width, height]);
628
-
629
- const zoomGesture = useMemo(() => {
630
- return Gesture.Race(zoomPinchGesture, Gesture.Exclusive(zoomPanGesture, doubleTapGesture));
631
- }, [zoomPinchGesture, zoomPanGesture, doubleTapGesture]);
632
-
633
- const animatedStyle = useAnimatedStyle(() => {
634
- return {
635
- opacity: triggerOpacity.value,
636
- transform: [
637
- { translateX: triggerTranslateX.value },
638
- { translateY: triggerTranslateY.value },
639
- { scale: triggerScale.value },
640
-
641
- { translateY: translateY.value },
642
- { translateX: translateX.value },
643
- { scale: scale.value },
644
- { rotate: `${rotation.value}deg` },
645
- ],
646
- };
647
- });
689
+ const animatedStyle = useAnimatedStyle(() => ({
690
+ opacity: triggerOpacity.value,
691
+ transform: [
692
+ { translateX: triggerTranslateX.value },
693
+ { translateY: triggerTranslateY.value },
694
+ { scale: triggerScale.value },
695
+
696
+ { translateY: translateY.value },
697
+ { translateX: translateX.value },
698
+ { scale: scale.value },
699
+ { rotate: `${rotation.value}deg` },
700
+ ],
701
+ }));
648
702
 
649
703
  const backdropStyle = useAnimatedStyle(() => {
650
704
  const baseOpacity = triggerOpacity.value;
@@ -658,24 +712,29 @@ export const useGestureViewer = <ItemT, LC>({
658
712
  return { opacity: baseOpacity * dismissOpacity };
659
713
  }, [dismissOptions.fadeBackdrop]);
660
714
 
715
+ const nativeScrollGesture = useMemo(() => {
716
+ return Gesture.Native().requireExternalGestureToFail(dismissGestureRef);
717
+ }, []);
718
+
661
719
  const onScrollBeginDrag = useCallback(() => {
662
720
  manager?.handleScrollBeginDrag();
663
721
  }, [manager]);
664
722
 
665
723
  return {
724
+ animatedStyle,
725
+ backdropStyle,
666
726
  dataLength,
667
- listRef,
668
- isZoomed,
669
- isRotated,
670
- isPinching,
671
727
  dismissGesture,
672
- zoomGesture,
728
+ handleDismiss,
729
+ isPinching,
730
+ isRotated,
673
731
 
732
+ isZoomed,
733
+ listRef,
734
+ nativeScrollGesture,
674
735
  onMomentumScrollEnd,
675
- onScrollBeginDrag,
676
- handleDismiss,
677
736
 
678
- animatedStyle,
679
- backdropStyle,
737
+ onScrollBeginDrag,
738
+ zoomGesture,
680
739
  };
681
740
  };
@@ -1,4 +1,5 @@
1
1
  import { useEffect, useMemo, useRef } from 'react';
2
+
2
3
  import type GestureViewerManager from './GestureViewerManager';
3
4
  import { registry } from './GestureViewerRegistry';
4
5
  import type { GestureViewerController } from './types';
@@ -1,4 +1,5 @@
1
1
  import { useEffect } from 'react';
2
+
2
3
  import type GestureViewerManager from './GestureViewerManager';
3
4
  import { registry } from './GestureViewerRegistry';
4
5
  import type { GestureViewerEventCallback, GestureViewerEventType } from './types';
@@ -71,7 +72,10 @@ export function useGestureViewerEvent<T extends GestureViewerEventType>(
71
72
  callback?: GestureViewerEventCallback<T>,
72
73
  ) {
73
74
  const id = typeof idOrEventType === 'string' && callback ? idOrEventType : 'default';
74
- const eventType = typeof idOrEventType === 'string' && callback ? (eventTypeOrCallback as T) : (idOrEventType as T);
75
+ const eventType =
76
+ typeof idOrEventType === 'string' && callback
77
+ ? (eventTypeOrCallback as T)
78
+ : (idOrEventType as T);
75
79
  const finalCallback = callback || (eventTypeOrCallback as GestureViewerEventCallback<T>);
76
80
 
77
81
  useEffect(() => {
@@ -1,4 +1,5 @@
1
1
  import { useCallback, useRef, useSyncExternalStore } from 'react';
2
+
2
3
  import { registry } from './GestureViewerRegistry';
3
4
  import type { GestureViewerState } from './types';
4
5