react-native-gesture-image-viewer 2.3.3 → 3.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/README.md +5 -3
  2. package/lib/module/GestureViewer.js +88 -140
  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/gestureViewerPaging.js +66 -0
  9. package/lib/module/gestureViewerPaging.js.map +1 -0
  10. package/lib/module/gestureViewerRenderWindow.js +29 -0
  11. package/lib/module/gestureViewerRenderWindow.js.map +1 -0
  12. package/lib/module/getWebContentProps.js +6 -0
  13. package/lib/module/getWebContentProps.js.map +1 -0
  14. package/lib/module/getWebContentProps.web.js +8 -0
  15. package/lib/module/getWebContentProps.web.js.map +1 -0
  16. package/lib/module/index.js.map +1 -1
  17. package/lib/module/platformTapGestures.js +4 -0
  18. package/lib/module/platformTapGestures.js.map +1 -0
  19. package/lib/module/platformTapGestures.web.js +4 -0
  20. package/lib/module/platformTapGestures.web.js.map +1 -0
  21. package/lib/module/renderWindow.js +160 -0
  22. package/lib/module/renderWindow.js.map +1 -0
  23. package/lib/module/useGestureViewer.js +386 -200
  24. package/lib/module/useGestureViewer.js.map +1 -1
  25. package/lib/module/useGestureViewerController.js +3 -3
  26. package/lib/module/useGestureViewerController.js.map +1 -1
  27. package/lib/module/useGestureViewerManagerBridge.js +85 -0
  28. package/lib/module/useGestureViewerManagerBridge.js.map +1 -0
  29. package/lib/module/useGestureViewerPaging.js +147 -65
  30. package/lib/module/useGestureViewerPaging.js.map +1 -1
  31. package/lib/module/useWebClickHandler.js +7 -0
  32. package/lib/module/useWebClickHandler.js.map +1 -0
  33. package/lib/module/useWebClickHandler.web.js +85 -0
  34. package/lib/module/useWebClickHandler.web.js.map +1 -0
  35. package/lib/module/useWebSingleTapTimer.js +12 -0
  36. package/lib/module/useWebSingleTapTimer.js.map +1 -0
  37. package/lib/module/useWebSingleTapTimer.web.js +25 -0
  38. package/lib/module/useWebSingleTapTimer.web.js.map +1 -0
  39. package/lib/module/utils/index.js +0 -77
  40. package/lib/module/utils/index.js.map +1 -1
  41. package/lib/module/utils/tapZoom.js +4 -3
  42. package/lib/module/utils/tapZoom.js.map +1 -1
  43. package/lib/typescript/src/GestureViewer.d.ts +1 -1
  44. package/lib/typescript/src/GestureViewer.d.ts.map +1 -1
  45. package/lib/typescript/src/GestureViewerManager.d.ts +20 -20
  46. package/lib/typescript/src/GestureViewerManager.d.ts.map +1 -1
  47. package/lib/typescript/src/gestureViewerAnimation.d.ts +15 -0
  48. package/lib/typescript/src/gestureViewerAnimation.d.ts.map +1 -0
  49. package/lib/typescript/src/gestureViewerPaging.d.ts +13 -0
  50. package/lib/typescript/src/gestureViewerPaging.d.ts.map +1 -0
  51. package/lib/typescript/src/gestureViewerRenderWindow.d.ts +15 -0
  52. package/lib/typescript/src/gestureViewerRenderWindow.d.ts.map +1 -0
  53. package/lib/typescript/src/getWebContentProps.d.ts +3 -0
  54. package/lib/typescript/src/getWebContentProps.d.ts.map +1 -0
  55. package/lib/typescript/src/getWebContentProps.web.d.ts +5 -0
  56. package/lib/typescript/src/getWebContentProps.web.d.ts.map +1 -0
  57. package/lib/typescript/src/index.d.ts +1 -1
  58. package/lib/typescript/src/index.d.ts.map +1 -1
  59. package/lib/typescript/src/platformTapGestures.d.ts +2 -0
  60. package/lib/typescript/src/platformTapGestures.d.ts.map +1 -0
  61. package/lib/typescript/src/platformTapGestures.web.d.ts +2 -0
  62. package/lib/typescript/src/platformTapGestures.web.d.ts.map +1 -0
  63. package/lib/typescript/src/renderWindow.d.ts +44 -0
  64. package/lib/typescript/src/renderWindow.d.ts.map +1 -0
  65. package/lib/typescript/src/types.d.ts +45 -52
  66. package/lib/typescript/src/types.d.ts.map +1 -1
  67. package/lib/typescript/src/useGestureViewer.d.ts +9 -8
  68. package/lib/typescript/src/useGestureViewer.d.ts.map +1 -1
  69. package/lib/typescript/src/useGestureViewerController.d.ts +1 -1
  70. package/lib/typescript/src/useGestureViewerManagerBridge.d.ts +28 -0
  71. package/lib/typescript/src/useGestureViewerManagerBridge.d.ts.map +1 -0
  72. package/lib/typescript/src/useGestureViewerPaging.d.ts +31 -2
  73. package/lib/typescript/src/useGestureViewerPaging.d.ts.map +1 -1
  74. package/lib/typescript/src/useWebClickHandler.d.ts +35 -0
  75. package/lib/typescript/src/useWebClickHandler.d.ts.map +1 -0
  76. package/lib/typescript/src/useWebClickHandler.web.d.ts +36 -0
  77. package/lib/typescript/src/useWebClickHandler.web.d.ts.map +1 -0
  78. package/lib/typescript/src/useWebSingleTapTimer.d.ts +6 -0
  79. package/lib/typescript/src/useWebSingleTapTimer.d.ts.map +1 -0
  80. package/lib/typescript/src/useWebSingleTapTimer.web.d.ts +6 -0
  81. package/lib/typescript/src/useWebSingleTapTimer.web.d.ts.map +1 -0
  82. package/lib/typescript/src/utils/index.d.ts +0 -20
  83. package/lib/typescript/src/utils/index.d.ts.map +1 -1
  84. package/lib/typescript/src/utils/tapZoom.d.ts.map +1 -1
  85. package/package.json +1 -1
  86. package/src/GestureViewer.tsx +104 -191
  87. package/src/GestureViewerManager.ts +77 -122
  88. package/src/gestureViewerAnimation.ts +18 -0
  89. package/src/gestureViewerPaging.ts +116 -0
  90. package/src/gestureViewerRenderWindow.ts +56 -0
  91. package/src/getWebContentProps.ts +5 -0
  92. package/src/getWebContentProps.web.ts +5 -0
  93. package/src/index.tsx +1 -0
  94. package/src/platformTapGestures.ts +1 -0
  95. package/src/platformTapGestures.web.ts +1 -0
  96. package/src/renderWindow.ts +253 -0
  97. package/src/types.ts +44 -99
  98. package/src/useGestureViewer.ts +557 -258
  99. package/src/useGestureViewerController.ts +3 -3
  100. package/src/useGestureViewerManagerBridge.ts +118 -0
  101. package/src/useGestureViewerPaging.ts +277 -98
  102. package/src/useWebClickHandler.ts +44 -0
  103. package/src/useWebClickHandler.web.ts +146 -0
  104. package/src/useWebSingleTapTimer.ts +10 -0
  105. package/src/useWebSingleTapTimer.web.ts +30 -0
  106. package/src/utils/index.ts +0 -102
  107. package/src/utils/tapZoom.ts +4 -3
  108. package/lib/module/WebPagingFixStyle.js +0 -25
  109. package/lib/module/WebPagingFixStyle.js.map +0 -1
  110. package/lib/module/scheduleInitialScroll.js +0 -21
  111. package/lib/module/scheduleInitialScroll.js.map +0 -1
  112. package/lib/module/useGestureViewerPaging.types.js +0 -4
  113. package/lib/module/useGestureViewerPaging.types.js.map +0 -1
  114. package/lib/module/useGestureViewerPaging.web.js +0 -275
  115. package/lib/module/useGestureViewerPaging.web.js.map +0 -1
  116. package/lib/module/utils/FlashList.js +0 -9
  117. package/lib/module/utils/FlashList.js.map +0 -1
  118. package/lib/module/utils/webScroll.js +0 -70
  119. package/lib/module/utils/webScroll.js.map +0 -1
  120. package/lib/typescript/src/WebPagingFixStyle.d.ts +0 -6
  121. package/lib/typescript/src/WebPagingFixStyle.d.ts.map +0 -1
  122. package/lib/typescript/src/scheduleInitialScroll.d.ts +0 -3
  123. package/lib/typescript/src/scheduleInitialScroll.d.ts.map +0 -1
  124. package/lib/typescript/src/useGestureViewerPaging.types.d.ts +0 -41
  125. package/lib/typescript/src/useGestureViewerPaging.types.d.ts.map +0 -1
  126. package/lib/typescript/src/useGestureViewerPaging.web.d.ts +0 -3
  127. package/lib/typescript/src/useGestureViewerPaging.web.d.ts.map +0 -1
  128. package/lib/typescript/src/utils/FlashList.d.ts +0 -2
  129. package/lib/typescript/src/utils/FlashList.d.ts.map +0 -1
  130. package/lib/typescript/src/utils/webScroll.d.ts +0 -18
  131. package/lib/typescript/src/utils/webScroll.d.ts.map +0 -1
  132. package/src/WebPagingFixStyle.tsx +0 -25
  133. package/src/scheduleInitialScroll.ts +0 -36
  134. package/src/useGestureViewerPaging.types.ts +0 -41
  135. package/src/useGestureViewerPaging.web.ts +0 -434
  136. package/src/utils/FlashList.ts +0 -7
  137. package/src/utils/webScroll.ts +0 -96
@@ -1,8 +1,9 @@
1
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
- import { Platform, type View, useWindowDimensions } from 'react-native';
1
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
2
+ import { type View, useWindowDimensions } from 'react-native';
3
3
  import { Gesture, type GestureType } from 'react-native-gesture-handler';
4
4
  import {
5
5
  Easing,
6
+ cancelAnimation,
6
7
  interpolate,
7
8
  useAnimatedReaction,
8
9
  useAnimatedStyle,
@@ -12,28 +13,46 @@ import {
12
13
  } from 'react-native-reanimated';
13
14
  import { scheduleOnRN } from 'react-native-worklets';
14
15
 
16
+ import {
17
+ PAGE_SPRING_CONFIG,
18
+ SWIPE_THRESHOLD_RATIO,
19
+ SWIPE_VELOCITY_THRESHOLD,
20
+ } from './gestureViewerAnimation';
15
21
  import type GestureViewerManager from './GestureViewerManager';
22
+ import { normalizeHorizontalSwipeThreshold } from './gestureViewerPaging';
16
23
  import { registry } from './GestureViewerRegistry';
17
- import { scheduleInitialScroll } from './scheduleInitialScroll';
24
+ import { resolveGestureViewerRenderWindow } from './gestureViewerRenderWindow';
25
+ import { enableNativeTapGestures } from './platformTapGestures';
26
+ import {
27
+ type NavigationOptions,
28
+ getLogicalIndex,
29
+ getPageStride,
30
+ getVirtualIndexForLogicalIndex,
31
+ normalizePageSpacing,
32
+ clampIndex,
33
+ resolveNavigation,
34
+ shouldRunNavigationDuringTransition,
35
+ } from './renderWindow';
18
36
  import type { GestureViewerProps, TriggerRect } from './types';
37
+ import { useGestureViewerManagerBridge } from './useGestureViewerManagerBridge';
19
38
  import { useGestureViewerPaging } from './useGestureViewerPaging';
20
- import { createBoundsConstraint, createScrollAction } from './utils';
39
+ import { type EmitSingleTap, useWebClickHandler } from './useWebClickHandler';
40
+ import { useWebSingleTapTimer } from './useWebSingleTapTimer';
41
+ import { createBoundsConstraint } from './utils';
21
42
  import { getDismissDistance, shouldDismissByDirection } from './utils/dismiss';
22
43
  import { applyTapZoomAtPoint } from './utils/tapZoom';
23
44
  import { calculateFocalPointTranslation, shouldAcceptFocalPoint } from './utils/zoom';
24
45
 
25
- type UseGestureViewerProps<ItemT, LC> = Omit<
26
- GestureViewerProps<ItemT, LC>,
27
- | 'renderItem'
28
- | 'renderContainer'
29
- | 'ListComponent'
30
- | 'listProps'
31
- | 'containerStyle'
32
- | 'backdropStyle'
33
- | 'enableSnapMode'
46
+ const isValidTriggerRect = (rect: TriggerRect | null): rect is TriggerRect => {
47
+ return !!rect && rect.width > 0 && rect.height > 0;
48
+ };
49
+
50
+ type UseGestureViewerProps<ItemT> = Omit<
51
+ GestureViewerProps<ItemT>,
52
+ 'renderItem' | 'renderContainer' | 'containerStyle' | 'backdropStyle'
34
53
  >;
35
54
 
36
- export const useGestureViewer = <ItemT, LC>({
55
+ export const useGestureViewer = <ItemT>({
37
56
  data,
38
57
  initialIndex = 0,
39
58
  onDismiss,
@@ -42,21 +61,35 @@ export const useGestureViewer = <ItemT, LC>({
42
61
  dismiss,
43
62
  enableDoubleTapZoom = true,
44
63
  enablePinchZoom = true,
45
- enableHorizontalSwipe = true,
64
+ horizontalSwipe,
46
65
  enablePanWhenZoomed = true,
47
66
  enableLoop = false,
48
67
  maxZoomScale = 2,
49
- itemSpacing = 0,
68
+ pageSpacing = 0,
69
+ windowSize = 3,
50
70
  height: customHeight,
51
71
  id = 'default',
52
72
  onDismissStart,
53
73
  triggerAnimation,
54
74
  autoPlay = false,
55
75
  autoPlayInterval = 3000,
56
- }: UseGestureViewerProps<ItemT, LC>) => {
76
+ }: UseGestureViewerProps<ItemT>) => {
57
77
  const { width: screenWidth, height: screenHeight } = useWindowDimensions();
58
78
  const width = customWidth || screenWidth;
59
79
  const height = customHeight || screenHeight;
80
+ const dataLength = data?.length || 0;
81
+ const normalizedPageSpacing = normalizePageSpacing(pageSpacing);
82
+ const pageStride = getPageStride(width, normalizedPageSpacing);
83
+ const initialCurrentIndex = clampIndex(initialIndex, dataLength);
84
+ const horizontalSwipeEnabled = horizontalSwipe?.enabled ?? true;
85
+ const horizontalSwipeDistanceThresholdRatio = normalizeHorizontalSwipeThreshold(
86
+ horizontalSwipe?.distanceThresholdRatio,
87
+ SWIPE_THRESHOLD_RATIO,
88
+ );
89
+ const horizontalSwipeVelocityThreshold = normalizeHorizontalSwipeThreshold(
90
+ horizontalSwipe?.velocityThreshold,
91
+ SWIPE_VELOCITY_THRESHOLD,
92
+ );
60
93
 
61
94
  const dismissGestureRef = useRef<GestureType>(undefined);
62
95
 
@@ -64,21 +97,30 @@ export const useGestureViewer = <ItemT, LC>({
64
97
  const [isRotated, setIsRotated] = useState(false);
65
98
  const [isPinching, setIsPinching] = useState(false);
66
99
  const [shouldStartTriggerAnimation, setShouldStartTriggerAnimation] = useState(false);
67
- const [manager, setManager] = useState<GestureViewerManager | null>(null);
68
- const [currentIndex, setCurrentIndex] = useState(initialIndex);
69
- const [activeTriggerNode, setActiveTriggerNode] = useState<View | null>(null);
100
+ const [currentIndex, setCurrentIndex] = useState(initialCurrentIndex);
101
+ const [centerVirtualIndex, setCenterVirtualIndex] = useState(initialCurrentIndex);
102
+ const [activeTriggerNode, setActiveTriggerNode] = useState<View | null>(() =>
103
+ registry.getActiveTriggerNode(id),
104
+ );
105
+ const [isTriggerOpening, setIsTriggerOpening] = useState(
106
+ () => !!registry.getActiveTriggerNode(id),
107
+ );
70
108
 
71
- const listRef = useRef<any>(null);
72
109
  const triggerRectRef = useRef<TriggerRect | null>(null);
73
- const pendingIndexRef = useRef(initialIndex);
110
+ const pendingIndexRef = useRef(initialCurrentIndex);
111
+ const currentIndexRef = useRef(initialCurrentIndex);
112
+ const centerVirtualIndexRef = useRef(initialCurrentIndex);
113
+ const previousInitialIndexRef = useRef(initialIndex);
114
+ const hasResolvedInitialIndexRef = useRef(dataLength > 0);
74
115
  const onAnimationCompleteRef = useRef(triggerAnimation?.onAnimationComplete);
75
116
  const onSingleTapRef = useRef(onSingleTap);
76
117
  const dataRef = useRef(data);
77
- const managerRef = useRef(manager);
78
-
79
- const isValidTriggerRect = useCallback((rect: TriggerRect | null): rect is TriggerRect => {
80
- return !!rect && rect.width > 0 && rect.height > 0;
81
- }, []);
118
+ const dataLengthRef = useRef(dataLength);
119
+ const enableLoopRef = useRef(enableLoop);
120
+ const managerRef = useRef<GestureViewerManager | null>(null);
121
+ const isZoomedRef = useRef(isZoomed);
122
+ const isRotatedRef = useRef(isRotated);
123
+ const isPinchingRef = useRef(isPinching);
82
124
 
83
125
  const initialTranslateY = useSharedValue(0);
84
126
  const initialTranslateX = useSharedValue(0);
@@ -87,21 +129,22 @@ export const useGestureViewer = <ItemT, LC>({
87
129
  const translateY = useSharedValue(0);
88
130
  const translateX = useSharedValue(0);
89
131
  const scale = useSharedValue(1);
90
- const backdropOpacity = useSharedValue(1);
91
132
  const rotation = useSharedValue(0);
92
133
 
93
134
  const triggerScale = useSharedValue(1);
94
135
  const triggerTranslateX = useSharedValue(0);
95
136
  const triggerTranslateY = useSharedValue(0);
96
- const triggerOpacity = useSharedValue(1);
137
+ const triggerOpacity = useSharedValue(activeTriggerNode ? 0 : 1);
138
+
139
+ const hasZoomChangeListeners = useSharedValue(false);
140
+ const hasRotationChangeListeners = useSharedValue(false);
97
141
 
98
142
  const lastFocalX = useSharedValue(0);
99
143
  const lastFocalY = useSharedValue(0);
100
144
  const startFocalX = useSharedValue(0);
101
145
  const startFocalY = useSharedValue(0);
102
146
  const hasActiveFocal = useSharedValue(false);
103
-
104
- const dataLength = data?.length || 0;
147
+ const { clearPendingWebSingleTap, scheduleWebSingleTap } = useWebSingleTapTimer();
105
148
 
106
149
  const animationConfig = useMemo(
107
150
  () => ({
@@ -129,28 +172,24 @@ export const useGestureViewer = <ItemT, LC>({
129
172
  ],
130
173
  );
131
174
 
132
- const adjustedInitialIndex = useMemo(() => {
133
- if (enableLoop && dataLength > 1) {
134
- return initialIndex + 1;
135
- }
136
-
137
- return initialIndex;
138
- }, [enableLoop, dataLength, initialIndex]);
175
+ const {
176
+ centerVirtualIndex: renderCenterVirtualIndex,
177
+ currentIndex: renderCurrentIndex,
178
+ slots: renderWindowSlots,
179
+ } = resolveGestureViewerRenderWindow({
180
+ centerVirtualIndex,
181
+ currentIndex,
182
+ data,
183
+ enableLoop,
184
+ isTriggerOpening,
185
+ windowSize,
186
+ });
139
187
 
140
188
  const constrainTranslation = useMemo(
141
189
  () => createBoundsConstraint({ height, width }),
142
190
  [width, height],
143
191
  );
144
192
 
145
- const scrollTo = useCallback(
146
- (index: number, animated: boolean) => {
147
- const scrollAction = createScrollAction(listRef.current, width + itemSpacing);
148
-
149
- return scrollAction.scrollTo(index, animated);
150
- },
151
- [width, itemSpacing],
152
- );
153
-
154
193
  const resetTransformState = useCallback(() => {
155
194
  translateX.set(withTiming(0));
156
195
  translateY.set(withTiming(0));
@@ -161,38 +200,179 @@ export const useGestureViewer = <ItemT, LC>({
161
200
  rotation.set(0);
162
201
  }, [initialTranslateX, initialTranslateY, rotation, scale, startScale, translateX, translateY]);
163
202
 
164
- const syncPendingIndex = useCallback(
165
- (nextIndex: number) => {
166
- if (nextIndex < 0 || nextIndex >= dataLength) {
203
+ const resetTransformStateImmediately = useCallback(() => {
204
+ cancelAnimation(translateX);
205
+ cancelAnimation(translateY);
206
+ cancelAnimation(initialTranslateX);
207
+ cancelAnimation(initialTranslateY);
208
+ cancelAnimation(startScale);
209
+ cancelAnimation(scale);
210
+ cancelAnimation(rotation);
211
+ translateX.set(0);
212
+ translateY.set(0);
213
+ initialTranslateX.set(0);
214
+ initialTranslateY.set(0);
215
+ startScale.set(1);
216
+ scale.set(1);
217
+ rotation.set(0);
218
+ }, [initialTranslateX, initialTranslateY, rotation, scale, startScale, translateX, translateY]);
219
+
220
+ const commitCurrentIndex = useCallback((nextIndex: number) => {
221
+ pendingIndexRef.current = nextIndex;
222
+ currentIndexRef.current = nextIndex;
223
+ setCurrentIndex(nextIndex);
224
+ managerRef.current?.notifyStateChange();
225
+ }, []);
226
+
227
+ const commitVirtualIndexOnly = useCallback(
228
+ (nextVirtualIndex: number) => {
229
+ const nextLogicalIndex = getLogicalIndex(
230
+ nextVirtualIndex,
231
+ dataLengthRef.current,
232
+ enableLoopRef.current,
233
+ );
234
+
235
+ if (nextLogicalIndex === null) {
167
236
  return;
168
237
  }
169
238
 
170
- pendingIndexRef.current = nextIndex;
239
+ centerVirtualIndexRef.current = nextVirtualIndex;
240
+ setCenterVirtualIndex(nextVirtualIndex);
241
+ commitCurrentIndex(nextLogicalIndex);
171
242
  },
172
- [dataLength],
243
+ [commitCurrentIndex],
173
244
  );
174
245
 
175
- const syncCurrentIndex = useCallback(
176
- (nextIndex: number) => {
177
- if (!manager || nextIndex < 0 || nextIndex >= dataLength) {
246
+ const {
247
+ animateToVirtualPage,
248
+ horizontalPagingGesture,
249
+ isPageTransitioningRef,
250
+ pageTransitionLocked,
251
+ snapToVirtualPage,
252
+ visualPage,
253
+ } = useGestureViewerPaging({
254
+ centerVirtualIndex,
255
+ clearPendingWebSingleTap,
256
+ commitVirtualIndexOnly,
257
+ currentIndex,
258
+ dataLength,
259
+ enableLoop,
260
+ horizontalSwipeDistanceThresholdRatio,
261
+ horizontalSwipeEnabled,
262
+ horizontalSwipeVelocityThreshold,
263
+ initialPage: initialCurrentIndex,
264
+ isPinching,
265
+ isRotated,
266
+ isTriggerOpening,
267
+ isZoomed,
268
+ pageStride,
269
+ width,
270
+ });
271
+
272
+ const navigateToIndex = useCallback(
273
+ (targetIndex: number, options?: NavigationOptions, preferredDirection?: -1 | 1) => {
274
+ const resolution = resolveNavigation({
275
+ currentIndex: currentIndexRef.current,
276
+ dataLength: dataLengthRef.current,
277
+ enableLoop: enableLoopRef.current,
278
+ preferredDirection,
279
+ targetIndex,
280
+ });
281
+
282
+ if (resolution.kind === 'noop') {
178
283
  return;
179
284
  }
180
285
 
181
- pendingIndexRef.current = nextIndex;
286
+ if (
287
+ isPageTransitioningRef.current &&
288
+ !shouldRunNavigationDuringTransition(resolution, options)
289
+ ) {
290
+ return;
291
+ }
182
292
 
183
- const managerCurrentIndex = manager.getState().currentIndex;
293
+ const targetVirtualIndex =
294
+ resolution.kind === 'step'
295
+ ? centerVirtualIndexRef.current + resolution.direction
296
+ : getVirtualIndexForLogicalIndex(
297
+ resolution.targetIndex,
298
+ centerVirtualIndexRef.current,
299
+ dataLengthRef.current,
300
+ enableLoopRef.current,
301
+ );
184
302
 
185
- if (nextIndex === managerCurrentIndex) {
303
+ if (targetVirtualIndex === null) {
304
+ return;
305
+ }
306
+
307
+ if (resolution.kind !== 'step' || options?.animated === false) {
308
+ resetTransformStateImmediately();
309
+ snapToVirtualPage(targetVirtualIndex);
186
310
  return;
187
311
  }
188
312
 
189
- manager.setCurrentIndex(nextIndex);
190
- manager.notifyStateChange();
191
313
  resetTransformState();
314
+ animateToVirtualPage(targetVirtualIndex);
192
315
  },
193
- [dataLength, manager, resetTransformState],
316
+ [
317
+ animateToVirtualPage,
318
+ isPageTransitioningRef,
319
+ resetTransformStateImmediately,
320
+ resetTransformState,
321
+ snapToVirtualPage,
322
+ ],
194
323
  );
195
324
 
325
+ const navigateByDirection = useCallback(
326
+ (direction: -1 | 1) => {
327
+ const currentDataLength = dataLengthRef.current;
328
+
329
+ if (currentDataLength <= 0) {
330
+ return;
331
+ }
332
+
333
+ const nextIndex = currentIndexRef.current + direction;
334
+
335
+ if (nextIndex >= 0 && nextIndex < currentDataLength) {
336
+ navigateToIndex(nextIndex, undefined, direction);
337
+ return;
338
+ }
339
+
340
+ if (!enableLoopRef.current || currentDataLength <= 1) {
341
+ return;
342
+ }
343
+
344
+ navigateToIndex(direction === 1 ? 0 : currentDataLength - 1, undefined, direction);
345
+ },
346
+ [navigateToIndex],
347
+ );
348
+
349
+ const navigateToNext = useCallback(() => {
350
+ navigateByDirection(1);
351
+ }, [navigateByDirection]);
352
+
353
+ const navigateToPrevious = useCallback(() => {
354
+ navigateByDirection(-1);
355
+ }, [navigateByDirection]);
356
+
357
+ useGestureViewerManagerBridge({
358
+ currentIndexRef,
359
+ dataLengthRef,
360
+ goToIndex: navigateToIndex,
361
+ goToNext: navigateToNext,
362
+ goToPrevious: navigateToPrevious,
363
+ hasRotationChangeListeners,
364
+ hasZoomChangeListeners,
365
+ height,
366
+ id,
367
+ managerRef,
368
+ maxZoomScale,
369
+ rotation,
370
+ scale,
371
+ translateX,
372
+ translateY,
373
+ width,
374
+ });
375
+
196
376
  const emitZoomChange = useCallback((currentScale: number, prevScale: number | null) => {
197
377
  managerRef.current?.emitZoomChange(currentScale, prevScale);
198
378
  }, []);
@@ -201,136 +381,203 @@ export const useGestureViewer = <ItemT, LC>({
201
381
  managerRef.current?.emitRotationChange(currentRotation, prevRotation);
202
382
  }, []);
203
383
 
204
- const onAnimationComplete = useCallback(() => {
384
+ const finishTriggerOpening = useCallback(() => {
385
+ setIsTriggerOpening(false);
205
386
  onAnimationCompleteRef.current?.();
206
387
  }, []);
207
388
 
208
389
  useAnimatedReaction(
209
390
  () => scale.get(),
210
391
  (currentScale, previousScale) => {
211
- if (currentScale !== previousScale) {
392
+ if (currentScale !== previousScale && hasZoomChangeListeners.get()) {
212
393
  scheduleOnRN(emitZoomChange, currentScale, previousScale);
213
394
  }
214
395
 
215
- scheduleOnRN(setIsZoomed, currentScale > 1);
396
+ const currentIsZoomed = currentScale > 1;
397
+ const previousIsZoomed = (previousScale ?? 1) > 1;
398
+
399
+ if (previousScale === null || currentIsZoomed !== previousIsZoomed) {
400
+ scheduleOnRN(setIsZoomed, currentIsZoomed);
401
+ }
216
402
  },
217
403
  );
218
404
 
219
405
  useAnimatedReaction(
220
406
  () => rotation.get(),
221
407
  (currentRotation, previousRotation) => {
222
- if (currentRotation !== previousRotation) {
408
+ if (currentRotation !== previousRotation && hasRotationChangeListeners.get()) {
223
409
  scheduleOnRN(emitRotationChange, currentRotation, previousRotation);
224
410
  }
225
411
 
226
- scheduleOnRN(setIsRotated, currentRotation % 360 !== 0);
412
+ const currentIsRotated = currentRotation % 360 !== 0;
413
+ const previousIsRotated = (previousRotation ?? 0) % 360 !== 0;
414
+
415
+ if (previousRotation === null || currentIsRotated !== previousIsRotated) {
416
+ scheduleOnRN(setIsRotated, currentIsRotated);
417
+ }
227
418
  },
228
419
  );
229
420
 
230
421
  useEffect(() => {
231
- let unsubscribe: (() => void) | undefined;
422
+ return registry.subscribeToActiveTrigger(id, (node) => {
423
+ setActiveTriggerNode(node);
232
424
 
233
- const unsubscribeFromRegistry = registry.subscribeToManager(id, (managerInstance) => {
234
- setManager(managerInstance);
235
- unsubscribe = managerInstance?.subscribe((state) => {
236
- pendingIndexRef.current = state.currentIndex;
237
- setCurrentIndex(state.currentIndex);
238
- });
425
+ if (node) {
426
+ setIsTriggerOpening(true);
427
+ }
239
428
  });
240
-
241
- return () => {
242
- unsubscribeFromRegistry();
243
- unsubscribe?.();
244
- };
245
429
  }, [id]);
246
430
 
247
431
  useEffect(() => {
248
- return registry.subscribeToActiveTrigger(id, setActiveTriggerNode);
249
- }, [id]);
432
+ dataRef.current = data;
433
+ }, [data]);
250
434
 
251
435
  useEffect(() => {
252
- pendingIndexRef.current = initialIndex;
436
+ isZoomedRef.current = isZoomed;
437
+ }, [isZoomed]);
253
438
 
254
- if (!manager) {
255
- return;
439
+ useEffect(() => {
440
+ isRotatedRef.current = isRotated;
441
+ }, [isRotated]);
442
+
443
+ useEffect(() => {
444
+ isPinchingRef.current = isPinching;
445
+ }, [isPinching]);
446
+
447
+ useEffect(() => {
448
+ onSingleTapRef.current = onSingleTap;
449
+ }, [onSingleTap]);
450
+
451
+ useEffect(() => {
452
+ onAnimationCompleteRef.current = triggerAnimation?.onAnimationComplete;
453
+ }, [triggerAnimation?.onAnimationComplete]);
454
+
455
+ useLayoutEffect(() => {
456
+ const initialIndexChanged = previousInitialIndexRef.current !== initialIndex;
457
+ const shouldResolveInitialIndex =
458
+ initialIndexChanged || (!hasResolvedInitialIndexRef.current && dataLength > 0);
459
+
460
+ previousInitialIndexRef.current = initialIndex;
461
+ dataLengthRef.current = dataLength;
462
+ enableLoopRef.current = enableLoop;
463
+
464
+ hasResolvedInitialIndexRef.current = dataLength > 0;
465
+
466
+ const nextIndex = shouldResolveInitialIndex
467
+ ? clampIndex(initialIndex, dataLength)
468
+ : clampIndex(currentIndexRef.current, dataLength);
469
+
470
+ const nextVirtualIndex =
471
+ getVirtualIndexForLogicalIndex(
472
+ nextIndex,
473
+ centerVirtualIndexRef.current,
474
+ dataLength,
475
+ enableLoop,
476
+ ) ?? nextIndex;
477
+
478
+ const previousIndex = currentIndexRef.current;
479
+ const previousVirtualIndex = centerVirtualIndexRef.current;
480
+ const shouldSyncVirtualPage =
481
+ isPageTransitioningRef.current || previousVirtualIndex !== nextVirtualIndex;
482
+ const shouldResetTransform =
483
+ initialIndexChanged || previousIndex !== nextIndex || shouldSyncVirtualPage;
484
+
485
+ if (shouldSyncVirtualPage) {
486
+ snapToVirtualPage(nextVirtualIndex);
487
+
488
+ if (dataLength === 0) {
489
+ centerVirtualIndexRef.current = nextVirtualIndex;
490
+ setCenterVirtualIndex(nextVirtualIndex);
491
+ }
256
492
  }
257
493
 
258
- manager.setDataLength(dataLength);
259
- manager.setEnableHorizontalSwipe(enableHorizontalSwipe);
260
- manager.setCurrentIndex(initialIndex);
261
- manager.setWidth(width + itemSpacing);
262
- manager.setHeight(height);
263
- manager.setZoomSharedValues(scale, translateX, translateY, maxZoomScale);
264
- manager.setResetTransformCallback(resetTransformState);
265
- manager.setRotation(rotation);
266
- manager.setEnableLoop(enableLoop);
267
- manager.notifyStateChange();
494
+ const shouldCommitIndexOutsidePaging = !shouldSyncVirtualPage || dataLength === 0;
495
+
496
+ if (shouldCommitIndexOutsidePaging && previousIndex !== nextIndex) {
497
+ commitCurrentIndex(nextIndex);
498
+ }
499
+
500
+ if (shouldCommitIndexOutsidePaging && previousIndex === nextIndex) {
501
+ managerRef.current?.notifyStateChange();
502
+ }
503
+
504
+ if (shouldResetTransform) {
505
+ resetTransformState();
506
+ }
268
507
  }, [
508
+ commitCurrentIndex,
269
509
  dataLength,
270
- enableHorizontalSwipe,
271
- initialIndex,
272
- manager,
273
- width,
274
- itemSpacing,
275
- maxZoomScale,
276
510
  enableLoop,
277
- scale,
278
- height,
511
+ initialIndex,
512
+ isPageTransitioningRef,
279
513
  resetTransformState,
280
- translateX,
281
- translateY,
282
- rotation,
514
+ snapToVirtualPage,
283
515
  ]);
284
516
 
285
517
  useEffect(() => {
286
- if (!manager || !listRef.current) {
518
+ return () => {
519
+ triggerRectRef.current = null;
520
+ clearPendingWebSingleTap();
521
+ };
522
+ }, [clearPendingWebSingleTap]);
523
+
524
+ useEffect(() => {
525
+ if (
526
+ !autoPlay ||
527
+ dataLength <= 1 ||
528
+ isTriggerOpening ||
529
+ isZoomed ||
530
+ isRotated ||
531
+ isPinching ||
532
+ (!enableLoop && currentIndex >= dataLength - 1)
533
+ ) {
287
534
  return;
288
535
  }
289
536
 
290
- manager.setListRef(listRef.current);
291
- }, [manager]);
537
+ const interval = Math.max(250, Math.floor(autoPlayInterval || 0));
292
538
 
293
- useEffect(() => {
294
- translateY.set(0);
295
- translateX.set(0);
296
- scale.set(1);
297
- backdropOpacity.set(1);
298
- startScale.set(1);
299
- rotation.set(0);
300
-
301
- if (adjustedInitialIndex <= 0 || !listRef.current) {
539
+ if (!Number.isFinite(interval)) {
302
540
  return;
303
541
  }
304
542
 
305
- return scheduleInitialScroll(() => {
306
- scrollTo(adjustedInitialIndex, false);
307
- });
308
- }, [
309
- adjustedInitialIndex,
310
- translateY,
311
- backdropOpacity,
312
- translateX,
313
- scale,
314
- startScale,
315
- rotation,
316
- scrollTo,
317
- ]);
543
+ const timer = setInterval(() => {
544
+ if (
545
+ isPageTransitioningRef.current ||
546
+ isZoomedRef.current ||
547
+ isRotatedRef.current ||
548
+ isPinchingRef.current
549
+ ) {
550
+ return;
551
+ }
318
552
 
319
- useEffect(() => {
320
- onAnimationCompleteRef.current = triggerAnimation?.onAnimationComplete;
321
- }, [triggerAnimation?.onAnimationComplete]);
553
+ const nextIndex =
554
+ currentIndexRef.current >= dataLengthRef.current - 1
555
+ ? enableLoopRef.current
556
+ ? 0
557
+ : null
558
+ : currentIndexRef.current + 1;
322
559
 
323
- useEffect(() => {
324
- dataRef.current = data;
325
- }, [data]);
560
+ if (nextIndex === null) {
561
+ return;
562
+ }
326
563
 
327
- useEffect(() => {
328
- managerRef.current = manager;
329
- }, [manager]);
564
+ navigateToIndex(nextIndex);
565
+ }, interval);
330
566
 
331
- useEffect(() => {
332
- onSingleTapRef.current = onSingleTap;
333
- }, [onSingleTap]);
567
+ return () => clearInterval(timer);
568
+ }, [
569
+ autoPlay,
570
+ autoPlayInterval,
571
+ currentIndex,
572
+ dataLength,
573
+ enableLoop,
574
+ isPinching,
575
+ isRotated,
576
+ isTriggerOpening,
577
+ isZoomed,
578
+ isPageTransitioningRef,
579
+ navigateToIndex,
580
+ ]);
334
581
 
335
582
  useEffect(() => {
336
583
  if (shouldStartTriggerAnimation && triggerRectRef.current) {
@@ -349,7 +596,7 @@ export const useGestureViewer = <ItemT, LC>({
349
596
  triggerScale.set(
350
597
  withTiming(1, animationConfig, (finished) => {
351
598
  if (finished) {
352
- scheduleOnRN(onAnimationComplete);
599
+ scheduleOnRN(finishTriggerOpening);
353
600
  }
354
601
  }),
355
602
  );
@@ -374,11 +621,18 @@ export const useGestureViewer = <ItemT, LC>({
374
621
  triggerScale,
375
622
  triggerTranslateX,
376
623
  triggerTranslateY,
377
- onAnimationComplete,
624
+ finishTriggerOpening,
378
625
  ]);
379
626
 
380
627
  useEffect(() => {
381
- if (!activeTriggerNode || typeof activeTriggerNode.measure !== 'function') {
628
+ if (!activeTriggerNode) {
629
+ return;
630
+ }
631
+
632
+ if (typeof activeTriggerNode.measure !== 'function') {
633
+ setIsTriggerOpening(false);
634
+ triggerOpacity.set(1);
635
+ registry.clearActiveTriggerNode(id);
382
636
  return;
383
637
  }
384
638
 
@@ -391,6 +645,8 @@ export const useGestureViewer = <ItemT, LC>({
391
645
  } satisfies TriggerRect;
392
646
 
393
647
  if (!isValidTriggerRect(nextTriggerRect)) {
648
+ setIsTriggerOpening(false);
649
+ triggerOpacity.set(1);
394
650
  registry.clearActiveTriggerNode(id);
395
651
  return;
396
652
  }
@@ -400,13 +656,7 @@ export const useGestureViewer = <ItemT, LC>({
400
656
  setShouldStartTriggerAnimation(true);
401
657
  registry.clearActiveTriggerNode(id);
402
658
  });
403
- }, [activeTriggerNode, id, isValidTriggerRect, triggerOpacity]);
404
-
405
- useEffect(() => {
406
- return () => {
407
- triggerRectRef.current = null;
408
- };
409
- }, []);
659
+ }, [activeTriggerNode, id, triggerOpacity]);
410
660
 
411
661
  const animateDismissToRect = useCallback(
412
662
  (rect: TriggerRect) => {
@@ -444,6 +694,10 @@ export const useGestureViewer = <ItemT, LC>({
444
694
  }, [onDismiss]);
445
695
 
446
696
  const handleDismiss = useCallback(() => {
697
+ if (isPageTransitioningRef.current) {
698
+ return;
699
+ }
700
+
447
701
  onDismissStart?.();
448
702
 
449
703
  const dismissTargetIndex = pendingIndexRef.current;
@@ -479,10 +733,10 @@ export const useGestureViewer = <ItemT, LC>({
479
733
  }
480
734
 
481
735
  dismissWithoutTrigger();
482
- }, [animateDismissToRect, dismissWithoutTrigger, id, isValidTriggerRect, onDismissStart]);
736
+ }, [animateDismissToRect, dismissWithoutTrigger, id, isPageTransitioningRef, onDismissStart]);
483
737
 
484
738
  const dismissGesture = useMemo(() => {
485
- const canDismiss = !isZoomed && dismissOptions.enabled;
739
+ const canDismiss = !isTriggerOpening && !isZoomed && dismissOptions.enabled;
486
740
 
487
741
  return Gesture.Pan()
488
742
  .minDistance(10)
@@ -493,9 +747,18 @@ export const useGestureViewer = <ItemT, LC>({
493
747
  .withRef(dismissGestureRef)
494
748
  .enabled(canDismiss)
495
749
  .onUpdate((event) => {
750
+ if (pageTransitionLocked.get()) {
751
+ return;
752
+ }
753
+
496
754
  translateY.set(event.translationY / dismissOptions.resistance);
497
755
  })
498
756
  .onEnd((event) => {
757
+ if (pageTransitionLocked.get()) {
758
+ translateY.set(withSpring(0, PAGE_SPRING_CONFIG));
759
+ return;
760
+ }
761
+
499
762
  if (
500
763
  canDismiss &&
501
764
  shouldDismissByDirection(
@@ -508,28 +771,120 @@ export const useGestureViewer = <ItemT, LC>({
508
771
  return;
509
772
  }
510
773
 
511
- translateY.set(
512
- withSpring(0, {
513
- damping: 50,
514
- energyThreshold: 6e-9,
515
- mass: 4,
516
- overshootClamping: false,
517
- stiffness: 600,
518
- }),
519
- );
774
+ translateY.set(withSpring(0, PAGE_SPRING_CONFIG));
520
775
  });
521
- }, [translateY, dismissOptions, handleDismiss, isZoomed]);
776
+ }, [translateY, dismissOptions, handleDismiss, isTriggerOpening, isZoomed, pageTransitionLocked]);
777
+
778
+ const getCurrentTapTarget = useCallback((): { index: number; item: ItemT } | null => {
779
+ const index = pendingIndexRef.current;
780
+
781
+ if (index < 0 || index >= dataRef.current.length || !(index in dataRef.current)) {
782
+ return null;
783
+ }
784
+
785
+ return { index, item: dataRef.current[index] as ItemT };
786
+ }, []);
787
+
788
+ const isWebInteractionLocked = useCallback(
789
+ () => isPageTransitioningRef.current || pageTransitionLocked.get(),
790
+ [isPageTransitioningRef, pageTransitionLocked],
791
+ );
792
+
793
+ const emitSingleTap = useCallback<EmitSingleTap<ItemT>>(
794
+ (x, y, tapTarget) => {
795
+ if (isPageTransitioningRef.current) {
796
+ return;
797
+ }
798
+
799
+ const target = tapTarget ?? getCurrentTapTarget();
800
+
801
+ if (!target) {
802
+ return;
803
+ }
804
+
805
+ const { index: targetIndex, item: targetItem } = target;
806
+
807
+ if (targetIndex < 0) {
808
+ return;
809
+ }
810
+
811
+ managerRef.current?.emitTap({ kind: 'single', x, y, index: targetIndex });
812
+ onSingleTapRef.current?.({ x, y, index: targetIndex, item: targetItem });
813
+ },
814
+ [getCurrentTapTarget, isPageTransitioningRef],
815
+ );
816
+
817
+ const singleTapGesture = useMemo(
818
+ () =>
819
+ Gesture.Tap()
820
+ .enabled(enableNativeTapGestures)
821
+ .numberOfTaps(1)
822
+ .onEnd((event, success) => {
823
+ if (!success || pageTransitionLocked.get()) {
824
+ return;
825
+ }
826
+
827
+ scheduleOnRN(emitSingleTap, event.x, event.y);
828
+ }),
829
+ [emitSingleTap, pageTransitionLocked],
830
+ );
831
+
832
+ const doubleTapGesture = useMemo(
833
+ () =>
834
+ Gesture.Tap()
835
+ .enabled(enableDoubleTapZoom && enableNativeTapGestures)
836
+ .numberOfTaps(2)
837
+ .onEnd((event) => {
838
+ if (pageTransitionLocked.get()) {
839
+ return;
840
+ }
841
+
842
+ applyTapZoomAtPoint({
843
+ x: event.x,
844
+ y: event.y,
845
+ width,
846
+ height,
847
+ maxZoomScale,
848
+ scale,
849
+ translateX,
850
+ translateY,
851
+ });
852
+ }),
853
+ [
854
+ enableDoubleTapZoom,
855
+ height,
856
+ maxZoomScale,
857
+ pageTransitionLocked,
858
+ scale,
859
+ translateX,
860
+ translateY,
861
+ width,
862
+ ],
863
+ );
864
+
865
+ const tapGesture = useMemo(
866
+ () => Gesture.Exclusive(doubleTapGesture, singleTapGesture),
867
+ [doubleTapGesture, singleTapGesture],
868
+ );
522
869
 
523
870
  const zoomPinchGesture = useMemo(
524
871
  () =>
525
872
  Gesture.Pinch()
526
873
  .enabled(enablePinchZoom)
527
874
  .onTouchesDown((event) => {
875
+ if (pageTransitionLocked.get()) {
876
+ return;
877
+ }
878
+
528
879
  if (event.numberOfTouches === 2) {
529
880
  scheduleOnRN(setIsPinching, true);
530
881
  }
531
882
  })
532
883
  .onStart((event) => {
884
+ if (pageTransitionLocked.get()) {
885
+ return;
886
+ }
887
+
533
888
  startScale.set(scale.get());
534
889
  initialTranslateX.set(translateX.get());
535
890
  initialTranslateY.set(translateY.get());
@@ -540,6 +895,10 @@ export const useGestureViewer = <ItemT, LC>({
540
895
  hasActiveFocal.set(false);
541
896
  })
542
897
  .onUpdate((event) => {
898
+ if (pageTransitionLocked.get()) {
899
+ return;
900
+ }
901
+
543
902
  const initialScale = startScale.get();
544
903
  const newScale = initialScale * event.scale;
545
904
 
@@ -601,6 +960,10 @@ export const useGestureViewer = <ItemT, LC>({
601
960
  translateY.set(constrainedTranslateY);
602
961
  })
603
962
  .onEnd(() => {
963
+ if (pageTransitionLocked.get()) {
964
+ return;
965
+ }
966
+
604
967
  const currentScale = scale.get();
605
968
 
606
969
  if (currentScale > maxZoomScale) {
@@ -673,6 +1036,7 @@ export const useGestureViewer = <ItemT, LC>({
673
1036
  startFocalX,
674
1037
  startFocalY,
675
1038
  hasActiveFocal,
1039
+ pageTransitionLocked,
676
1040
  ],
677
1041
  );
678
1042
 
@@ -683,10 +1047,18 @@ export const useGestureViewer = <ItemT, LC>({
683
1047
  .activeCursor('grabbing')
684
1048
  .averageTouches(true)
685
1049
  .onBegin(() => {
1050
+ if (pageTransitionLocked.get()) {
1051
+ return;
1052
+ }
1053
+
686
1054
  initialTranslateX.set(translateX.get());
687
1055
  initialTranslateY.set(translateY.get());
688
1056
  })
689
1057
  .onUpdate((event) => {
1058
+ if (pageTransitionLocked.get()) {
1059
+ return;
1060
+ }
1061
+
690
1062
  const currentScale = scale.get();
691
1063
 
692
1064
  if (currentScale > 1) {
@@ -713,73 +1085,30 @@ export const useGestureViewer = <ItemT, LC>({
713
1085
  initialTranslateX,
714
1086
  initialTranslateY,
715
1087
  constrainTranslation,
1088
+ pageTransitionLocked,
716
1089
  ],
717
1090
  );
718
1091
 
719
- const emitSingleTap = useCallback((x: number, y: number) => {
720
- const index = pendingIndexRef.current;
721
- const currentData = dataRef.current;
722
-
723
- if (index < 0 || index >= currentData.length) {
724
- return;
725
- }
726
-
727
- managerRef.current?.emitTap({ kind: 'single', x, y, index });
728
-
729
- const item = currentData[index];
730
-
731
- if (item === undefined) {
732
- return;
733
- }
734
-
735
- onSingleTapRef.current?.({ x, y, index, item });
736
- }, []);
737
-
738
- const singleTapGesture = useMemo(
739
- () =>
740
- Gesture.Tap()
741
- .enabled(Platform.OS !== 'web')
742
- .numberOfTaps(1)
743
- .onEnd((event, success) => {
744
- if (!success) {
745
- return;
746
- }
747
-
748
- scheduleOnRN(emitSingleTap, event.x, event.y);
749
- }),
750
- [emitSingleTap],
751
- );
752
-
753
- const doubleTapGesture = useMemo(
754
- () =>
755
- Gesture.Tap()
756
- .enabled(enableDoubleTapZoom && Platform.OS !== 'web')
757
- .numberOfTaps(2)
758
- .onEnd((event) => {
759
- applyTapZoomAtPoint({
760
- x: event.x,
761
- y: event.y,
762
- width,
763
- height,
764
- maxZoomScale,
765
- scale,
766
- translateX,
767
- translateY,
768
- });
769
- }),
770
- [enableDoubleTapZoom, height, maxZoomScale, scale, translateX, translateY, width],
771
- );
772
-
773
- const tapGesture = useMemo(
774
- () => Gesture.Exclusive(doubleTapGesture, singleTapGesture),
775
- [doubleTapGesture, singleTapGesture],
776
- );
777
-
778
1092
  const zoomGesture = useMemo(
779
1093
  () => Gesture.Race(zoomPinchGesture, Gesture.Exclusive(zoomPanGesture, tapGesture)),
780
1094
  [zoomPinchGesture, zoomPanGesture, tapGesture],
781
1095
  );
782
1096
 
1097
+ const onWebClick = useWebClickHandler({
1098
+ clearPendingWebSingleTap,
1099
+ emitSingleTap,
1100
+ enableDoubleTapZoom,
1101
+ getCurrentTapTarget,
1102
+ height,
1103
+ maxZoomScale,
1104
+ scale,
1105
+ scheduleWebSingleTap,
1106
+ isInteractionLocked: isWebInteractionLocked,
1107
+ translateX,
1108
+ translateY,
1109
+ width,
1110
+ });
1111
+
783
1112
  const animatedStyle = useAnimatedStyle(() => ({
784
1113
  opacity: triggerOpacity.get(),
785
1114
  transform: [
@@ -807,52 +1136,22 @@ export const useGestureViewer = <ItemT, LC>({
807
1136
  return { opacity: baseOpacity * dismissOpacity };
808
1137
  }, [dismissOptions.direction, dismissOptions.fadeBackdrop]);
809
1138
 
810
- const nativeScrollGesture = useMemo(() => {
811
- return Gesture.Native().requireExternalGestureToFail(dismissGestureRef);
812
- }, []);
813
-
814
- const { onMomentumScrollEnd, onScroll, onScrollBeginDrag, onWebClick } = useGestureViewerPaging({
815
- adjustedInitialIndex,
816
- autoPlay,
817
- autoPlayInterval,
818
- currentIndex,
819
- dataLength,
820
- enableDoubleTapZoom,
821
- enableHorizontalSwipe,
822
- enableLoop,
823
- height,
824
- isRotated,
825
- isZoomed,
826
- itemSpacing,
827
- manager,
828
- maxZoomScale,
829
- onSingleTap: emitSingleTap,
830
- scale,
831
- scrollTo,
832
- syncCurrentIndex,
833
- syncPendingIndex,
834
- translateX,
835
- translateY,
836
- width,
837
- });
838
-
839
1139
  return {
840
1140
  animatedStyle,
841
1141
  backdropStyle,
1142
+ centerVirtualIndex: renderCenterVirtualIndex,
1143
+ currentIndex: renderCurrentIndex,
842
1144
  dataLength,
843
1145
  dismissGesture,
844
1146
  handleDismiss,
1147
+ horizontalPagingGesture,
845
1148
  isPinching,
846
1149
  isRotated,
847
-
848
1150
  isZoomed,
849
- listRef,
850
- nativeScrollGesture,
851
1151
  onWebClick,
852
- onMomentumScrollEnd,
853
- onScroll,
854
-
855
- onScrollBeginDrag,
1152
+ pageStride,
1153
+ renderWindowSlots,
1154
+ visualPage,
856
1155
  zoomGesture,
857
1156
  };
858
1157
  };