react-native-gesture-image-viewer 2.4.0 → 3.0.0-beta.1

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 (138) hide show
  1. package/README.md +5 -3
  2. package/lib/module/GestureViewer.js +96 -147
  3. package/lib/module/GestureViewer.js.map +1 -1
  4. package/lib/module/GestureViewerManager.js +49 -105
  5. package/lib/module/GestureViewerManager.js.map +1 -1
  6. package/lib/module/gestureViewerAnimation.js +18 -0
  7. package/lib/module/gestureViewerAnimation.js.map +1 -0
  8. package/lib/module/gestureViewerGestures.js +12 -0
  9. package/lib/module/gestureViewerGestures.js.map +1 -0
  10. package/lib/module/gestureViewerPaging.js +66 -0
  11. package/lib/module/gestureViewerPaging.js.map +1 -0
  12. package/lib/module/gestureViewerRenderWindow.js +29 -0
  13. package/lib/module/gestureViewerRenderWindow.js.map +1 -0
  14. package/lib/module/getWebContentProps.js +6 -0
  15. package/lib/module/getWebContentProps.js.map +1 -0
  16. package/lib/module/getWebContentProps.web.js +8 -0
  17. package/lib/module/getWebContentProps.web.js.map +1 -0
  18. package/lib/module/platformTapGestures.js +4 -0
  19. package/lib/module/platformTapGestures.js.map +1 -0
  20. package/lib/module/platformTapGestures.web.js +4 -0
  21. package/lib/module/platformTapGestures.web.js.map +1 -0
  22. package/lib/module/renderWindow.js +160 -0
  23. package/lib/module/renderWindow.js.map +1 -0
  24. package/lib/module/useGestureViewer.js +459 -211
  25. package/lib/module/useGestureViewer.js.map +1 -1
  26. package/lib/module/useGestureViewerController.js +3 -3
  27. package/lib/module/useGestureViewerController.js.map +1 -1
  28. package/lib/module/useGestureViewerManagerBridge.js +85 -0
  29. package/lib/module/useGestureViewerManagerBridge.js.map +1 -0
  30. package/lib/module/useGestureViewerPaging.js +168 -79
  31. package/lib/module/useGestureViewerPaging.js.map +1 -1
  32. package/lib/module/useWebClickHandler.js +7 -0
  33. package/lib/module/useWebClickHandler.js.map +1 -0
  34. package/lib/module/useWebClickHandler.web.js +85 -0
  35. package/lib/module/useWebClickHandler.web.js.map +1 -0
  36. package/lib/module/useWebSingleTapTimer.js +12 -0
  37. package/lib/module/useWebSingleTapTimer.js.map +1 -0
  38. package/lib/module/useWebSingleTapTimer.web.js +25 -0
  39. package/lib/module/useWebSingleTapTimer.web.js.map +1 -0
  40. package/lib/module/utils/index.js +0 -77
  41. package/lib/module/utils/index.js.map +1 -1
  42. package/lib/module/utils/tapZoom.js +4 -3
  43. package/lib/module/utils/tapZoom.js.map +1 -1
  44. package/lib/typescript/src/GestureViewer.d.ts +1 -1
  45. package/lib/typescript/src/GestureViewer.d.ts.map +1 -1
  46. package/lib/typescript/src/GestureViewerManager.d.ts +20 -20
  47. package/lib/typescript/src/GestureViewerManager.d.ts.map +1 -1
  48. package/lib/typescript/src/gestureViewerAnimation.d.ts +15 -0
  49. package/lib/typescript/src/gestureViewerAnimation.d.ts.map +1 -0
  50. package/lib/typescript/src/gestureViewerGestures.d.ts +10 -0
  51. package/lib/typescript/src/gestureViewerGestures.d.ts.map +1 -0
  52. package/lib/typescript/src/gestureViewerPaging.d.ts +13 -0
  53. package/lib/typescript/src/gestureViewerPaging.d.ts.map +1 -0
  54. package/lib/typescript/src/gestureViewerRenderWindow.d.ts +15 -0
  55. package/lib/typescript/src/gestureViewerRenderWindow.d.ts.map +1 -0
  56. package/lib/typescript/src/getWebContentProps.d.ts +3 -0
  57. package/lib/typescript/src/getWebContentProps.d.ts.map +1 -0
  58. package/lib/typescript/src/getWebContentProps.web.d.ts +5 -0
  59. package/lib/typescript/src/getWebContentProps.web.d.ts.map +1 -0
  60. package/lib/typescript/src/platformTapGestures.d.ts +2 -0
  61. package/lib/typescript/src/platformTapGestures.d.ts.map +1 -0
  62. package/lib/typescript/src/platformTapGestures.web.d.ts +2 -0
  63. package/lib/typescript/src/platformTapGestures.web.d.ts.map +1 -0
  64. package/lib/typescript/src/renderWindow.d.ts +44 -0
  65. package/lib/typescript/src/renderWindow.d.ts.map +1 -0
  66. package/lib/typescript/src/types.d.ts +36 -53
  67. package/lib/typescript/src/types.d.ts.map +1 -1
  68. package/lib/typescript/src/useGestureViewer.d.ts +11 -10
  69. package/lib/typescript/src/useGestureViewer.d.ts.map +1 -1
  70. package/lib/typescript/src/useGestureViewerController.d.ts +1 -1
  71. package/lib/typescript/src/useGestureViewerManagerBridge.d.ts +28 -0
  72. package/lib/typescript/src/useGestureViewerManagerBridge.d.ts.map +1 -0
  73. package/lib/typescript/src/useGestureViewerPaging.d.ts +33 -2
  74. package/lib/typescript/src/useGestureViewerPaging.d.ts.map +1 -1
  75. package/lib/typescript/src/useWebClickHandler.d.ts +35 -0
  76. package/lib/typescript/src/useWebClickHandler.d.ts.map +1 -0
  77. package/lib/typescript/src/useWebClickHandler.web.d.ts +36 -0
  78. package/lib/typescript/src/useWebClickHandler.web.d.ts.map +1 -0
  79. package/lib/typescript/src/useWebSingleTapTimer.d.ts +6 -0
  80. package/lib/typescript/src/useWebSingleTapTimer.d.ts.map +1 -0
  81. package/lib/typescript/src/useWebSingleTapTimer.web.d.ts +6 -0
  82. package/lib/typescript/src/useWebSingleTapTimer.web.d.ts.map +1 -0
  83. package/lib/typescript/src/utils/index.d.ts +0 -20
  84. package/lib/typescript/src/utils/index.d.ts.map +1 -1
  85. package/lib/typescript/src/utils/tapZoom.d.ts.map +1 -1
  86. package/package.json +2 -2
  87. package/src/GestureViewer.tsx +113 -208
  88. package/src/GestureViewerManager.ts +77 -122
  89. package/src/gestureViewerAnimation.ts +18 -0
  90. package/src/gestureViewerGestures.ts +20 -0
  91. package/src/gestureViewerPaging.ts +116 -0
  92. package/src/gestureViewerRenderWindow.ts +56 -0
  93. package/src/getWebContentProps.ts +5 -0
  94. package/src/getWebContentProps.web.ts +5 -0
  95. package/src/platformTapGestures.ts +1 -0
  96. package/src/platformTapGestures.web.ts +1 -0
  97. package/src/renderWindow.ts +253 -0
  98. package/src/types.ts +34 -100
  99. package/src/useGestureViewer.ts +665 -275
  100. package/src/useGestureViewerController.ts +3 -3
  101. package/src/useGestureViewerManagerBridge.ts +118 -0
  102. package/src/useGestureViewerPaging.ts +306 -112
  103. package/src/useWebClickHandler.ts +44 -0
  104. package/src/useWebClickHandler.web.ts +146 -0
  105. package/src/useWebSingleTapTimer.ts +10 -0
  106. package/src/useWebSingleTapTimer.web.ts +30 -0
  107. package/src/utils/index.ts +0 -102
  108. package/src/utils/tapZoom.ts +4 -3
  109. package/lib/module/WebPagingFixStyle.js +0 -25
  110. package/lib/module/WebPagingFixStyle.js.map +0 -1
  111. package/lib/module/scheduleInitialScroll.js +0 -21
  112. package/lib/module/scheduleInitialScroll.js.map +0 -1
  113. package/lib/module/useGestureViewerPaging.types.js +0 -4
  114. package/lib/module/useGestureViewerPaging.types.js.map +0 -1
  115. package/lib/module/useGestureViewerPaging.web.js +0 -291
  116. package/lib/module/useGestureViewerPaging.web.js.map +0 -1
  117. package/lib/module/utils/FlashList.js +0 -9
  118. package/lib/module/utils/FlashList.js.map +0 -1
  119. package/lib/module/utils/webScroll.js +0 -70
  120. package/lib/module/utils/webScroll.js.map +0 -1
  121. package/lib/typescript/src/WebPagingFixStyle.d.ts +0 -6
  122. package/lib/typescript/src/WebPagingFixStyle.d.ts.map +0 -1
  123. package/lib/typescript/src/scheduleInitialScroll.d.ts +0 -3
  124. package/lib/typescript/src/scheduleInitialScroll.d.ts.map +0 -1
  125. package/lib/typescript/src/useGestureViewerPaging.types.d.ts +0 -42
  126. package/lib/typescript/src/useGestureViewerPaging.types.d.ts.map +0 -1
  127. package/lib/typescript/src/useGestureViewerPaging.web.d.ts +0 -3
  128. package/lib/typescript/src/useGestureViewerPaging.web.d.ts.map +0 -1
  129. package/lib/typescript/src/utils/FlashList.d.ts +0 -2
  130. package/lib/typescript/src/utils/FlashList.d.ts.map +0 -1
  131. package/lib/typescript/src/utils/webScroll.d.ts +0 -18
  132. package/lib/typescript/src/utils/webScroll.d.ts.map +0 -1
  133. package/src/WebPagingFixStyle.tsx +0 -25
  134. package/src/scheduleInitialScroll.ts +0 -36
  135. package/src/useGestureViewerPaging.types.ts +0 -42
  136. package/src/useGestureViewerPaging.web.ts +0 -453
  137. package/src/utils/FlashList.ts +0 -7
  138. package/src/utils/webScroll.ts +0 -96
@@ -1,17 +1,28 @@
1
1
  "use strict";
2
2
 
3
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
- import { Platform, useWindowDimensions } from 'react-native';
3
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
4
+ import { useWindowDimensions } from 'react-native';
5
5
  import { Gesture } from 'react-native-gesture-handler';
6
- import { Easing, interpolate, useAnimatedReaction, useAnimatedStyle, useSharedValue, withSpring, withTiming } from 'react-native-reanimated';
6
+ import { Easing, cancelAnimation, interpolate, useAnimatedReaction, useAnimatedStyle, useSharedValue, withSpring, withTiming } from 'react-native-reanimated';
7
7
  import { scheduleOnRN } from 'react-native-worklets';
8
+ import { PAGE_SPRING_CONFIG, SWIPE_THRESHOLD_RATIO, SWIPE_VELOCITY_THRESHOLD } from "./gestureViewerAnimation.js";
9
+ import { normalizeHorizontalSwipeThreshold } from "./gestureViewerPaging.js";
8
10
  import { registry } from "./GestureViewerRegistry.js";
9
- import { scheduleInitialScroll } from "./scheduleInitialScroll.js";
10
- import { useGestureViewerPaging } from './useGestureViewerPaging';
11
- import { createBoundsConstraint, createScrollAction } from "./utils/index.js";
11
+ import { resolveGestureViewerRenderWindow } from "./gestureViewerRenderWindow.js";
12
+ import { enableNativeTapGestures } from './platformTapGestures';
13
+ import { getLogicalIndex, getPageStride, getVirtualIndexForLogicalIndex, normalizePageSpacing, clampIndex, resolveNavigation, shouldRunNavigationDuringTransition } from "./renderWindow.js";
14
+ import { useGestureViewerManagerBridge } from "./useGestureViewerManagerBridge.js";
15
+ import { useGestureViewerPaging } from "./useGestureViewerPaging.js";
16
+ import { useWebClickHandler } from './useWebClickHandler';
17
+ import { useWebSingleTapTimer } from './useWebSingleTapTimer';
18
+ import { createBoundsConstraint } from "./utils/index.js";
12
19
  import { getDismissDistance, shouldDismissByDirection } from "./utils/dismiss.js";
13
20
  import { applyTapZoomAtPoint } from "./utils/tapZoom.js";
14
21
  import { calculateFocalPointTranslation, shouldAcceptFocalPoint } from "./utils/zoom.js";
22
+ const NATIVE_TAP_MAX_DISTANCE = 10;
23
+ const isValidTriggerRect = rect => {
24
+ return !!rect && rect.width > 0 && rect.height > 0;
25
+ };
15
26
  export const useGestureViewer = ({
16
27
  data,
17
28
  initialIndex = 0,
@@ -21,11 +32,12 @@ export const useGestureViewer = ({
21
32
  dismiss,
22
33
  enableDoubleTapZoom = true,
23
34
  enablePinchZoom = true,
24
- enableHorizontalSwipe = true,
35
+ horizontalSwipe,
25
36
  enablePanWhenZoomed = true,
26
37
  enableLoop = false,
27
38
  maxZoomScale = 2,
28
- itemSpacing = 0,
39
+ pageSpacing = 0,
40
+ windowSize = 3,
29
41
  height: customHeight,
30
42
  id = 'default',
31
43
  onDismissStart,
@@ -39,43 +51,61 @@ export const useGestureViewer = ({
39
51
  } = useWindowDimensions();
40
52
  const width = customWidth || screenWidth;
41
53
  const height = customHeight || screenHeight;
54
+ const dataLength = data?.length || 0;
55
+ const normalizedPageSpacing = normalizePageSpacing(pageSpacing);
56
+ const pageStride = getPageStride(width, normalizedPageSpacing);
57
+ const initialCurrentIndex = clampIndex(initialIndex, dataLength);
58
+ const horizontalSwipeEnabled = horizontalSwipe?.enabled ?? true;
59
+ const horizontalSwipeDistanceThresholdRatio = normalizeHorizontalSwipeThreshold(horizontalSwipe?.distanceThresholdRatio, SWIPE_THRESHOLD_RATIO);
60
+ const horizontalSwipeVelocityThreshold = normalizeHorizontalSwipeThreshold(horizontalSwipe?.velocityThreshold, SWIPE_VELOCITY_THRESHOLD);
42
61
  const dismissGestureRef = useRef(undefined);
43
62
  const [isZoomed, setIsZoomed] = useState(false);
44
63
  const [isRotated, setIsRotated] = useState(false);
45
64
  const [isPinching, setIsPinching] = useState(false);
46
65
  const [shouldStartTriggerAnimation, setShouldStartTriggerAnimation] = useState(false);
47
- const [manager, setManager] = useState(null);
48
- const [currentIndex, setCurrentIndex] = useState(initialIndex);
49
- const [activeTriggerNode, setActiveTriggerNode] = useState(null);
50
- const listRef = useRef(null);
66
+ const [currentIndex, setCurrentIndex] = useState(initialCurrentIndex);
67
+ const [centerVirtualIndex, setCenterVirtualIndex] = useState(initialCurrentIndex);
68
+ const [activeTriggerNode, setActiveTriggerNode] = useState(() => registry.getActiveTriggerNode(id));
69
+ const [isTriggerOpening, setIsTriggerOpening] = useState(() => !!registry.getActiveTriggerNode(id));
51
70
  const triggerRectRef = useRef(null);
52
- const pendingIndexRef = useRef(initialIndex);
71
+ const pendingIndexRef = useRef(initialCurrentIndex);
72
+ const currentIndexRef = useRef(initialCurrentIndex);
73
+ const centerVirtualIndexRef = useRef(initialCurrentIndex);
74
+ const previousInitialIndexRef = useRef(initialIndex);
75
+ const hasResolvedInitialIndexRef = useRef(dataLength > 0);
53
76
  const onAnimationCompleteRef = useRef(triggerAnimation?.onAnimationComplete);
54
77
  const onSingleTapRef = useRef(onSingleTap);
55
78
  const dataRef = useRef(data);
56
- const managerRef = useRef(manager);
57
- const isValidTriggerRect = useCallback(rect => {
58
- return !!rect && rect.width > 0 && rect.height > 0;
59
- }, []);
79
+ const dataLengthRef = useRef(dataLength);
80
+ const enableLoopRef = useRef(enableLoop);
81
+ const managerRef = useRef(null);
82
+ const isZoomedRef = useRef(isZoomed);
83
+ const isRotatedRef = useRef(isRotated);
84
+ const isPinchingRef = useRef(isPinching);
60
85
  const initialTranslateY = useSharedValue(0);
61
86
  const initialTranslateX = useSharedValue(0);
62
87
  const startScale = useSharedValue(1);
63
88
  const translateY = useSharedValue(0);
64
89
  const translateX = useSharedValue(0);
65
90
  const scale = useSharedValue(1);
66
- const backdropOpacity = useSharedValue(1);
67
91
  const rotation = useSharedValue(0);
68
92
  const triggerScale = useSharedValue(1);
69
93
  const triggerTranslateX = useSharedValue(0);
70
94
  const triggerTranslateY = useSharedValue(0);
71
- const triggerOpacity = useSharedValue(1);
95
+ const triggerOpacity = useSharedValue(activeTriggerNode ? 0 : 1);
96
+ const hasZoomChangeListeners = useSharedValue(false);
97
+ const hasRotationChangeListeners = useSharedValue(false);
72
98
  const lastFocalX = useSharedValue(0);
73
99
  const lastFocalY = useSharedValue(0);
74
100
  const startFocalX = useSharedValue(0);
75
101
  const startFocalY = useSharedValue(0);
76
102
  const hasActiveFocal = useSharedValue(false);
77
- const dataLength = data?.length || 0;
78
- const previousDataLengthRef = useRef(dataLength);
103
+ const nativeInteractionHadMultipleTouches = useSharedValue(false);
104
+ const suppressNativeTap = useSharedValue(false);
105
+ const {
106
+ clearPendingWebSingleTap,
107
+ scheduleWebSingleTap
108
+ } = useWebSingleTapTimer();
79
109
  const animationConfig = useMemo(() => ({
80
110
  duration: triggerAnimation?.duration ?? 300,
81
111
  easing: triggerAnimation?.easing ?? Easing.bezier(0.25, 0.1, 0.25, 1),
@@ -88,20 +118,22 @@ export const useGestureViewer = ({
88
118
  resistance: dismiss?.resistance ?? 2,
89
119
  threshold: dismiss?.threshold ?? 80
90
120
  }), [dismiss?.direction, dismiss?.enabled, dismiss?.threshold, dismiss?.resistance, dismiss?.fadeBackdrop]);
91
- const adjustedInitialIndex = useMemo(() => {
92
- if (enableLoop && dataLength > 1) {
93
- return initialIndex + 1;
94
- }
95
- return initialIndex;
96
- }, [enableLoop, dataLength, initialIndex]);
121
+ const {
122
+ centerVirtualIndex: renderCenterVirtualIndex,
123
+ currentIndex: renderCurrentIndex,
124
+ slots: renderWindowSlots
125
+ } = resolveGestureViewerRenderWindow({
126
+ centerVirtualIndex,
127
+ currentIndex,
128
+ data,
129
+ enableLoop,
130
+ isTriggerOpening,
131
+ windowSize
132
+ });
97
133
  const constrainTranslation = useMemo(() => createBoundsConstraint({
98
134
  height,
99
135
  width
100
136
  }), [width, height]);
101
- const scrollTo = useCallback((index, animated) => {
102
- const scrollAction = createScrollAction(listRef.current, width + itemSpacing);
103
- return scrollAction.scrollTo(index, animated);
104
- }, [width, itemSpacing]);
105
137
  const resetTransformState = useCallback(() => {
106
138
  translateX.set(withTiming(0));
107
139
  translateY.set(withTiming(0));
@@ -111,114 +143,241 @@ export const useGestureViewer = ({
111
143
  scale.set(withTiming(1));
112
144
  rotation.set(0);
113
145
  }, [initialTranslateX, initialTranslateY, rotation, scale, startScale, translateX, translateY]);
114
- const syncPendingIndex = useCallback(nextIndex => {
115
- if (nextIndex < 0 || nextIndex >= dataLength) {
146
+ const resetTransformStateImmediately = useCallback(() => {
147
+ cancelAnimation(translateX);
148
+ cancelAnimation(translateY);
149
+ cancelAnimation(initialTranslateX);
150
+ cancelAnimation(initialTranslateY);
151
+ cancelAnimation(startScale);
152
+ cancelAnimation(scale);
153
+ cancelAnimation(rotation);
154
+ translateX.set(0);
155
+ translateY.set(0);
156
+ initialTranslateX.set(0);
157
+ initialTranslateY.set(0);
158
+ startScale.set(1);
159
+ scale.set(1);
160
+ rotation.set(0);
161
+ }, [initialTranslateX, initialTranslateY, rotation, scale, startScale, translateX, translateY]);
162
+ const commitCurrentIndex = useCallback(nextIndex => {
163
+ pendingIndexRef.current = nextIndex;
164
+ currentIndexRef.current = nextIndex;
165
+ setCurrentIndex(nextIndex);
166
+ managerRef.current?.notifyStateChange();
167
+ }, []);
168
+ const commitVirtualIndexOnly = useCallback(nextVirtualIndex => {
169
+ const nextLogicalIndex = getLogicalIndex(nextVirtualIndex, dataLengthRef.current, enableLoopRef.current);
170
+ if (nextLogicalIndex === null) {
116
171
  return;
117
172
  }
118
- pendingIndexRef.current = nextIndex;
119
- }, [dataLength]);
120
- const syncCurrentIndex = useCallback(nextIndex => {
121
- if (!manager || nextIndex < 0 || nextIndex >= dataLength) {
173
+ centerVirtualIndexRef.current = nextVirtualIndex;
174
+ setCenterVirtualIndex(nextVirtualIndex);
175
+ commitCurrentIndex(nextLogicalIndex);
176
+ }, [commitCurrentIndex]);
177
+ const {
178
+ animateToVirtualPage,
179
+ horizontalPagingGesture,
180
+ isPageTransitioningRef,
181
+ pageTransitionLocked,
182
+ snapToVirtualPage,
183
+ visualPage
184
+ } = useGestureViewerPaging({
185
+ centerVirtualIndex,
186
+ clearPendingWebSingleTap,
187
+ commitVirtualIndexOnly,
188
+ currentIndex,
189
+ dataLength,
190
+ enableLoop,
191
+ horizontalSwipeDistanceThresholdRatio,
192
+ horizontalSwipeEnabled,
193
+ horizontalSwipeVelocityThreshold,
194
+ initialPage: initialCurrentIndex,
195
+ isPinching,
196
+ isRotated,
197
+ isTriggerOpening,
198
+ isZoomed,
199
+ pageStride,
200
+ suppressNativeTap,
201
+ width
202
+ });
203
+ const navigateToIndex = useCallback((targetIndex, options, preferredDirection) => {
204
+ const resolution = resolveNavigation({
205
+ currentIndex: currentIndexRef.current,
206
+ dataLength: dataLengthRef.current,
207
+ enableLoop: enableLoopRef.current,
208
+ preferredDirection,
209
+ targetIndex
210
+ });
211
+ if (resolution.kind === 'noop') {
122
212
  return;
123
213
  }
124
- pendingIndexRef.current = nextIndex;
125
- const managerCurrentIndex = manager.getState().currentIndex;
126
- if (nextIndex === managerCurrentIndex) {
214
+ if (isPageTransitioningRef.current && !shouldRunNavigationDuringTransition(resolution, options)) {
215
+ return;
216
+ }
217
+ const targetVirtualIndex = resolution.kind === 'step' ? centerVirtualIndexRef.current + resolution.direction : getVirtualIndexForLogicalIndex(resolution.targetIndex, centerVirtualIndexRef.current, dataLengthRef.current, enableLoopRef.current);
218
+ if (targetVirtualIndex === null) {
219
+ return;
220
+ }
221
+ if (resolution.kind !== 'step' || options?.animated === false) {
222
+ resetTransformStateImmediately();
223
+ snapToVirtualPage(targetVirtualIndex);
127
224
  return;
128
225
  }
129
- manager.setCurrentIndex(nextIndex);
130
- manager.notifyStateChange();
131
226
  resetTransformState();
132
- }, [dataLength, manager, resetTransformState]);
227
+ animateToVirtualPage(targetVirtualIndex);
228
+ }, [animateToVirtualPage, isPageTransitioningRef, resetTransformStateImmediately, resetTransformState, snapToVirtualPage]);
229
+ const navigateByDirection = useCallback(direction => {
230
+ const currentDataLength = dataLengthRef.current;
231
+ if (currentDataLength <= 0) {
232
+ return;
233
+ }
234
+ const nextIndex = currentIndexRef.current + direction;
235
+ if (nextIndex >= 0 && nextIndex < currentDataLength) {
236
+ navigateToIndex(nextIndex, undefined, direction);
237
+ return;
238
+ }
239
+ if (!enableLoopRef.current || currentDataLength <= 1) {
240
+ return;
241
+ }
242
+ navigateToIndex(direction === 1 ? 0 : currentDataLength - 1, undefined, direction);
243
+ }, [navigateToIndex]);
244
+ const navigateToNext = useCallback(() => {
245
+ navigateByDirection(1);
246
+ }, [navigateByDirection]);
247
+ const navigateToPrevious = useCallback(() => {
248
+ navigateByDirection(-1);
249
+ }, [navigateByDirection]);
250
+ useGestureViewerManagerBridge({
251
+ currentIndexRef,
252
+ dataLengthRef,
253
+ goToIndex: navigateToIndex,
254
+ goToNext: navigateToNext,
255
+ goToPrevious: navigateToPrevious,
256
+ hasRotationChangeListeners,
257
+ hasZoomChangeListeners,
258
+ height,
259
+ id,
260
+ managerRef,
261
+ maxZoomScale,
262
+ rotation,
263
+ scale,
264
+ translateX,
265
+ translateY,
266
+ width
267
+ });
133
268
  const emitZoomChange = useCallback((currentScale, prevScale) => {
134
269
  managerRef.current?.emitZoomChange(currentScale, prevScale);
135
270
  }, []);
136
271
  const emitRotationChange = useCallback((currentRotation, prevRotation) => {
137
272
  managerRef.current?.emitRotationChange(currentRotation, prevRotation);
138
273
  }, []);
139
- const onAnimationComplete = useCallback(() => {
274
+ const finishTriggerOpening = useCallback(() => {
275
+ setIsTriggerOpening(false);
140
276
  onAnimationCompleteRef.current?.();
141
277
  }, []);
142
278
  useAnimatedReaction(() => scale.get(), (currentScale, previousScale) => {
143
- if (currentScale !== previousScale) {
279
+ if (currentScale !== previousScale && hasZoomChangeListeners.get()) {
144
280
  scheduleOnRN(emitZoomChange, currentScale, previousScale);
145
281
  }
146
- scheduleOnRN(setIsZoomed, currentScale > 1);
282
+ const currentIsZoomed = currentScale > 1;
283
+ const previousIsZoomed = (previousScale ?? 1) > 1;
284
+ if (previousScale === null || currentIsZoomed !== previousIsZoomed) {
285
+ scheduleOnRN(setIsZoomed, currentIsZoomed);
286
+ }
147
287
  });
148
288
  useAnimatedReaction(() => rotation.get(), (currentRotation, previousRotation) => {
149
- if (currentRotation !== previousRotation) {
289
+ if (currentRotation !== previousRotation && hasRotationChangeListeners.get()) {
150
290
  scheduleOnRN(emitRotationChange, currentRotation, previousRotation);
151
291
  }
152
- scheduleOnRN(setIsRotated, currentRotation % 360 !== 0);
292
+ const currentIsRotated = currentRotation % 360 !== 0;
293
+ const previousIsRotated = (previousRotation ?? 0) % 360 !== 0;
294
+ if (previousRotation === null || currentIsRotated !== previousIsRotated) {
295
+ scheduleOnRN(setIsRotated, currentIsRotated);
296
+ }
153
297
  });
154
298
  useEffect(() => {
155
- let unsubscribe;
156
- const unsubscribeFromRegistry = registry.subscribeToManager(id, managerInstance => {
157
- setManager(managerInstance);
158
- unsubscribe = managerInstance?.subscribe(state => {
159
- pendingIndexRef.current = state.currentIndex;
160
- setCurrentIndex(state.currentIndex);
161
- });
299
+ return registry.subscribeToActiveTrigger(id, node => {
300
+ setActiveTriggerNode(node);
301
+ if (node) {
302
+ setIsTriggerOpening(true);
303
+ }
162
304
  });
163
- return () => {
164
- unsubscribeFromRegistry();
165
- unsubscribe?.();
166
- };
167
305
  }, [id]);
168
306
  useEffect(() => {
169
- return registry.subscribeToActiveTrigger(id, setActiveTriggerNode);
170
- }, [id]);
307
+ dataRef.current = data;
308
+ }, [data]);
171
309
  useEffect(() => {
172
- pendingIndexRef.current = initialIndex;
173
- if (!manager) {
174
- return;
175
- }
176
- manager.setDataLength(dataLength);
177
- manager.setEnableHorizontalSwipe(enableHorizontalSwipe);
178
- manager.setCurrentIndex(initialIndex);
179
- manager.setWidth(width + itemSpacing);
180
- manager.setHeight(height);
181
- manager.setZoomSharedValues(scale, translateX, translateY, maxZoomScale);
182
- manager.setResetTransformCallback(resetTransformState);
183
- manager.setRotation(rotation);
184
- manager.setEnableLoop(enableLoop);
185
- manager.notifyStateChange();
186
- }, [dataLength, enableHorizontalSwipe, initialIndex, manager, width, itemSpacing, maxZoomScale, enableLoop, scale, height, resetTransformState, translateX, translateY, rotation]);
310
+ isZoomedRef.current = isZoomed;
311
+ }, [isZoomed]);
187
312
  useEffect(() => {
188
- if (!manager || !listRef.current) {
189
- return;
190
- }
191
- manager.setListRef(listRef.current);
192
- }, [manager]);
313
+ isRotatedRef.current = isRotated;
314
+ }, [isRotated]);
193
315
  useEffect(() => {
194
- const hasDataLengthChanged = previousDataLengthRef.current !== dataLength;
195
- const hasValidInitialIndex = initialIndex >= 0 && initialIndex < dataLength;
196
- previousDataLengthRef.current = dataLength;
197
- translateY.set(0);
198
- translateX.set(0);
199
- scale.set(1);
200
- backdropOpacity.set(1);
201
- startScale.set(1);
202
- rotation.set(0);
203
- if (!hasValidInitialIndex || !hasDataLengthChanged && adjustedInitialIndex <= 0 || !listRef.current) {
204
- return;
205
- }
206
- return scheduleInitialScroll(() => {
207
- scrollTo(adjustedInitialIndex, false);
208
- });
209
- }, [adjustedInitialIndex, dataLength, initialIndex, translateY, backdropOpacity, translateX, scale, startScale, rotation, scrollTo]);
316
+ isPinchingRef.current = isPinching;
317
+ }, [isPinching]);
318
+ useEffect(() => {
319
+ onSingleTapRef.current = onSingleTap;
320
+ }, [onSingleTap]);
210
321
  useEffect(() => {
211
322
  onAnimationCompleteRef.current = triggerAnimation?.onAnimationComplete;
212
323
  }, [triggerAnimation?.onAnimationComplete]);
324
+ useLayoutEffect(() => {
325
+ const initialIndexChanged = previousInitialIndexRef.current !== initialIndex;
326
+ const shouldResolveInitialIndex = initialIndexChanged || !hasResolvedInitialIndexRef.current && dataLength > 0;
327
+ previousInitialIndexRef.current = initialIndex;
328
+ dataLengthRef.current = dataLength;
329
+ enableLoopRef.current = enableLoop;
330
+ hasResolvedInitialIndexRef.current = dataLength > 0;
331
+ const nextIndex = shouldResolveInitialIndex ? clampIndex(initialIndex, dataLength) : clampIndex(currentIndexRef.current, dataLength);
332
+ const nextVirtualIndex = getVirtualIndexForLogicalIndex(nextIndex, centerVirtualIndexRef.current, dataLength, enableLoop) ?? nextIndex;
333
+ const previousIndex = currentIndexRef.current;
334
+ const previousVirtualIndex = centerVirtualIndexRef.current;
335
+ const shouldSyncVirtualPage = isPageTransitioningRef.current || previousVirtualIndex !== nextVirtualIndex;
336
+ const shouldResetTransform = initialIndexChanged || previousIndex !== nextIndex || shouldSyncVirtualPage;
337
+ if (shouldSyncVirtualPage) {
338
+ snapToVirtualPage(nextVirtualIndex);
339
+ if (dataLength === 0) {
340
+ centerVirtualIndexRef.current = nextVirtualIndex;
341
+ setCenterVirtualIndex(nextVirtualIndex);
342
+ }
343
+ }
344
+ const shouldCommitIndexOutsidePaging = !shouldSyncVirtualPage || dataLength === 0;
345
+ if (shouldCommitIndexOutsidePaging && previousIndex !== nextIndex) {
346
+ commitCurrentIndex(nextIndex);
347
+ }
348
+ if (shouldCommitIndexOutsidePaging && previousIndex === nextIndex) {
349
+ managerRef.current?.notifyStateChange();
350
+ }
351
+ if (shouldResetTransform) {
352
+ resetTransformState();
353
+ }
354
+ }, [commitCurrentIndex, dataLength, enableLoop, initialIndex, isPageTransitioningRef, resetTransformState, snapToVirtualPage]);
213
355
  useEffect(() => {
214
- dataRef.current = data;
215
- }, [data]);
216
- useEffect(() => {
217
- managerRef.current = manager;
218
- }, [manager]);
356
+ return () => {
357
+ triggerRectRef.current = null;
358
+ clearPendingWebSingleTap();
359
+ };
360
+ }, [clearPendingWebSingleTap]);
219
361
  useEffect(() => {
220
- onSingleTapRef.current = onSingleTap;
221
- }, [onSingleTap]);
362
+ if (!autoPlay || dataLength <= 1 || isTriggerOpening || isZoomed || isRotated || isPinching || !enableLoop && currentIndex >= dataLength - 1) {
363
+ return;
364
+ }
365
+ const interval = Math.max(250, Math.floor(autoPlayInterval || 0));
366
+ if (!Number.isFinite(interval)) {
367
+ return;
368
+ }
369
+ const timer = setInterval(() => {
370
+ if (isPageTransitioningRef.current || isZoomedRef.current || isRotatedRef.current || isPinchingRef.current) {
371
+ return;
372
+ }
373
+ const nextIndex = currentIndexRef.current >= dataLengthRef.current - 1 ? enableLoopRef.current ? 0 : null : currentIndexRef.current + 1;
374
+ if (nextIndex === null) {
375
+ return;
376
+ }
377
+ navigateToIndex(nextIndex);
378
+ }, interval);
379
+ return () => clearInterval(timer);
380
+ }, [autoPlay, autoPlayInterval, currentIndex, dataLength, enableLoop, isPinching, isRotated, isTriggerOpening, isZoomed, isPageTransitioningRef, navigateToIndex]);
222
381
  useEffect(() => {
223
382
  if (shouldStartTriggerAnimation && triggerRectRef.current) {
224
383
  const startX = triggerRectRef.current.x + triggerRectRef.current.width / 2 - width / 2;
@@ -230,7 +389,7 @@ export const useGestureViewer = ({
230
389
  triggerOpacity.set(0);
231
390
  triggerScale.set(withTiming(1, animationConfig, finished => {
232
391
  if (finished) {
233
- scheduleOnRN(onAnimationComplete);
392
+ scheduleOnRN(finishTriggerOpening);
234
393
  }
235
394
  }));
236
395
  triggerTranslateX.set(withTiming(0, animationConfig));
@@ -242,9 +401,15 @@ export const useGestureViewer = ({
242
401
  }));
243
402
  setShouldStartTriggerAnimation(false);
244
403
  }
245
- }, [shouldStartTriggerAnimation, animationConfig, width, height, triggerOpacity, triggerScale, triggerTranslateX, triggerTranslateY, onAnimationComplete]);
404
+ }, [shouldStartTriggerAnimation, animationConfig, width, height, triggerOpacity, triggerScale, triggerTranslateX, triggerTranslateY, finishTriggerOpening]);
246
405
  useEffect(() => {
247
- if (!activeTriggerNode || typeof activeTriggerNode.measure !== 'function') {
406
+ if (!activeTriggerNode) {
407
+ return;
408
+ }
409
+ if (typeof activeTriggerNode.measure !== 'function') {
410
+ setIsTriggerOpening(false);
411
+ triggerOpacity.set(1);
412
+ registry.clearActiveTriggerNode(id);
248
413
  return;
249
414
  }
250
415
  activeTriggerNode.measure((_x, _y, measuredWidth, measuredHeight, pageX, pageY) => {
@@ -255,6 +420,8 @@ export const useGestureViewer = ({
255
420
  y: pageY
256
421
  };
257
422
  if (!isValidTriggerRect(nextTriggerRect)) {
423
+ setIsTriggerOpening(false);
424
+ triggerOpacity.set(1);
258
425
  registry.clearActiveTriggerNode(id);
259
426
  return;
260
427
  }
@@ -263,12 +430,7 @@ export const useGestureViewer = ({
263
430
  setShouldStartTriggerAnimation(true);
264
431
  registry.clearActiveTriggerNode(id);
265
432
  });
266
- }, [activeTriggerNode, id, isValidTriggerRect, triggerOpacity]);
267
- useEffect(() => {
268
- return () => {
269
- triggerRectRef.current = null;
270
- };
271
- }, []);
433
+ }, [activeTriggerNode, id, triggerOpacity]);
272
434
  const animateDismissToRect = useCallback(rect => {
273
435
  const endX = rect.x + rect.width / 2 - width / 2;
274
436
  const endY = rect.y + rect.height / 2 - height / 2;
@@ -288,6 +450,9 @@ export const useGestureViewer = ({
288
450
  }
289
451
  }, [onDismiss]);
290
452
  const handleDismiss = useCallback(() => {
453
+ if (isPageTransitioningRef.current) {
454
+ return;
455
+ }
291
456
  onDismissStart?.();
292
457
  const dismissTargetIndex = pendingIndexRef.current;
293
458
  const indexedTriggerNode = registry.getIndexedTriggerNode(id, dismissTargetIndex);
@@ -316,31 +481,150 @@ export const useGestureViewer = ({
316
481
  return;
317
482
  }
318
483
  dismissWithoutTrigger();
319
- }, [animateDismissToRect, dismissWithoutTrigger, id, isValidTriggerRect, onDismissStart]);
484
+ }, [animateDismissToRect, dismissWithoutTrigger, id, isPageTransitioningRef, onDismissStart]);
320
485
  const dismissGesture = useMemo(() => {
321
- const canDismiss = !isZoomed && dismissOptions.enabled;
322
- return Gesture.Pan().minDistance(10).averageTouches(true).activeCursor('grabbing').activeOffsetY([-10, 10]).failOffsetX([-10, 10]).withRef(dismissGestureRef).enabled(canDismiss).onUpdate(event => {
486
+ const canDismiss = !isTriggerOpening && !isPinching && !isZoomed && dismissOptions.enabled;
487
+ const resetDismissTranslation = () => {
488
+ 'worklet';
489
+
490
+ cancelAnimation(translateY);
491
+ translateY.set(0);
492
+ };
493
+ return Gesture.Pan().minDistance(10).maxPointers(1).averageTouches(true).activeCursor('grabbing').activeOffsetY([-10, 10]).failOffsetX([-10, 10]).withRef(dismissGestureRef).enabled(canDismiss).onTouchesDown((event, stateManager) => {
494
+ if (event.numberOfTouches === 1) {
495
+ nativeInteractionHadMultipleTouches.set(false);
496
+ return;
497
+ }
498
+ if (event.numberOfTouches > 1) {
499
+ nativeInteractionHadMultipleTouches.set(true);
500
+ suppressNativeTap.set(true);
501
+ resetDismissTranslation();
502
+ stateManager.fail();
503
+ }
504
+ }).onStart(() => {
505
+ suppressNativeTap.set(true);
506
+ }).onUpdate(event => {
507
+ if (nativeInteractionHadMultipleTouches.get() || pageTransitionLocked.get()) {
508
+ return;
509
+ }
323
510
  translateY.set(event.translationY / dismissOptions.resistance);
324
511
  }).onEnd(event => {
512
+ if (nativeInteractionHadMultipleTouches.get()) {
513
+ resetDismissTranslation();
514
+ return;
515
+ }
516
+ if (pageTransitionLocked.get()) {
517
+ resetDismissTranslation();
518
+ return;
519
+ }
325
520
  if (canDismiss && shouldDismissByDirection(event.translationY, dismissOptions.threshold, dismissOptions.direction)) {
326
521
  scheduleOnRN(handleDismiss);
327
522
  return;
328
523
  }
329
- translateY.set(withSpring(0, {
330
- damping: 50,
331
- energyThreshold: 6e-9,
332
- mass: 4,
333
- overshootClamping: false,
334
- stiffness: 600
335
- }));
524
+ translateY.set(withSpring(0, PAGE_SPRING_CONFIG));
525
+ }).onFinalize((_event, success) => {
526
+ if ((!success || nativeInteractionHadMultipleTouches.get()) && scale.get() <= 1) {
527
+ resetDismissTranslation();
528
+ }
336
529
  });
337
- }, [translateY, dismissOptions, handleDismiss, isZoomed]);
530
+ }, [dismissOptions, handleDismiss, isPinching, isTriggerOpening, isZoomed, nativeInteractionHadMultipleTouches, pageTransitionLocked, scale, suppressNativeTap, translateY]);
531
+ const getCurrentTapTarget = useCallback(() => {
532
+ const index = pendingIndexRef.current;
533
+ if (index < 0 || index >= dataRef.current.length || !(index in dataRef.current)) {
534
+ return null;
535
+ }
536
+ return {
537
+ index,
538
+ item: dataRef.current[index]
539
+ };
540
+ }, []);
541
+ const isWebInteractionLocked = useCallback(() => isPageTransitioningRef.current || pageTransitionLocked.get(), [isPageTransitioningRef, pageTransitionLocked]);
542
+ const emitSingleTap = useCallback((x, y, tapTarget) => {
543
+ if (isPageTransitioningRef.current) {
544
+ return;
545
+ }
546
+ const target = tapTarget ?? getCurrentTapTarget();
547
+ if (!target) {
548
+ return;
549
+ }
550
+ const {
551
+ index: targetIndex,
552
+ item: targetItem
553
+ } = target;
554
+ if (targetIndex < 0) {
555
+ return;
556
+ }
557
+ managerRef.current?.emitTap({
558
+ kind: 'single',
559
+ x,
560
+ y,
561
+ index: targetIndex
562
+ });
563
+ onSingleTapRef.current?.({
564
+ x,
565
+ y,
566
+ index: targetIndex,
567
+ item: targetItem
568
+ });
569
+ }, [getCurrentTapTarget, isPageTransitioningRef]);
570
+ const singleTapGesture = useMemo(() => Gesture.Tap().enabled(enableNativeTapGestures).numberOfTaps(1).maxDistance(NATIVE_TAP_MAX_DISTANCE).onTouchesDown((event, stateManager) => {
571
+ if (event.numberOfTouches === 1) {
572
+ nativeInteractionHadMultipleTouches.set(false);
573
+ suppressNativeTap.set(false);
574
+ return;
575
+ }
576
+ nativeInteractionHadMultipleTouches.set(true);
577
+ suppressNativeTap.set(true);
578
+ stateManager.fail();
579
+ }).onEnd((event, success) => {
580
+ if (!success || pageTransitionLocked.get() || suppressNativeTap.get()) {
581
+ return;
582
+ }
583
+ scheduleOnRN(emitSingleTap, event.x, event.y);
584
+ }), [emitSingleTap, nativeInteractionHadMultipleTouches, pageTransitionLocked, suppressNativeTap]);
585
+ const doubleTapGesture = useMemo(() => Gesture.Tap().enabled(enableDoubleTapZoom && enableNativeTapGestures).numberOfTaps(2).maxDistance(NATIVE_TAP_MAX_DISTANCE).onTouchesDown((event, stateManager) => {
586
+ if (event.numberOfTouches === 1) {
587
+ return;
588
+ }
589
+ nativeInteractionHadMultipleTouches.set(true);
590
+ suppressNativeTap.set(true);
591
+ stateManager.fail();
592
+ }).onEnd(event => {
593
+ if (pageTransitionLocked.get() || suppressNativeTap.get()) {
594
+ return;
595
+ }
596
+ applyTapZoomAtPoint({
597
+ x: event.x,
598
+ y: event.y,
599
+ width,
600
+ height,
601
+ maxZoomScale,
602
+ scale,
603
+ translateX,
604
+ translateY
605
+ });
606
+ }), [enableDoubleTapZoom, height, maxZoomScale, nativeInteractionHadMultipleTouches, pageTransitionLocked, scale, suppressNativeTap, translateX, translateY, width]);
607
+ const tapGesture = useMemo(() => Gesture.Exclusive(doubleTapGesture, singleTapGesture), [doubleTapGesture, singleTapGesture]);
338
608
  const zoomPinchGesture = useMemo(() => Gesture.Pinch().enabled(enablePinchZoom).onTouchesDown(event => {
339
609
  if (event.numberOfTouches === 2) {
610
+ nativeInteractionHadMultipleTouches.set(true);
611
+ suppressNativeTap.set(true);
340
612
  scheduleOnRN(setIsPinching, true);
341
613
  }
342
614
  }).onStart(event => {
343
- startScale.set(scale.get());
615
+ if (pageTransitionLocked.get()) {
616
+ return;
617
+ }
618
+ const currentScale = scale.get();
619
+ nativeInteractionHadMultipleTouches.set(true);
620
+ suppressNativeTap.set(true);
621
+ startScale.set(currentScale);
622
+ if (currentScale <= 1) {
623
+ cancelAnimation(translateX);
624
+ cancelAnimation(translateY);
625
+ translateX.set(0);
626
+ translateY.set(0);
627
+ }
344
628
  initialTranslateX.set(translateX.get());
345
629
  initialTranslateY.set(translateY.get());
346
630
  startFocalX.set(event.focalX);
@@ -349,6 +633,9 @@ export const useGestureViewer = ({
349
633
  lastFocalY.set(event.focalY);
350
634
  hasActiveFocal.set(false);
351
635
  }).onUpdate(event => {
636
+ if (pageTransitionLocked.get()) {
637
+ return;
638
+ }
352
639
  const initialScale = startScale.get();
353
640
  const newScale = initialScale * event.scale;
354
641
  scale.set(newScale);
@@ -402,6 +689,9 @@ export const useGestureViewer = ({
402
689
  translateX.set(constrainedTranslateX);
403
690
  translateY.set(constrainedTranslateY);
404
691
  }).onEnd(() => {
692
+ if (pageTransitionLocked.get()) {
693
+ return;
694
+ }
405
695
  const currentScale = scale.get();
406
696
  if (currentScale > maxZoomScale) {
407
697
  scale.set(withTiming(maxZoomScale, {
@@ -447,11 +737,29 @@ export const useGestureViewer = ({
447
737
  }).onFinalize(() => {
448
738
  hasActiveFocal.set(false);
449
739
  scheduleOnRN(setIsPinching, false);
450
- }), [scale, enablePinchZoom, maxZoomScale, translateX, translateY, startScale, initialTranslateX, initialTranslateY, width, height, constrainTranslation, lastFocalX, lastFocalY, startFocalX, startFocalY, hasActiveFocal]);
451
- const zoomPanGesture = useMemo(() => Gesture.Pan().enabled(enablePanWhenZoomed && isZoomed).activeCursor('grabbing').averageTouches(true).onBegin(() => {
740
+ }), [scale, enablePinchZoom, maxZoomScale, translateX, translateY, startScale, initialTranslateX, initialTranslateY, width, height, constrainTranslation, lastFocalX, lastFocalY, startFocalX, startFocalY, hasActiveFocal, nativeInteractionHadMultipleTouches, pageTransitionLocked, suppressNativeTap]);
741
+ const zoomPanGesture = useMemo(() => Gesture.Pan().enabled(enablePanWhenZoomed && !isPinching && isZoomed).maxPointers(1).activeCursor('grabbing').averageTouches(true).onTouchesDown((event, stateManager) => {
742
+ if (event.numberOfTouches === 1) {
743
+ nativeInteractionHadMultipleTouches.set(false);
744
+ return;
745
+ }
746
+ if (event.numberOfTouches > 1) {
747
+ nativeInteractionHadMultipleTouches.set(true);
748
+ suppressNativeTap.set(true);
749
+ stateManager.fail();
750
+ }
751
+ }).onBegin(() => {
752
+ if (nativeInteractionHadMultipleTouches.get() || pageTransitionLocked.get()) {
753
+ return;
754
+ }
452
755
  initialTranslateX.set(translateX.get());
453
756
  initialTranslateY.set(translateY.get());
757
+ }).onStart(() => {
758
+ suppressNativeTap.set(true);
454
759
  }).onUpdate(event => {
760
+ if (nativeInteractionHadMultipleTouches.get() || pageTransitionLocked.get()) {
761
+ return;
762
+ }
455
763
  const currentScale = scale.get();
456
764
  if (currentScale > 1) {
457
765
  const newTranslateX = initialTranslateX.get() + event.translationX;
@@ -467,50 +775,22 @@ export const useGestureViewer = ({
467
775
  translateX.set(constrainedTranslateX);
468
776
  translateY.set(constrainedTranslateY);
469
777
  }
470
- }), [translateX, translateY, enablePanWhenZoomed, isZoomed, scale, initialTranslateX, initialTranslateY, constrainTranslation]);
471
- const emitSingleTap = useCallback((x, y) => {
472
- const index = pendingIndexRef.current;
473
- const currentData = dataRef.current;
474
- if (index < 0 || index >= currentData.length) {
475
- return;
476
- }
477
- managerRef.current?.emitTap({
478
- kind: 'single',
479
- x,
480
- y,
481
- index
482
- });
483
- const item = currentData[index];
484
- if (item === undefined) {
485
- return;
486
- }
487
- onSingleTapRef.current?.({
488
- x,
489
- y,
490
- index,
491
- item
492
- });
493
- }, []);
494
- const singleTapGesture = useMemo(() => Gesture.Tap().enabled(Platform.OS !== 'web').numberOfTaps(1).onEnd((event, success) => {
495
- if (!success) {
496
- return;
497
- }
498
- scheduleOnRN(emitSingleTap, event.x, event.y);
499
- }), [emitSingleTap]);
500
- const doubleTapGesture = useMemo(() => Gesture.Tap().enabled(enableDoubleTapZoom && Platform.OS !== 'web').numberOfTaps(2).onEnd(event => {
501
- applyTapZoomAtPoint({
502
- x: event.x,
503
- y: event.y,
504
- width,
505
- height,
506
- maxZoomScale,
507
- scale,
508
- translateX,
509
- translateY
510
- });
511
- }), [enableDoubleTapZoom, height, maxZoomScale, scale, translateX, translateY, width]);
512
- const tapGesture = useMemo(() => Gesture.Exclusive(doubleTapGesture, singleTapGesture), [doubleTapGesture, singleTapGesture]);
513
- const zoomGesture = useMemo(() => Gesture.Race(zoomPinchGesture, Gesture.Exclusive(zoomPanGesture, tapGesture)), [zoomPinchGesture, zoomPanGesture, tapGesture]);
778
+ }), [translateX, translateY, enablePanWhenZoomed, isPinching, isZoomed, scale, initialTranslateX, initialTranslateY, constrainTranslation, nativeInteractionHadMultipleTouches, pageTransitionLocked, suppressNativeTap]);
779
+ const zoomGesture = useMemo(() => Gesture.Exclusive(zoomPanGesture, tapGesture), [zoomPanGesture, tapGesture]);
780
+ const onWebClick = useWebClickHandler({
781
+ clearPendingWebSingleTap,
782
+ emitSingleTap,
783
+ enableDoubleTapZoom,
784
+ getCurrentTapTarget,
785
+ height,
786
+ maxZoomScale,
787
+ scale,
788
+ scheduleWebSingleTap,
789
+ isInteractionLocked: isWebInteractionLocked,
790
+ translateX,
791
+ translateY,
792
+ width
793
+ });
514
794
  const animatedStyle = useAnimatedStyle(() => ({
515
795
  opacity: triggerOpacity.get(),
516
796
  transform: [{
@@ -542,56 +822,24 @@ export const useGestureViewer = ({
542
822
  opacity: baseOpacity * dismissOpacity
543
823
  };
544
824
  }, [dismissOptions.direction, dismissOptions.fadeBackdrop]);
545
- const nativeScrollGesture = useMemo(() => {
546
- return Gesture.Native().requireExternalGestureToFail(dismissGestureRef);
547
- }, []);
548
- const {
549
- activeListIndex,
550
- onMomentumScrollEnd,
551
- onScroll,
552
- onScrollBeginDrag,
553
- onWebClick
554
- } = useGestureViewerPaging({
555
- adjustedInitialIndex,
556
- autoPlay,
557
- autoPlayInterval,
558
- currentIndex,
559
- dataLength,
560
- enableDoubleTapZoom,
561
- enableHorizontalSwipe,
562
- enableLoop,
563
- height,
564
- isRotated,
565
- isZoomed,
566
- itemSpacing,
567
- manager,
568
- maxZoomScale,
569
- onSingleTap: emitSingleTap,
570
- scale,
571
- scrollTo,
572
- syncCurrentIndex,
573
- syncPendingIndex,
574
- translateX,
575
- translateY,
576
- width
577
- });
578
825
  return {
579
- activeListIndex,
580
826
  animatedStyle,
581
827
  backdropStyle,
828
+ centerVirtualIndex: renderCenterVirtualIndex,
829
+ currentIndex: renderCurrentIndex,
582
830
  dataLength,
583
831
  dismissGesture,
584
832
  handleDismiss,
833
+ horizontalPagingGesture,
585
834
  isPinching,
586
835
  isRotated,
587
836
  isZoomed,
588
- listRef,
589
- nativeScrollGesture,
590
837
  onWebClick,
591
- onMomentumScrollEnd,
592
- onScroll,
593
- onScrollBeginDrag,
594
- zoomGesture
838
+ pageStride,
839
+ renderWindowSlots,
840
+ visualPage,
841
+ zoomGesture,
842
+ zoomPinchGesture
595
843
  };
596
844
  };
597
845
  //# sourceMappingURL=useGestureViewer.js.map