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,17 +1,27 @@
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 isValidTriggerRect = rect => {
23
+ return !!rect && rect.width > 0 && rect.height > 0;
24
+ };
15
25
  export const useGestureViewer = ({
16
26
  data,
17
27
  initialIndex = 0,
@@ -21,11 +31,12 @@ export const useGestureViewer = ({
21
31
  dismiss,
22
32
  enableDoubleTapZoom = true,
23
33
  enablePinchZoom = true,
24
- enableHorizontalSwipe = true,
34
+ horizontalSwipe,
25
35
  enablePanWhenZoomed = true,
26
36
  enableLoop = false,
27
37
  maxZoomScale = 2,
28
- itemSpacing = 0,
38
+ pageSpacing = 0,
39
+ windowSize = 3,
29
40
  height: customHeight,
30
41
  id = 'default',
31
42
  onDismissStart,
@@ -39,42 +50,59 @@ export const useGestureViewer = ({
39
50
  } = useWindowDimensions();
40
51
  const width = customWidth || screenWidth;
41
52
  const height = customHeight || screenHeight;
53
+ const dataLength = data?.length || 0;
54
+ const normalizedPageSpacing = normalizePageSpacing(pageSpacing);
55
+ const pageStride = getPageStride(width, normalizedPageSpacing);
56
+ const initialCurrentIndex = clampIndex(initialIndex, dataLength);
57
+ const horizontalSwipeEnabled = horizontalSwipe?.enabled ?? true;
58
+ const horizontalSwipeDistanceThresholdRatio = normalizeHorizontalSwipeThreshold(horizontalSwipe?.distanceThresholdRatio, SWIPE_THRESHOLD_RATIO);
59
+ const horizontalSwipeVelocityThreshold = normalizeHorizontalSwipeThreshold(horizontalSwipe?.velocityThreshold, SWIPE_VELOCITY_THRESHOLD);
42
60
  const dismissGestureRef = useRef(undefined);
43
61
  const [isZoomed, setIsZoomed] = useState(false);
44
62
  const [isRotated, setIsRotated] = useState(false);
45
63
  const [isPinching, setIsPinching] = useState(false);
46
64
  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);
65
+ const [currentIndex, setCurrentIndex] = useState(initialCurrentIndex);
66
+ const [centerVirtualIndex, setCenterVirtualIndex] = useState(initialCurrentIndex);
67
+ const [activeTriggerNode, setActiveTriggerNode] = useState(() => registry.getActiveTriggerNode(id));
68
+ const [isTriggerOpening, setIsTriggerOpening] = useState(() => !!registry.getActiveTriggerNode(id));
51
69
  const triggerRectRef = useRef(null);
52
- const pendingIndexRef = useRef(initialIndex);
70
+ const pendingIndexRef = useRef(initialCurrentIndex);
71
+ const currentIndexRef = useRef(initialCurrentIndex);
72
+ const centerVirtualIndexRef = useRef(initialCurrentIndex);
73
+ const previousInitialIndexRef = useRef(initialIndex);
74
+ const hasResolvedInitialIndexRef = useRef(dataLength > 0);
53
75
  const onAnimationCompleteRef = useRef(triggerAnimation?.onAnimationComplete);
54
76
  const onSingleTapRef = useRef(onSingleTap);
55
77
  const dataRef = useRef(data);
56
- const managerRef = useRef(manager);
57
- const isValidTriggerRect = useCallback(rect => {
58
- return !!rect && rect.width > 0 && rect.height > 0;
59
- }, []);
78
+ const dataLengthRef = useRef(dataLength);
79
+ const enableLoopRef = useRef(enableLoop);
80
+ const managerRef = useRef(null);
81
+ const isZoomedRef = useRef(isZoomed);
82
+ const isRotatedRef = useRef(isRotated);
83
+ const isPinchingRef = useRef(isPinching);
60
84
  const initialTranslateY = useSharedValue(0);
61
85
  const initialTranslateX = useSharedValue(0);
62
86
  const startScale = useSharedValue(1);
63
87
  const translateY = useSharedValue(0);
64
88
  const translateX = useSharedValue(0);
65
89
  const scale = useSharedValue(1);
66
- const backdropOpacity = useSharedValue(1);
67
90
  const rotation = useSharedValue(0);
68
91
  const triggerScale = useSharedValue(1);
69
92
  const triggerTranslateX = useSharedValue(0);
70
93
  const triggerTranslateY = useSharedValue(0);
71
- const triggerOpacity = useSharedValue(1);
94
+ const triggerOpacity = useSharedValue(activeTriggerNode ? 0 : 1);
95
+ const hasZoomChangeListeners = useSharedValue(false);
96
+ const hasRotationChangeListeners = useSharedValue(false);
72
97
  const lastFocalX = useSharedValue(0);
73
98
  const lastFocalY = useSharedValue(0);
74
99
  const startFocalX = useSharedValue(0);
75
100
  const startFocalY = useSharedValue(0);
76
101
  const hasActiveFocal = useSharedValue(false);
77
- const dataLength = data?.length || 0;
102
+ const {
103
+ clearPendingWebSingleTap,
104
+ scheduleWebSingleTap
105
+ } = useWebSingleTapTimer();
78
106
  const animationConfig = useMemo(() => ({
79
107
  duration: triggerAnimation?.duration ?? 300,
80
108
  easing: triggerAnimation?.easing ?? Easing.bezier(0.25, 0.1, 0.25, 1),
@@ -87,20 +115,22 @@ export const useGestureViewer = ({
87
115
  resistance: dismiss?.resistance ?? 2,
88
116
  threshold: dismiss?.threshold ?? 80
89
117
  }), [dismiss?.direction, dismiss?.enabled, dismiss?.threshold, dismiss?.resistance, dismiss?.fadeBackdrop]);
90
- const adjustedInitialIndex = useMemo(() => {
91
- if (enableLoop && dataLength > 1) {
92
- return initialIndex + 1;
93
- }
94
- return initialIndex;
95
- }, [enableLoop, dataLength, initialIndex]);
118
+ const {
119
+ centerVirtualIndex: renderCenterVirtualIndex,
120
+ currentIndex: renderCurrentIndex,
121
+ slots: renderWindowSlots
122
+ } = resolveGestureViewerRenderWindow({
123
+ centerVirtualIndex,
124
+ currentIndex,
125
+ data,
126
+ enableLoop,
127
+ isTriggerOpening,
128
+ windowSize
129
+ });
96
130
  const constrainTranslation = useMemo(() => createBoundsConstraint({
97
131
  height,
98
132
  width
99
133
  }), [width, height]);
100
- const scrollTo = useCallback((index, animated) => {
101
- const scrollAction = createScrollAction(listRef.current, width + itemSpacing);
102
- return scrollAction.scrollTo(index, animated);
103
- }, [width, itemSpacing]);
104
134
  const resetTransformState = useCallback(() => {
105
135
  translateX.set(withTiming(0));
106
136
  translateY.set(withTiming(0));
@@ -110,111 +140,240 @@ export const useGestureViewer = ({
110
140
  scale.set(withTiming(1));
111
141
  rotation.set(0);
112
142
  }, [initialTranslateX, initialTranslateY, rotation, scale, startScale, translateX, translateY]);
113
- const syncPendingIndex = useCallback(nextIndex => {
114
- if (nextIndex < 0 || nextIndex >= dataLength) {
143
+ const resetTransformStateImmediately = useCallback(() => {
144
+ cancelAnimation(translateX);
145
+ cancelAnimation(translateY);
146
+ cancelAnimation(initialTranslateX);
147
+ cancelAnimation(initialTranslateY);
148
+ cancelAnimation(startScale);
149
+ cancelAnimation(scale);
150
+ cancelAnimation(rotation);
151
+ translateX.set(0);
152
+ translateY.set(0);
153
+ initialTranslateX.set(0);
154
+ initialTranslateY.set(0);
155
+ startScale.set(1);
156
+ scale.set(1);
157
+ rotation.set(0);
158
+ }, [initialTranslateX, initialTranslateY, rotation, scale, startScale, translateX, translateY]);
159
+ const commitCurrentIndex = useCallback(nextIndex => {
160
+ pendingIndexRef.current = nextIndex;
161
+ currentIndexRef.current = nextIndex;
162
+ setCurrentIndex(nextIndex);
163
+ managerRef.current?.notifyStateChange();
164
+ }, []);
165
+ const commitVirtualIndexOnly = useCallback(nextVirtualIndex => {
166
+ const nextLogicalIndex = getLogicalIndex(nextVirtualIndex, dataLengthRef.current, enableLoopRef.current);
167
+ if (nextLogicalIndex === null) {
115
168
  return;
116
169
  }
117
- pendingIndexRef.current = nextIndex;
118
- }, [dataLength]);
119
- const syncCurrentIndex = useCallback(nextIndex => {
120
- if (!manager || nextIndex < 0 || nextIndex >= dataLength) {
170
+ centerVirtualIndexRef.current = nextVirtualIndex;
171
+ setCenterVirtualIndex(nextVirtualIndex);
172
+ commitCurrentIndex(nextLogicalIndex);
173
+ }, [commitCurrentIndex]);
174
+ const {
175
+ animateToVirtualPage,
176
+ horizontalPagingGesture,
177
+ isPageTransitioningRef,
178
+ pageTransitionLocked,
179
+ snapToVirtualPage,
180
+ visualPage
181
+ } = useGestureViewerPaging({
182
+ centerVirtualIndex,
183
+ clearPendingWebSingleTap,
184
+ commitVirtualIndexOnly,
185
+ currentIndex,
186
+ dataLength,
187
+ enableLoop,
188
+ horizontalSwipeDistanceThresholdRatio,
189
+ horizontalSwipeEnabled,
190
+ horizontalSwipeVelocityThreshold,
191
+ initialPage: initialCurrentIndex,
192
+ isPinching,
193
+ isRotated,
194
+ isTriggerOpening,
195
+ isZoomed,
196
+ pageStride,
197
+ width
198
+ });
199
+ const navigateToIndex = useCallback((targetIndex, options, preferredDirection) => {
200
+ const resolution = resolveNavigation({
201
+ currentIndex: currentIndexRef.current,
202
+ dataLength: dataLengthRef.current,
203
+ enableLoop: enableLoopRef.current,
204
+ preferredDirection,
205
+ targetIndex
206
+ });
207
+ if (resolution.kind === 'noop') {
121
208
  return;
122
209
  }
123
- pendingIndexRef.current = nextIndex;
124
- const managerCurrentIndex = manager.getState().currentIndex;
125
- if (nextIndex === managerCurrentIndex) {
210
+ if (isPageTransitioningRef.current && !shouldRunNavigationDuringTransition(resolution, options)) {
211
+ return;
212
+ }
213
+ const targetVirtualIndex = resolution.kind === 'step' ? centerVirtualIndexRef.current + resolution.direction : getVirtualIndexForLogicalIndex(resolution.targetIndex, centerVirtualIndexRef.current, dataLengthRef.current, enableLoopRef.current);
214
+ if (targetVirtualIndex === null) {
215
+ return;
216
+ }
217
+ if (resolution.kind !== 'step' || options?.animated === false) {
218
+ resetTransformStateImmediately();
219
+ snapToVirtualPage(targetVirtualIndex);
126
220
  return;
127
221
  }
128
- manager.setCurrentIndex(nextIndex);
129
- manager.notifyStateChange();
130
222
  resetTransformState();
131
- }, [dataLength, manager, resetTransformState]);
223
+ animateToVirtualPage(targetVirtualIndex);
224
+ }, [animateToVirtualPage, isPageTransitioningRef, resetTransformStateImmediately, resetTransformState, snapToVirtualPage]);
225
+ const navigateByDirection = useCallback(direction => {
226
+ const currentDataLength = dataLengthRef.current;
227
+ if (currentDataLength <= 0) {
228
+ return;
229
+ }
230
+ const nextIndex = currentIndexRef.current + direction;
231
+ if (nextIndex >= 0 && nextIndex < currentDataLength) {
232
+ navigateToIndex(nextIndex, undefined, direction);
233
+ return;
234
+ }
235
+ if (!enableLoopRef.current || currentDataLength <= 1) {
236
+ return;
237
+ }
238
+ navigateToIndex(direction === 1 ? 0 : currentDataLength - 1, undefined, direction);
239
+ }, [navigateToIndex]);
240
+ const navigateToNext = useCallback(() => {
241
+ navigateByDirection(1);
242
+ }, [navigateByDirection]);
243
+ const navigateToPrevious = useCallback(() => {
244
+ navigateByDirection(-1);
245
+ }, [navigateByDirection]);
246
+ useGestureViewerManagerBridge({
247
+ currentIndexRef,
248
+ dataLengthRef,
249
+ goToIndex: navigateToIndex,
250
+ goToNext: navigateToNext,
251
+ goToPrevious: navigateToPrevious,
252
+ hasRotationChangeListeners,
253
+ hasZoomChangeListeners,
254
+ height,
255
+ id,
256
+ managerRef,
257
+ maxZoomScale,
258
+ rotation,
259
+ scale,
260
+ translateX,
261
+ translateY,
262
+ width
263
+ });
132
264
  const emitZoomChange = useCallback((currentScale, prevScale) => {
133
265
  managerRef.current?.emitZoomChange(currentScale, prevScale);
134
266
  }, []);
135
267
  const emitRotationChange = useCallback((currentRotation, prevRotation) => {
136
268
  managerRef.current?.emitRotationChange(currentRotation, prevRotation);
137
269
  }, []);
138
- const onAnimationComplete = useCallback(() => {
270
+ const finishTriggerOpening = useCallback(() => {
271
+ setIsTriggerOpening(false);
139
272
  onAnimationCompleteRef.current?.();
140
273
  }, []);
141
274
  useAnimatedReaction(() => scale.get(), (currentScale, previousScale) => {
142
- if (currentScale !== previousScale) {
275
+ if (currentScale !== previousScale && hasZoomChangeListeners.get()) {
143
276
  scheduleOnRN(emitZoomChange, currentScale, previousScale);
144
277
  }
145
- scheduleOnRN(setIsZoomed, currentScale > 1);
278
+ const currentIsZoomed = currentScale > 1;
279
+ const previousIsZoomed = (previousScale ?? 1) > 1;
280
+ if (previousScale === null || currentIsZoomed !== previousIsZoomed) {
281
+ scheduleOnRN(setIsZoomed, currentIsZoomed);
282
+ }
146
283
  });
147
284
  useAnimatedReaction(() => rotation.get(), (currentRotation, previousRotation) => {
148
- if (currentRotation !== previousRotation) {
285
+ if (currentRotation !== previousRotation && hasRotationChangeListeners.get()) {
149
286
  scheduleOnRN(emitRotationChange, currentRotation, previousRotation);
150
287
  }
151
- scheduleOnRN(setIsRotated, currentRotation % 360 !== 0);
288
+ const currentIsRotated = currentRotation % 360 !== 0;
289
+ const previousIsRotated = (previousRotation ?? 0) % 360 !== 0;
290
+ if (previousRotation === null || currentIsRotated !== previousIsRotated) {
291
+ scheduleOnRN(setIsRotated, currentIsRotated);
292
+ }
152
293
  });
153
294
  useEffect(() => {
154
- let unsubscribe;
155
- const unsubscribeFromRegistry = registry.subscribeToManager(id, managerInstance => {
156
- setManager(managerInstance);
157
- unsubscribe = managerInstance?.subscribe(state => {
158
- pendingIndexRef.current = state.currentIndex;
159
- setCurrentIndex(state.currentIndex);
160
- });
295
+ return registry.subscribeToActiveTrigger(id, node => {
296
+ setActiveTriggerNode(node);
297
+ if (node) {
298
+ setIsTriggerOpening(true);
299
+ }
161
300
  });
162
- return () => {
163
- unsubscribeFromRegistry();
164
- unsubscribe?.();
165
- };
166
301
  }, [id]);
167
302
  useEffect(() => {
168
- return registry.subscribeToActiveTrigger(id, setActiveTriggerNode);
169
- }, [id]);
303
+ dataRef.current = data;
304
+ }, [data]);
170
305
  useEffect(() => {
171
- pendingIndexRef.current = initialIndex;
172
- if (!manager) {
173
- return;
174
- }
175
- manager.setDataLength(dataLength);
176
- manager.setEnableHorizontalSwipe(enableHorizontalSwipe);
177
- manager.setCurrentIndex(initialIndex);
178
- manager.setWidth(width + itemSpacing);
179
- manager.setHeight(height);
180
- manager.setZoomSharedValues(scale, translateX, translateY, maxZoomScale);
181
- manager.setResetTransformCallback(resetTransformState);
182
- manager.setRotation(rotation);
183
- manager.setEnableLoop(enableLoop);
184
- manager.notifyStateChange();
185
- }, [dataLength, enableHorizontalSwipe, initialIndex, manager, width, itemSpacing, maxZoomScale, enableLoop, scale, height, resetTransformState, translateX, translateY, rotation]);
306
+ isZoomedRef.current = isZoomed;
307
+ }, [isZoomed]);
186
308
  useEffect(() => {
187
- if (!manager || !listRef.current) {
188
- return;
189
- }
190
- manager.setListRef(listRef.current);
191
- }, [manager]);
309
+ isRotatedRef.current = isRotated;
310
+ }, [isRotated]);
192
311
  useEffect(() => {
193
- translateY.set(0);
194
- translateX.set(0);
195
- scale.set(1);
196
- backdropOpacity.set(1);
197
- startScale.set(1);
198
- rotation.set(0);
199
- if (adjustedInitialIndex <= 0 || !listRef.current) {
200
- return;
201
- }
202
- return scheduleInitialScroll(() => {
203
- scrollTo(adjustedInitialIndex, false);
204
- });
205
- }, [adjustedInitialIndex, translateY, backdropOpacity, translateX, scale, startScale, rotation, scrollTo]);
312
+ isPinchingRef.current = isPinching;
313
+ }, [isPinching]);
314
+ useEffect(() => {
315
+ onSingleTapRef.current = onSingleTap;
316
+ }, [onSingleTap]);
206
317
  useEffect(() => {
207
318
  onAnimationCompleteRef.current = triggerAnimation?.onAnimationComplete;
208
319
  }, [triggerAnimation?.onAnimationComplete]);
320
+ useLayoutEffect(() => {
321
+ const initialIndexChanged = previousInitialIndexRef.current !== initialIndex;
322
+ const shouldResolveInitialIndex = initialIndexChanged || !hasResolvedInitialIndexRef.current && dataLength > 0;
323
+ previousInitialIndexRef.current = initialIndex;
324
+ dataLengthRef.current = dataLength;
325
+ enableLoopRef.current = enableLoop;
326
+ hasResolvedInitialIndexRef.current = dataLength > 0;
327
+ const nextIndex = shouldResolveInitialIndex ? clampIndex(initialIndex, dataLength) : clampIndex(currentIndexRef.current, dataLength);
328
+ const nextVirtualIndex = getVirtualIndexForLogicalIndex(nextIndex, centerVirtualIndexRef.current, dataLength, enableLoop) ?? nextIndex;
329
+ const previousIndex = currentIndexRef.current;
330
+ const previousVirtualIndex = centerVirtualIndexRef.current;
331
+ const shouldSyncVirtualPage = isPageTransitioningRef.current || previousVirtualIndex !== nextVirtualIndex;
332
+ const shouldResetTransform = initialIndexChanged || previousIndex !== nextIndex || shouldSyncVirtualPage;
333
+ if (shouldSyncVirtualPage) {
334
+ snapToVirtualPage(nextVirtualIndex);
335
+ if (dataLength === 0) {
336
+ centerVirtualIndexRef.current = nextVirtualIndex;
337
+ setCenterVirtualIndex(nextVirtualIndex);
338
+ }
339
+ }
340
+ const shouldCommitIndexOutsidePaging = !shouldSyncVirtualPage || dataLength === 0;
341
+ if (shouldCommitIndexOutsidePaging && previousIndex !== nextIndex) {
342
+ commitCurrentIndex(nextIndex);
343
+ }
344
+ if (shouldCommitIndexOutsidePaging && previousIndex === nextIndex) {
345
+ managerRef.current?.notifyStateChange();
346
+ }
347
+ if (shouldResetTransform) {
348
+ resetTransformState();
349
+ }
350
+ }, [commitCurrentIndex, dataLength, enableLoop, initialIndex, isPageTransitioningRef, resetTransformState, snapToVirtualPage]);
209
351
  useEffect(() => {
210
- dataRef.current = data;
211
- }, [data]);
212
- useEffect(() => {
213
- managerRef.current = manager;
214
- }, [manager]);
352
+ return () => {
353
+ triggerRectRef.current = null;
354
+ clearPendingWebSingleTap();
355
+ };
356
+ }, [clearPendingWebSingleTap]);
215
357
  useEffect(() => {
216
- onSingleTapRef.current = onSingleTap;
217
- }, [onSingleTap]);
358
+ if (!autoPlay || dataLength <= 1 || isTriggerOpening || isZoomed || isRotated || isPinching || !enableLoop && currentIndex >= dataLength - 1) {
359
+ return;
360
+ }
361
+ const interval = Math.max(250, Math.floor(autoPlayInterval || 0));
362
+ if (!Number.isFinite(interval)) {
363
+ return;
364
+ }
365
+ const timer = setInterval(() => {
366
+ if (isPageTransitioningRef.current || isZoomedRef.current || isRotatedRef.current || isPinchingRef.current) {
367
+ return;
368
+ }
369
+ const nextIndex = currentIndexRef.current >= dataLengthRef.current - 1 ? enableLoopRef.current ? 0 : null : currentIndexRef.current + 1;
370
+ if (nextIndex === null) {
371
+ return;
372
+ }
373
+ navigateToIndex(nextIndex);
374
+ }, interval);
375
+ return () => clearInterval(timer);
376
+ }, [autoPlay, autoPlayInterval, currentIndex, dataLength, enableLoop, isPinching, isRotated, isTriggerOpening, isZoomed, isPageTransitioningRef, navigateToIndex]);
218
377
  useEffect(() => {
219
378
  if (shouldStartTriggerAnimation && triggerRectRef.current) {
220
379
  const startX = triggerRectRef.current.x + triggerRectRef.current.width / 2 - width / 2;
@@ -226,7 +385,7 @@ export const useGestureViewer = ({
226
385
  triggerOpacity.set(0);
227
386
  triggerScale.set(withTiming(1, animationConfig, finished => {
228
387
  if (finished) {
229
- scheduleOnRN(onAnimationComplete);
388
+ scheduleOnRN(finishTriggerOpening);
230
389
  }
231
390
  }));
232
391
  triggerTranslateX.set(withTiming(0, animationConfig));
@@ -238,9 +397,15 @@ export const useGestureViewer = ({
238
397
  }));
239
398
  setShouldStartTriggerAnimation(false);
240
399
  }
241
- }, [shouldStartTriggerAnimation, animationConfig, width, height, triggerOpacity, triggerScale, triggerTranslateX, triggerTranslateY, onAnimationComplete]);
400
+ }, [shouldStartTriggerAnimation, animationConfig, width, height, triggerOpacity, triggerScale, triggerTranslateX, triggerTranslateY, finishTriggerOpening]);
242
401
  useEffect(() => {
243
- if (!activeTriggerNode || typeof activeTriggerNode.measure !== 'function') {
402
+ if (!activeTriggerNode) {
403
+ return;
404
+ }
405
+ if (typeof activeTriggerNode.measure !== 'function') {
406
+ setIsTriggerOpening(false);
407
+ triggerOpacity.set(1);
408
+ registry.clearActiveTriggerNode(id);
244
409
  return;
245
410
  }
246
411
  activeTriggerNode.measure((_x, _y, measuredWidth, measuredHeight, pageX, pageY) => {
@@ -251,6 +416,8 @@ export const useGestureViewer = ({
251
416
  y: pageY
252
417
  };
253
418
  if (!isValidTriggerRect(nextTriggerRect)) {
419
+ setIsTriggerOpening(false);
420
+ triggerOpacity.set(1);
254
421
  registry.clearActiveTriggerNode(id);
255
422
  return;
256
423
  }
@@ -259,12 +426,7 @@ export const useGestureViewer = ({
259
426
  setShouldStartTriggerAnimation(true);
260
427
  registry.clearActiveTriggerNode(id);
261
428
  });
262
- }, [activeTriggerNode, id, isValidTriggerRect, triggerOpacity]);
263
- useEffect(() => {
264
- return () => {
265
- triggerRectRef.current = null;
266
- };
267
- }, []);
429
+ }, [activeTriggerNode, id, triggerOpacity]);
268
430
  const animateDismissToRect = useCallback(rect => {
269
431
  const endX = rect.x + rect.width / 2 - width / 2;
270
432
  const endY = rect.y + rect.height / 2 - height / 2;
@@ -284,6 +446,9 @@ export const useGestureViewer = ({
284
446
  }
285
447
  }, [onDismiss]);
286
448
  const handleDismiss = useCallback(() => {
449
+ if (isPageTransitioningRef.current) {
450
+ return;
451
+ }
287
452
  onDismissStart?.();
288
453
  const dismissTargetIndex = pendingIndexRef.current;
289
454
  const indexedTriggerNode = registry.getIndexedTriggerNode(id, dismissTargetIndex);
@@ -312,30 +477,98 @@ export const useGestureViewer = ({
312
477
  return;
313
478
  }
314
479
  dismissWithoutTrigger();
315
- }, [animateDismissToRect, dismissWithoutTrigger, id, isValidTriggerRect, onDismissStart]);
480
+ }, [animateDismissToRect, dismissWithoutTrigger, id, isPageTransitioningRef, onDismissStart]);
316
481
  const dismissGesture = useMemo(() => {
317
- const canDismiss = !isZoomed && dismissOptions.enabled;
482
+ const canDismiss = !isTriggerOpening && !isZoomed && dismissOptions.enabled;
318
483
  return Gesture.Pan().minDistance(10).averageTouches(true).activeCursor('grabbing').activeOffsetY([-10, 10]).failOffsetX([-10, 10]).withRef(dismissGestureRef).enabled(canDismiss).onUpdate(event => {
484
+ if (pageTransitionLocked.get()) {
485
+ return;
486
+ }
319
487
  translateY.set(event.translationY / dismissOptions.resistance);
320
488
  }).onEnd(event => {
489
+ if (pageTransitionLocked.get()) {
490
+ translateY.set(withSpring(0, PAGE_SPRING_CONFIG));
491
+ return;
492
+ }
321
493
  if (canDismiss && shouldDismissByDirection(event.translationY, dismissOptions.threshold, dismissOptions.direction)) {
322
494
  scheduleOnRN(handleDismiss);
323
495
  return;
324
496
  }
325
- translateY.set(withSpring(0, {
326
- damping: 50,
327
- energyThreshold: 6e-9,
328
- mass: 4,
329
- overshootClamping: false,
330
- stiffness: 600
331
- }));
497
+ translateY.set(withSpring(0, PAGE_SPRING_CONFIG));
332
498
  });
333
- }, [translateY, dismissOptions, handleDismiss, isZoomed]);
499
+ }, [translateY, dismissOptions, handleDismiss, isTriggerOpening, isZoomed, pageTransitionLocked]);
500
+ const getCurrentTapTarget = useCallback(() => {
501
+ const index = pendingIndexRef.current;
502
+ if (index < 0 || index >= dataRef.current.length || !(index in dataRef.current)) {
503
+ return null;
504
+ }
505
+ return {
506
+ index,
507
+ item: dataRef.current[index]
508
+ };
509
+ }, []);
510
+ const isWebInteractionLocked = useCallback(() => isPageTransitioningRef.current || pageTransitionLocked.get(), [isPageTransitioningRef, pageTransitionLocked]);
511
+ const emitSingleTap = useCallback((x, y, tapTarget) => {
512
+ if (isPageTransitioningRef.current) {
513
+ return;
514
+ }
515
+ const target = tapTarget ?? getCurrentTapTarget();
516
+ if (!target) {
517
+ return;
518
+ }
519
+ const {
520
+ index: targetIndex,
521
+ item: targetItem
522
+ } = target;
523
+ if (targetIndex < 0) {
524
+ return;
525
+ }
526
+ managerRef.current?.emitTap({
527
+ kind: 'single',
528
+ x,
529
+ y,
530
+ index: targetIndex
531
+ });
532
+ onSingleTapRef.current?.({
533
+ x,
534
+ y,
535
+ index: targetIndex,
536
+ item: targetItem
537
+ });
538
+ }, [getCurrentTapTarget, isPageTransitioningRef]);
539
+ const singleTapGesture = useMemo(() => Gesture.Tap().enabled(enableNativeTapGestures).numberOfTaps(1).onEnd((event, success) => {
540
+ if (!success || pageTransitionLocked.get()) {
541
+ return;
542
+ }
543
+ scheduleOnRN(emitSingleTap, event.x, event.y);
544
+ }), [emitSingleTap, pageTransitionLocked]);
545
+ const doubleTapGesture = useMemo(() => Gesture.Tap().enabled(enableDoubleTapZoom && enableNativeTapGestures).numberOfTaps(2).onEnd(event => {
546
+ if (pageTransitionLocked.get()) {
547
+ return;
548
+ }
549
+ applyTapZoomAtPoint({
550
+ x: event.x,
551
+ y: event.y,
552
+ width,
553
+ height,
554
+ maxZoomScale,
555
+ scale,
556
+ translateX,
557
+ translateY
558
+ });
559
+ }), [enableDoubleTapZoom, height, maxZoomScale, pageTransitionLocked, scale, translateX, translateY, width]);
560
+ const tapGesture = useMemo(() => Gesture.Exclusive(doubleTapGesture, singleTapGesture), [doubleTapGesture, singleTapGesture]);
334
561
  const zoomPinchGesture = useMemo(() => Gesture.Pinch().enabled(enablePinchZoom).onTouchesDown(event => {
562
+ if (pageTransitionLocked.get()) {
563
+ return;
564
+ }
335
565
  if (event.numberOfTouches === 2) {
336
566
  scheduleOnRN(setIsPinching, true);
337
567
  }
338
568
  }).onStart(event => {
569
+ if (pageTransitionLocked.get()) {
570
+ return;
571
+ }
339
572
  startScale.set(scale.get());
340
573
  initialTranslateX.set(translateX.get());
341
574
  initialTranslateY.set(translateY.get());
@@ -345,6 +578,9 @@ export const useGestureViewer = ({
345
578
  lastFocalY.set(event.focalY);
346
579
  hasActiveFocal.set(false);
347
580
  }).onUpdate(event => {
581
+ if (pageTransitionLocked.get()) {
582
+ return;
583
+ }
348
584
  const initialScale = startScale.get();
349
585
  const newScale = initialScale * event.scale;
350
586
  scale.set(newScale);
@@ -398,6 +634,9 @@ export const useGestureViewer = ({
398
634
  translateX.set(constrainedTranslateX);
399
635
  translateY.set(constrainedTranslateY);
400
636
  }).onEnd(() => {
637
+ if (pageTransitionLocked.get()) {
638
+ return;
639
+ }
401
640
  const currentScale = scale.get();
402
641
  if (currentScale > maxZoomScale) {
403
642
  scale.set(withTiming(maxZoomScale, {
@@ -443,11 +682,17 @@ export const useGestureViewer = ({
443
682
  }).onFinalize(() => {
444
683
  hasActiveFocal.set(false);
445
684
  scheduleOnRN(setIsPinching, false);
446
- }), [scale, enablePinchZoom, maxZoomScale, translateX, translateY, startScale, initialTranslateX, initialTranslateY, width, height, constrainTranslation, lastFocalX, lastFocalY, startFocalX, startFocalY, hasActiveFocal]);
685
+ }), [scale, enablePinchZoom, maxZoomScale, translateX, translateY, startScale, initialTranslateX, initialTranslateY, width, height, constrainTranslation, lastFocalX, lastFocalY, startFocalX, startFocalY, hasActiveFocal, pageTransitionLocked]);
447
686
  const zoomPanGesture = useMemo(() => Gesture.Pan().enabled(enablePanWhenZoomed && isZoomed).activeCursor('grabbing').averageTouches(true).onBegin(() => {
687
+ if (pageTransitionLocked.get()) {
688
+ return;
689
+ }
448
690
  initialTranslateX.set(translateX.get());
449
691
  initialTranslateY.set(translateY.get());
450
692
  }).onUpdate(event => {
693
+ if (pageTransitionLocked.get()) {
694
+ return;
695
+ }
451
696
  const currentScale = scale.get();
452
697
  if (currentScale > 1) {
453
698
  const newTranslateX = initialTranslateX.get() + event.translationX;
@@ -463,50 +708,22 @@ export const useGestureViewer = ({
463
708
  translateX.set(constrainedTranslateX);
464
709
  translateY.set(constrainedTranslateY);
465
710
  }
466
- }), [translateX, translateY, enablePanWhenZoomed, isZoomed, scale, initialTranslateX, initialTranslateY, constrainTranslation]);
467
- const emitSingleTap = useCallback((x, y) => {
468
- const index = pendingIndexRef.current;
469
- const currentData = dataRef.current;
470
- if (index < 0 || index >= currentData.length) {
471
- return;
472
- }
473
- managerRef.current?.emitTap({
474
- kind: 'single',
475
- x,
476
- y,
477
- index
478
- });
479
- const item = currentData[index];
480
- if (item === undefined) {
481
- return;
482
- }
483
- onSingleTapRef.current?.({
484
- x,
485
- y,
486
- index,
487
- item
488
- });
489
- }, []);
490
- const singleTapGesture = useMemo(() => Gesture.Tap().enabled(Platform.OS !== 'web').numberOfTaps(1).onEnd((event, success) => {
491
- if (!success) {
492
- return;
493
- }
494
- scheduleOnRN(emitSingleTap, event.x, event.y);
495
- }), [emitSingleTap]);
496
- const doubleTapGesture = useMemo(() => Gesture.Tap().enabled(enableDoubleTapZoom && Platform.OS !== 'web').numberOfTaps(2).onEnd(event => {
497
- applyTapZoomAtPoint({
498
- x: event.x,
499
- y: event.y,
500
- width,
501
- height,
502
- maxZoomScale,
503
- scale,
504
- translateX,
505
- translateY
506
- });
507
- }), [enableDoubleTapZoom, height, maxZoomScale, scale, translateX, translateY, width]);
508
- const tapGesture = useMemo(() => Gesture.Exclusive(doubleTapGesture, singleTapGesture), [doubleTapGesture, singleTapGesture]);
711
+ }), [translateX, translateY, enablePanWhenZoomed, isZoomed, scale, initialTranslateX, initialTranslateY, constrainTranslation, pageTransitionLocked]);
509
712
  const zoomGesture = useMemo(() => Gesture.Race(zoomPinchGesture, Gesture.Exclusive(zoomPanGesture, tapGesture)), [zoomPinchGesture, zoomPanGesture, tapGesture]);
713
+ const onWebClick = useWebClickHandler({
714
+ clearPendingWebSingleTap,
715
+ emitSingleTap,
716
+ enableDoubleTapZoom,
717
+ getCurrentTapTarget,
718
+ height,
719
+ maxZoomScale,
720
+ scale,
721
+ scheduleWebSingleTap,
722
+ isInteractionLocked: isWebInteractionLocked,
723
+ translateX,
724
+ translateY,
725
+ width
726
+ });
510
727
  const animatedStyle = useAnimatedStyle(() => ({
511
728
  opacity: triggerOpacity.get(),
512
729
  transform: [{
@@ -538,53 +755,22 @@ export const useGestureViewer = ({
538
755
  opacity: baseOpacity * dismissOpacity
539
756
  };
540
757
  }, [dismissOptions.direction, dismissOptions.fadeBackdrop]);
541
- const nativeScrollGesture = useMemo(() => {
542
- return Gesture.Native().requireExternalGestureToFail(dismissGestureRef);
543
- }, []);
544
- const {
545
- onMomentumScrollEnd,
546
- onScroll,
547
- onScrollBeginDrag,
548
- onWebClick
549
- } = useGestureViewerPaging({
550
- adjustedInitialIndex,
551
- autoPlay,
552
- autoPlayInterval,
553
- currentIndex,
554
- dataLength,
555
- enableDoubleTapZoom,
556
- enableHorizontalSwipe,
557
- enableLoop,
558
- height,
559
- isRotated,
560
- isZoomed,
561
- itemSpacing,
562
- manager,
563
- maxZoomScale,
564
- onSingleTap: emitSingleTap,
565
- scale,
566
- scrollTo,
567
- syncCurrentIndex,
568
- syncPendingIndex,
569
- translateX,
570
- translateY,
571
- width
572
- });
573
758
  return {
574
759
  animatedStyle,
575
760
  backdropStyle,
761
+ centerVirtualIndex: renderCenterVirtualIndex,
762
+ currentIndex: renderCurrentIndex,
576
763
  dataLength,
577
764
  dismissGesture,
578
765
  handleDismiss,
766
+ horizontalPagingGesture,
579
767
  isPinching,
580
768
  isRotated,
581
769
  isZoomed,
582
- listRef,
583
- nativeScrollGesture,
584
770
  onWebClick,
585
- onMomentumScrollEnd,
586
- onScroll,
587
- onScrollBeginDrag,
771
+ pageStride,
772
+ renderWindowSlots,
773
+ visualPage,
588
774
  zoomGesture
589
775
  };
590
776
  };