react-native-gesture-image-viewer 2.1.2 → 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 +168 -173
  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 +267 -220
  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);
@@ -90,20 +93,21 @@ export const useGestureViewer = <ItemT, LC>({
90
93
  const animationConfig = useMemo(
91
94
  () => ({
92
95
  duration: triggerAnimation?.duration ?? 300,
93
- 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),
94
97
  reduceMotion: triggerAnimation?.reduceMotion,
95
98
  }),
96
99
  [triggerAnimation?.duration, triggerAnimation?.easing, triggerAnimation?.reduceMotion],
97
100
  );
98
101
 
99
- const dismissOptions = useMemo(() => {
100
- return {
102
+ const dismissOptions = useMemo(
103
+ () => ({
101
104
  enabled: dismiss?.enabled ?? true,
102
- threshold: dismiss?.threshold ?? 80,
103
- resistance: dismiss?.resistance ?? 2,
104
105
  fadeBackdrop: dismiss?.fadeBackdrop ?? true,
105
- };
106
- }, [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
+ );
107
111
 
108
112
  const adjustedInitialIndex = useMemo(() => {
109
113
  if (enableLoop && dataLength > 1) {
@@ -113,7 +117,10 @@ export const useGestureViewer = <ItemT, LC>({
113
117
  return initialIndex;
114
118
  }, [enableLoop, dataLength, initialIndex]);
115
119
 
116
- const constrainTranslation = useMemo(() => createBoundsConstraint({ width, height }), [width, height]);
120
+ const constrainTranslation = useMemo(
121
+ () => createBoundsConstraint({ height, width }),
122
+ [width, height],
123
+ );
117
124
 
118
125
  const scrollTo = useCallback(
119
126
  (index: number, animated: boolean) => {
@@ -239,7 +246,16 @@ export const useGestureViewer = <ItemT, LC>({
239
246
  return () => {
240
247
  runAfterInteractions?.cancel();
241
248
  };
242
- }, [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
+ ]);
243
259
 
244
260
  useEffect(() => {
245
261
  if (
@@ -268,7 +284,16 @@ export const useGestureViewer = <ItemT, LC>({
268
284
  }, intervalMs);
269
285
 
270
286
  return () => clearInterval(interval);
271
- }, [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
+ ]);
272
297
 
273
298
  useEffect(() => {
274
299
  onAnimationCompleteRef.current = triggerAnimation?.onAnimationComplete;
@@ -320,7 +345,7 @@ export const useGestureViewer = <ItemT, LC>({
320
345
 
321
346
  if (node && typeof node.measure === 'function') {
322
347
  node.measure((_x, _y, width, height, pageX, pageY) => {
323
- triggerRectRef.current = { x: pageX, y: pageY, width, height };
348
+ triggerRectRef.current = { height, width, x: pageX, y: pageY };
324
349
  triggerOpacity.value = 0;
325
350
  setShouldStartTriggerAnimation(true);
326
351
  registry.clearTriggerNode(id);
@@ -338,7 +363,10 @@ export const useGestureViewer = <ItemT, LC>({
338
363
  if (triggerRectRef.current) {
339
364
  const endX = triggerRectRef.current.x + triggerRectRef.current.width / 2 - width / 2;
340
365
  const endY = triggerRectRef.current.y + triggerRectRef.current.height / 2 - height / 2;
341
- 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
+ );
342
370
 
343
371
  triggerScale.value = withTiming(endScale, animationConfig);
344
372
  triggerTranslateX.value = withTiming(endX, animationConfig);
@@ -372,7 +400,7 @@ export const useGestureViewer = <ItemT, LC>({
372
400
  return;
373
401
  }
374
402
 
375
- const contentOffset = event.nativeEvent.contentOffset;
403
+ const { contentOffset } = event.nativeEvent;
376
404
  const scrollIndex = Math.round(contentOffset.x / (width + itemSpacing));
377
405
 
378
406
  const isLoopHandled = manager?.handleMomentumScrollEnd(scrollIndex);
@@ -381,7 +409,11 @@ export const useGestureViewer = <ItemT, LC>({
381
409
  return;
382
410
  }
383
411
 
384
- const { realIndex, needsJump, jumpToIndex } = getLoopAdjustedIndex(scrollIndex, dataLength, enableLoop);
412
+ const { realIndex, needsJump, jumpToIndex } = getLoopAdjustedIndex(
413
+ scrollIndex,
414
+ dataLength,
415
+ enableLoop,
416
+ );
385
417
 
386
418
  if (needsJump && jumpToIndex !== undefined) {
387
419
  scrollTo(jumpToIndex, false);
@@ -431,6 +463,7 @@ export const useGestureViewer = <ItemT, LC>({
431
463
  .activeCursor('grabbing')
432
464
  .activeOffsetY([-10, 10])
433
465
  .failOffsetX([-10, 10])
466
+ .withRef(dismissGestureRef)
434
467
  .enabled(canDismiss)
435
468
  .onUpdate((event) => {
436
469
  translateY.value = event.translationY / dismissOptions.resistance;
@@ -442,221 +475,230 @@ export const useGestureViewer = <ItemT, LC>({
442
475
  }
443
476
 
444
477
  translateY.value = withSpring(0, {
445
- stiffness: 600,
446
478
  damping: 50,
479
+ energyThreshold: 6e-9,
447
480
  mass: 4,
448
481
  overshootClamping: false,
449
- energyThreshold: 6e-9,
482
+ stiffness: 600,
450
483
  });
451
484
  });
452
485
  }, [translateY, dismissOptions, handleDismiss, isZoomed]);
453
486
 
454
- const zoomPinchGesture = useMemo(() => {
455
- return Gesture.Pinch()
456
- .enabled(enablePinchZoom)
457
- .onTouchesDown((event) => {
458
- if (event.numberOfTouches === 2) {
459
- scheduleOnRN(setIsPinching, true);
460
- }
461
- })
462
- .onBegin((event) => {
463
- startScale.value = scale.value;
464
- initialTranslateX.value = translateX.value;
465
- initialTranslateY.value = translateY.value;
466
- lastFocalX.value = event.focalX;
467
- lastFocalY.value = event.focalY;
468
- })
469
- .onUpdate((event) => {
470
- const newScale = startScale.value * event.scale;
471
-
472
- scale.value = newScale;
473
-
474
- if (newScale <= 1) {
475
- translateX.value = withTiming(0);
476
- translateY.value = withTiming(0);
477
- return;
478
- }
479
-
480
- const focalDeltaX = Math.abs(event.focalX - lastFocalX.value);
481
- const focalDeltaY = Math.abs(event.focalY - lastFocalY.value);
482
- const threshold = 50;
483
-
484
- if (focalDeltaX < threshold && focalDeltaY < threshold) {
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;
485
500
  lastFocalX.value = event.focalX;
486
501
  lastFocalY.value = event.focalY;
487
- }
488
-
489
- const deltaScale = newScale - startScale.value;
490
- const centerX = lastFocalX.value - width / 2;
491
- const centerY = lastFocalY.value - height / 2;
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
+ });
492
537
 
493
- // NOTE 새로운 이동값 = 기존 이동값 - (중심점 거리 × 스케일 변화량) / 원래 스케일 (중심점이 화면 중심에서 멀수록, 확대 배율이 클수록 더 많이 이동)
494
- const newTranslateX = initialTranslateX.value - (centerX * deltaScale) / startScale.value;
495
- const newTranslateY = initialTranslateY.value - (centerY * deltaScale) / startScale.value;
496
-
497
- const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } = constrainTranslation({
498
- translateX: newTranslateX,
499
- translateY: newTranslateY,
500
- scale: newScale,
501
- });
502
-
503
- translateX.value = constrainedTranslateX;
504
- translateY.value = constrainedTranslateY;
505
- })
506
- .onEnd(() => {
507
- if (scale.value > maxZoomScale) {
508
- scale.value = withTiming(maxZoomScale, {
509
- duration: 300,
510
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
511
- });
512
-
513
- const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } = constrainTranslation({
514
- translateX: translateX.value,
515
- translateY: translateY.value,
516
- scale: maxZoomScale,
517
- });
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
+ });
518
579
 
519
580
  translateX.value = withTiming(constrainedTranslateX);
520
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
+ );
521
605
 
522
- return;
523
- }
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
+ );
524
643
 
525
- if (scale.value < 1) {
526
- 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, {
527
677
  duration: 300,
528
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
678
+ easing: Easing.bezier(0.25, 0.1, 0.25, 1),
529
679
  });
530
- translateX.value = withTiming(0);
531
- translateY.value = withTiming(0);
532
- initialTranslateX.value = withTiming(0);
533
- initialTranslateY.value = withTiming(0);
534
- return;
535
- }
536
-
537
- const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } = constrainTranslation({
538
- translateX: translateX.value,
539
- translateY: translateY.value,
540
- scale: scale.value,
541
- });
542
-
543
- translateX.value = withTiming(constrainedTranslateX);
544
- translateY.value = withTiming(constrainedTranslateY);
545
- })
546
- .onTouchesUp(() => {
547
- scheduleOnRN(setIsPinching, false);
548
- })
549
- .onFinalize(() => {
550
- scheduleOnRN(setIsPinching, false);
551
- });
552
- }, [
553
- scale,
554
- enablePinchZoom,
555
- maxZoomScale,
556
- translateX,
557
- translateY,
558
- startScale,
559
- initialTranslateX,
560
- initialTranslateY,
561
- width,
562
- height,
563
- constrainTranslation,
564
- lastFocalX,
565
- lastFocalY,
566
- ]);
567
-
568
- const zoomPanGesture = useMemo(() => {
569
- return Gesture.Pan()
570
- .enabled(enablePanWhenZoomed && isZoomed)
571
- .activeCursor('grabbing')
572
- .averageTouches(true)
573
- .onBegin(() => {
574
- initialTranslateX.value = translateX.value;
575
- initialTranslateY.value = translateY.value;
576
- })
577
- .onUpdate((event) => {
578
- if (scale.value > 1) {
579
- const newTranslateX = initialTranslateX.value + event.translationX;
580
- const newTranslateY = initialTranslateY.value + event.translationY;
581
-
582
- const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } = constrainTranslation({
583
- translateX: newTranslateX,
584
- translateY: newTranslateY,
585
- scale: scale.value,
586
- });
587
-
588
- translateX.value = constrainedTranslateX;
589
- translateY.value = constrainedTranslateY;
590
- }
591
- });
592
- }, [
593
- translateX,
594
- translateY,
595
- enablePanWhenZoomed,
596
- isZoomed,
597
- scale,
598
- initialTranslateX,
599
- initialTranslateY,
600
- constrainTranslation,
601
- ]);
602
-
603
- const doubleTapGesture = useMemo(() => {
604
- return Gesture.Tap()
605
- .enabled(enableDoubleTapZoom)
606
- .numberOfTaps(2)
607
- .onEnd((event) => {
608
- const nextScale = scale.value > 1 ? 1 : maxZoomScale;
609
-
610
- if (nextScale > 1) {
611
- const centerX = event.x - width / 2;
612
- const centerY = event.y - height / 2;
680
+ }),
681
+ [scale, enableDoubleTapZoom, maxZoomScale, translateX, translateY, width, height],
682
+ );
613
683
 
614
- // NOTE 확대로 밀려난 거리만큼 반대로 이동해서 탭 지점을 제자리에 유지
615
- translateX.value = withTiming(-centerX * (nextScale - 1), {
616
- duration: 300,
617
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
618
- });
619
- translateY.value = withTiming(-centerY * (nextScale - 1), {
620
- duration: 300,
621
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
622
- });
623
- } else {
624
- translateX.value = withTiming(0, {
625
- duration: 300,
626
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
627
- });
628
- translateY.value = withTiming(0, {
629
- duration: 300,
630
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
631
- });
632
- }
684
+ const zoomGesture = useMemo(
685
+ () => Gesture.Race(zoomPinchGesture, Gesture.Exclusive(zoomPanGesture, doubleTapGesture)),
686
+ [zoomPinchGesture, zoomPanGesture, doubleTapGesture],
687
+ );
633
688
 
634
- scale.value = withTiming(nextScale, {
635
- duration: 300,
636
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
637
- });
638
- });
639
- }, [scale, enableDoubleTapZoom, maxZoomScale, translateX, translateY, width, height]);
640
-
641
- const zoomGesture = useMemo(() => {
642
- return Gesture.Race(zoomPinchGesture, Gesture.Exclusive(zoomPanGesture, doubleTapGesture));
643
- }, [zoomPinchGesture, zoomPanGesture, doubleTapGesture]);
644
-
645
- const animatedStyle = useAnimatedStyle(() => {
646
- return {
647
- opacity: triggerOpacity.value,
648
- transform: [
649
- { translateX: triggerTranslateX.value },
650
- { translateY: triggerTranslateY.value },
651
- { scale: triggerScale.value },
652
-
653
- { translateY: translateY.value },
654
- { translateX: translateX.value },
655
- { scale: scale.value },
656
- { rotate: `${rotation.value}deg` },
657
- ],
658
- };
659
- });
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
+ }));
660
702
 
661
703
  const backdropStyle = useAnimatedStyle(() => {
662
704
  const baseOpacity = triggerOpacity.value;
@@ -670,24 +712,29 @@ export const useGestureViewer = <ItemT, LC>({
670
712
  return { opacity: baseOpacity * dismissOpacity };
671
713
  }, [dismissOptions.fadeBackdrop]);
672
714
 
715
+ const nativeScrollGesture = useMemo(() => {
716
+ return Gesture.Native().requireExternalGestureToFail(dismissGestureRef);
717
+ }, []);
718
+
673
719
  const onScrollBeginDrag = useCallback(() => {
674
720
  manager?.handleScrollBeginDrag();
675
721
  }, [manager]);
676
722
 
677
723
  return {
724
+ animatedStyle,
725
+ backdropStyle,
678
726
  dataLength,
679
- listRef,
680
- isZoomed,
681
- isRotated,
682
- isPinching,
683
727
  dismissGesture,
684
- zoomGesture,
728
+ handleDismiss,
729
+ isPinching,
730
+ isRotated,
685
731
 
732
+ isZoomed,
733
+ listRef,
734
+ nativeScrollGesture,
686
735
  onMomentumScrollEnd,
687
- onScrollBeginDrag,
688
- handleDismiss,
689
736
 
690
- animatedStyle,
691
- backdropStyle,
737
+ onScrollBeginDrag,
738
+ zoomGesture,
692
739
  };
693
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