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

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 (45) hide show
  1. package/lib/module/GestureViewer.js +50 -40
  2. package/lib/module/GestureViewer.js.map +1 -1
  3. package/lib/module/GestureViewerManager.js +26 -6
  4. package/lib/module/GestureViewerManager.js.map +1 -1
  5. package/lib/module/useGestureViewer.js +67 -86
  6. package/lib/module/useGestureViewer.js.map +1 -1
  7. package/lib/module/useGestureViewerPaging.js +68 -0
  8. package/lib/module/useGestureViewerPaging.js.map +1 -0
  9. package/lib/module/useGestureViewerPaging.types.js +4 -0
  10. package/lib/module/useGestureViewerPaging.types.js.map +1 -0
  11. package/lib/module/useGestureViewerPaging.web.js +241 -0
  12. package/lib/module/useGestureViewerPaging.web.js.map +1 -0
  13. package/lib/module/utils/index.js +9 -0
  14. package/lib/module/utils/index.js.map +1 -1
  15. package/lib/module/utils/tapZoom.js +34 -0
  16. package/lib/module/utils/tapZoom.js.map +1 -0
  17. package/lib/module/utils/webScroll.js +70 -0
  18. package/lib/module/utils/webScroll.js.map +1 -0
  19. package/lib/typescript/src/GestureViewer.d.ts.map +1 -1
  20. package/lib/typescript/src/GestureViewerManager.d.ts +5 -0
  21. package/lib/typescript/src/GestureViewerManager.d.ts.map +1 -1
  22. package/lib/typescript/src/useGestureViewer.d.ts +4 -3
  23. package/lib/typescript/src/useGestureViewer.d.ts.map +1 -1
  24. package/lib/typescript/src/useGestureViewerPaging.d.ts +3 -0
  25. package/lib/typescript/src/useGestureViewerPaging.d.ts.map +1 -0
  26. package/lib/typescript/src/useGestureViewerPaging.types.d.ts +32 -0
  27. package/lib/typescript/src/useGestureViewerPaging.types.d.ts.map +1 -0
  28. package/lib/typescript/src/useGestureViewerPaging.web.d.ts +3 -0
  29. package/lib/typescript/src/useGestureViewerPaging.web.d.ts.map +1 -0
  30. package/lib/typescript/src/utils/index.d.ts +7 -0
  31. package/lib/typescript/src/utils/index.d.ts.map +1 -1
  32. package/lib/typescript/src/utils/tapZoom.d.ts +12 -0
  33. package/lib/typescript/src/utils/tapZoom.d.ts.map +1 -0
  34. package/lib/typescript/src/utils/webScroll.d.ts +18 -0
  35. package/lib/typescript/src/utils/webScroll.d.ts.map +1 -0
  36. package/package.json +1 -1
  37. package/src/GestureViewer.tsx +39 -15
  38. package/src/GestureViewerManager.ts +29 -6
  39. package/src/useGestureViewer.ts +71 -139
  40. package/src/useGestureViewerPaging.ts +112 -0
  41. package/src/useGestureViewerPaging.types.ts +34 -0
  42. package/src/useGestureViewerPaging.web.ts +390 -0
  43. package/src/utils/index.ts +17 -1
  44. package/src/utils/tapZoom.ts +44 -0
  45. package/src/utils/webScroll.ts +96 -0
@@ -1,10 +1,5 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
- import {
3
- InteractionManager,
4
- type NativeScrollEvent,
5
- type NativeSyntheticEvent,
6
- useWindowDimensions,
7
- } from 'react-native';
2
+ import { InteractionManager, Platform, useWindowDimensions } from 'react-native';
8
3
  import { Gesture, type GestureType } from 'react-native-gesture-handler';
9
4
  import {
10
5
  Easing,
@@ -20,7 +15,9 @@ import { scheduleOnRN } from 'react-native-worklets';
20
15
  import type GestureViewerManager from './GestureViewerManager';
21
16
  import { registry } from './GestureViewerRegistry';
22
17
  import type { GestureViewerProps, TriggerRect } from './types';
23
- import { createBoundsConstraint, createScrollAction, getLoopAdjustedIndex } from './utils';
18
+ import { useGestureViewerPaging } from './useGestureViewerPaging';
19
+ import { createBoundsConstraint, createScrollAction } from './utils';
20
+ import { applyTapZoomAtPoint } from './utils/tapZoom';
24
21
 
25
22
  type UseGestureViewerProps<ItemT, LC> = Omit<
26
23
  GestureViewerProps<ItemT, LC>,
@@ -131,6 +128,35 @@ export const useGestureViewer = <ItemT, LC>({
131
128
  [width, itemSpacing],
132
129
  );
133
130
 
131
+ const resetTransformState = useCallback(() => {
132
+ translateX.value = withTiming(0);
133
+ translateY.value = withTiming(0);
134
+ initialTranslateX.value = withTiming(0);
135
+ initialTranslateY.value = withTiming(0);
136
+ startScale.value = withTiming(1);
137
+ scale.value = withTiming(1);
138
+ rotation.value = 0;
139
+ }, [initialTranslateX, initialTranslateY, rotation, scale, startScale, translateX, translateY]);
140
+
141
+ const syncCurrentIndex = useCallback(
142
+ (nextIndex: number) => {
143
+ if (!manager || nextIndex < 0 || nextIndex >= dataLength) {
144
+ return;
145
+ }
146
+
147
+ const managerCurrentIndex = manager.getState().currentIndex;
148
+
149
+ if (nextIndex === managerCurrentIndex) {
150
+ return;
151
+ }
152
+
153
+ manager.setCurrentIndex(nextIndex);
154
+ manager.notifyStateChange();
155
+ resetTransformState();
156
+ },
157
+ [dataLength, manager, resetTransformState],
158
+ );
159
+
134
160
  const emitZoomChange = useCallback(
135
161
  (currentScale: number, prevScale: number | null) => {
136
162
  if (manager) {
@@ -200,6 +226,7 @@ export const useGestureViewer = <ItemT, LC>({
200
226
  manager.setWidth(width + itemSpacing);
201
227
  manager.setHeight(height);
202
228
  manager.setZoomSharedValues(scale, translateX, translateY, maxZoomScale);
229
+ manager.setResetTransformCallback(resetTransformState);
203
230
  manager.setRotation(rotation);
204
231
  manager.setEnableLoop(enableLoop);
205
232
  manager.notifyStateChange();
@@ -214,6 +241,7 @@ export const useGestureViewer = <ItemT, LC>({
214
241
  enableLoop,
215
242
  scale,
216
243
  height,
244
+ resetTransformState,
217
245
  translateX,
218
246
  translateY,
219
247
  rotation,
@@ -257,44 +285,6 @@ export const useGestureViewer = <ItemT, LC>({
257
285
  scrollTo,
258
286
  ]);
259
287
 
260
- useEffect(() => {
261
- if (
262
- !autoPlay ||
263
- !manager ||
264
- dataLength <= 1 ||
265
- isZoomed ||
266
- isRotated ||
267
- (!enableLoop && currentIndex === dataLength - 1)
268
- ) {
269
- return;
270
- }
271
-
272
- const intervalMs = Math.max(250, Math.floor(autoPlayInterval || 0));
273
-
274
- if (!Number.isFinite(intervalMs)) {
275
- return;
276
- }
277
-
278
- const interval = setInterval(() => {
279
- if (isZoomed || isRotated) {
280
- return;
281
- }
282
-
283
- manager.goToNext();
284
- }, intervalMs);
285
-
286
- return () => clearInterval(interval);
287
- }, [
288
- autoPlay,
289
- autoPlayInterval,
290
- manager,
291
- dataLength,
292
- currentIndex,
293
- enableLoop,
294
- isZoomed,
295
- isRotated,
296
- ]);
297
-
298
288
  useEffect(() => {
299
289
  onAnimationCompleteRef.current = triggerAnimation?.onAnimationComplete;
300
290
  }, [triggerAnimation?.onAnimationComplete]);
@@ -394,66 +384,6 @@ export const useGestureViewer = <ItemT, LC>({
394
384
  triggerOpacity,
395
385
  ]);
396
386
 
397
- const onMomentumScrollEnd = useCallback(
398
- (event: NativeSyntheticEvent<NativeScrollEvent>) => {
399
- if (!enableHorizontalSwipe) {
400
- return;
401
- }
402
-
403
- const { contentOffset } = event.nativeEvent;
404
- const scrollIndex = Math.round(contentOffset.x / (width + itemSpacing));
405
-
406
- const isLoopHandled = manager?.handleMomentumScrollEnd(scrollIndex);
407
-
408
- if (isLoopHandled) {
409
- return;
410
- }
411
-
412
- const { realIndex, needsJump, jumpToIndex } = getLoopAdjustedIndex(
413
- scrollIndex,
414
- dataLength,
415
- enableLoop,
416
- );
417
-
418
- if (needsJump && jumpToIndex !== undefined) {
419
- scrollTo(jumpToIndex, false);
420
- }
421
-
422
- const currentIndex = manager?.getState()?.currentIndex;
423
-
424
- if (realIndex !== currentIndex && realIndex >= 0 && realIndex < dataLength) {
425
- if (manager) {
426
- manager.setCurrentIndex(realIndex);
427
- manager.notifyStateChange();
428
- }
429
-
430
- translateX.value = withTiming(0);
431
- translateY.value = withTiming(0);
432
- initialTranslateX.value = withTiming(0);
433
- initialTranslateY.value = withTiming(0);
434
- startScale.value = withTiming(1);
435
- scale.value = withTiming(1);
436
- rotation.value = 0;
437
- }
438
- },
439
- [
440
- scrollTo,
441
- manager,
442
- dataLength,
443
- width,
444
- itemSpacing,
445
- enableHorizontalSwipe,
446
- enableLoop,
447
- translateX,
448
- translateY,
449
- scale,
450
- initialTranslateX,
451
- initialTranslateY,
452
- startScale,
453
- rotation,
454
- ],
455
- );
456
-
457
387
  const dismissGesture = useMemo(() => {
458
388
  const canDismiss = !isZoomed && dismissOptions.enabled;
459
389
 
@@ -644,41 +574,21 @@ export const useGestureViewer = <ItemT, LC>({
644
574
  const doubleTapGesture = useMemo(
645
575
  () =>
646
576
  Gesture.Tap()
647
- .enabled(enableDoubleTapZoom)
577
+ .enabled(enableDoubleTapZoom && Platform.OS !== 'web')
648
578
  .numberOfTaps(2)
649
579
  .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, {
677
- duration: 300,
678
- easing: Easing.bezier(0.25, 0.1, 0.25, 1),
580
+ applyTapZoomAtPoint({
581
+ x: event.x,
582
+ y: event.y,
583
+ width,
584
+ height,
585
+ maxZoomScale,
586
+ scale,
587
+ translateX,
588
+ translateY,
679
589
  });
680
590
  }),
681
- [scale, enableDoubleTapZoom, maxZoomScale, translateX, translateY, width, height],
591
+ [enableDoubleTapZoom, height, maxZoomScale, scale, translateX, translateY, width],
682
592
  );
683
593
 
684
594
  const zoomGesture = useMemo(
@@ -716,9 +626,29 @@ export const useGestureViewer = <ItemT, LC>({
716
626
  return Gesture.Native().requireExternalGestureToFail(dismissGestureRef);
717
627
  }, []);
718
628
 
719
- const onScrollBeginDrag = useCallback(() => {
720
- manager?.handleScrollBeginDrag();
721
- }, [manager]);
629
+ const { onMomentumScrollEnd, onScroll, onScrollBeginDrag, onWebDoubleClick } =
630
+ useGestureViewerPaging({
631
+ adjustedInitialIndex,
632
+ autoPlay,
633
+ autoPlayInterval,
634
+ currentIndex,
635
+ dataLength,
636
+ enableDoubleTapZoom,
637
+ enableHorizontalSwipe,
638
+ enableLoop,
639
+ height,
640
+ isRotated,
641
+ isZoomed,
642
+ itemSpacing,
643
+ manager,
644
+ maxZoomScale,
645
+ scale,
646
+ scrollTo,
647
+ syncCurrentIndex,
648
+ translateX,
649
+ translateY,
650
+ width,
651
+ });
722
652
 
723
653
  return {
724
654
  animatedStyle,
@@ -732,7 +662,9 @@ export const useGestureViewer = <ItemT, LC>({
732
662
  isZoomed,
733
663
  listRef,
734
664
  nativeScrollGesture,
665
+ onWebDoubleClick,
735
666
  onMomentumScrollEnd,
667
+ onScroll,
736
668
 
737
669
  onScrollBeginDrag,
738
670
  zoomGesture,
@@ -0,0 +1,112 @@
1
+ import { useCallback, useEffect } from 'react';
2
+ import type { NativeScrollEvent, NativeSyntheticEvent } from 'react-native';
3
+
4
+ import type {
5
+ UseGestureViewerPagingArgs,
6
+ UseGestureViewerPagingResult,
7
+ } from './useGestureViewerPaging.types';
8
+ import { getLoopAdjustedIndex } from './utils';
9
+
10
+ export function useGestureViewerPaging({
11
+ autoPlay,
12
+ autoPlayInterval,
13
+ currentIndex,
14
+ dataLength,
15
+ enableHorizontalSwipe,
16
+ enableLoop,
17
+ isRotated,
18
+ isZoomed,
19
+ itemSpacing,
20
+ manager,
21
+ scrollTo,
22
+ syncCurrentIndex,
23
+ width,
24
+ }: UseGestureViewerPagingArgs): UseGestureViewerPagingResult {
25
+ useEffect(() => {
26
+ if (
27
+ !autoPlay ||
28
+ !manager ||
29
+ dataLength <= 1 ||
30
+ isZoomed ||
31
+ isRotated ||
32
+ (!enableLoop && currentIndex === dataLength - 1)
33
+ ) {
34
+ return;
35
+ }
36
+
37
+ const intervalMs = Math.max(250, Math.floor(autoPlayInterval || 0));
38
+
39
+ if (!Number.isFinite(intervalMs)) {
40
+ return;
41
+ }
42
+
43
+ const interval = setInterval(() => {
44
+ if (isZoomed || isRotated) {
45
+ return;
46
+ }
47
+
48
+ manager.goToNext();
49
+ }, intervalMs);
50
+
51
+ return () => clearInterval(interval);
52
+ }, [
53
+ autoPlay,
54
+ autoPlayInterval,
55
+ currentIndex,
56
+ dataLength,
57
+ enableLoop,
58
+ isRotated,
59
+ isZoomed,
60
+ manager,
61
+ ]);
62
+
63
+ const onMomentumScrollEnd = useCallback(
64
+ (event: NativeSyntheticEvent<NativeScrollEvent>) => {
65
+ if (!enableHorizontalSwipe) {
66
+ return;
67
+ }
68
+
69
+ const { contentOffset } = event.nativeEvent;
70
+ const scrollIndex = Math.round(contentOffset.x / (width + itemSpacing));
71
+
72
+ const isLoopHandled = manager?.handleMomentumScrollEnd(scrollIndex);
73
+
74
+ if (isLoopHandled) {
75
+ return;
76
+ }
77
+
78
+ const { realIndex, needsJump, jumpToIndex } = getLoopAdjustedIndex(
79
+ scrollIndex,
80
+ dataLength,
81
+ enableLoop,
82
+ );
83
+
84
+ if (needsJump && jumpToIndex !== undefined) {
85
+ scrollTo(jumpToIndex, false);
86
+ }
87
+
88
+ syncCurrentIndex(realIndex);
89
+ },
90
+ [
91
+ dataLength,
92
+ enableHorizontalSwipe,
93
+ enableLoop,
94
+ itemSpacing,
95
+ manager,
96
+ scrollTo,
97
+ syncCurrentIndex,
98
+ width,
99
+ ],
100
+ );
101
+
102
+ const onScrollBeginDrag = useCallback(() => {
103
+ manager?.handleScrollBeginDrag();
104
+ }, [manager]);
105
+
106
+ return {
107
+ onMomentumScrollEnd,
108
+ onScroll: undefined,
109
+ onScrollBeginDrag,
110
+ onWebDoubleClick: undefined,
111
+ };
112
+ }
@@ -0,0 +1,34 @@
1
+ import type { ScrollViewProps } from 'react-native';
2
+ import type { SharedValue } from 'react-native-reanimated';
3
+
4
+ import type GestureViewerManager from './GestureViewerManager';
5
+
6
+ export type UseGestureViewerPagingArgs = {
7
+ adjustedInitialIndex: number;
8
+ autoPlay: boolean;
9
+ autoPlayInterval: number;
10
+ currentIndex: number;
11
+ dataLength: number;
12
+ enableDoubleTapZoom: boolean;
13
+ enableHorizontalSwipe: boolean;
14
+ enableLoop: boolean;
15
+ height: number;
16
+ isRotated: boolean;
17
+ isZoomed: boolean;
18
+ itemSpacing: number;
19
+ manager: GestureViewerManager | null;
20
+ maxZoomScale: number;
21
+ scale: SharedValue<number>;
22
+ scrollTo: (index: number, animated: boolean) => void;
23
+ syncCurrentIndex: (nextIndex: number) => void;
24
+ translateX: SharedValue<number>;
25
+ translateY: SharedValue<number>;
26
+ width: number;
27
+ };
28
+
29
+ export type UseGestureViewerPagingResult = {
30
+ onMomentumScrollEnd?: ScrollViewProps['onMomentumScrollEnd'];
31
+ onScroll?: ScrollViewProps['onScroll'];
32
+ onScrollBeginDrag?: ScrollViewProps['onScrollBeginDrag'];
33
+ onWebDoubleClick?: (event: any) => void;
34
+ };