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
@@ -11,7 +11,7 @@ import type { GestureViewerController } from './types';
11
11
  * @returns Methods for controlling the viewer
12
12
  *
13
13
  * **Available methods:**
14
- * - `goToIndex(index: number)` - Navigate to specific index (0 to totalCount-1)
14
+ * - `goToIndex(index: number, options?: { animated?: boolean })` - Navigate to specific index (0 to totalCount-1)
15
15
  * - `goToPrevious()` - Navigate to previous item
16
16
  * - `goToNext()` - Navigate to next item
17
17
  * - `zoomIn(multiplier?: number)` - Zoom in (default: 0.25)
@@ -58,8 +58,8 @@ export const useGestureViewerController = (id = 'default'): GestureViewerControl
58
58
 
59
59
  return useMemo<GestureViewerController>(
60
60
  () => ({
61
- goToIndex: (index) => {
62
- managerRef.current?.goToIndex(index);
61
+ goToIndex: (index, options) => {
62
+ managerRef.current?.goToIndex(index, options);
63
63
  },
64
64
  goToPrevious: () => {
65
65
  managerRef.current?.goToPrevious();
@@ -0,0 +1,118 @@
1
+ import { useEffect, useState, type RefObject } from 'react';
2
+ import type { SharedValue } from 'react-native-reanimated';
3
+
4
+ import type GestureViewerManager from './GestureViewerManager';
5
+ import type { GestureViewerNavigationOptions } from './GestureViewerManager';
6
+ import { registry } from './GestureViewerRegistry';
7
+
8
+ type UseGestureViewerManagerBridgeOptions = {
9
+ currentIndexRef: RefObject<number>;
10
+ dataLengthRef: RefObject<number>;
11
+ goToIndex: (index: number, options?: GestureViewerNavigationOptions) => void;
12
+ goToNext: () => void;
13
+ goToPrevious: () => void;
14
+ hasRotationChangeListeners: SharedValue<boolean>;
15
+ hasZoomChangeListeners: SharedValue<boolean>;
16
+ height: number;
17
+ id: string;
18
+ managerRef: RefObject<GestureViewerManager | null>;
19
+ maxZoomScale: number;
20
+ rotation: SharedValue<number>;
21
+ scale: SharedValue<number>;
22
+ translateX: SharedValue<number>;
23
+ translateY: SharedValue<number>;
24
+ width: number;
25
+ };
26
+
27
+ /**
28
+ * Owns the registry-to-manager bridge side effects for one viewer instance.
29
+ */
30
+ export function useGestureViewerManagerBridge({
31
+ currentIndexRef,
32
+ dataLengthRef,
33
+ goToIndex,
34
+ goToNext,
35
+ goToPrevious,
36
+ hasRotationChangeListeners,
37
+ hasZoomChangeListeners,
38
+ height,
39
+ id,
40
+ managerRef,
41
+ maxZoomScale,
42
+ rotation,
43
+ scale,
44
+ translateX,
45
+ translateY,
46
+ width,
47
+ }: UseGestureViewerManagerBridgeOptions) {
48
+ const [manager, setManager] = useState<GestureViewerManager | null>(null);
49
+
50
+ useEffect(() => {
51
+ return registry.subscribeToManager(id, (managerInstance) => {
52
+ managerRef.current = managerInstance;
53
+ setManager(managerInstance);
54
+ });
55
+ }, [id, managerRef]);
56
+
57
+ useEffect(() => {
58
+ if (!manager) {
59
+ return;
60
+ }
61
+
62
+ manager.setWidth(width);
63
+ manager.setHeight(height);
64
+ manager.setZoomSharedValues(scale, translateX, translateY, maxZoomScale);
65
+ manager.setRotation(rotation);
66
+ }, [height, manager, maxZoomScale, rotation, scale, translateX, translateY, width]);
67
+
68
+ useEffect(() => {
69
+ if (!manager) {
70
+ hasZoomChangeListeners.set(false);
71
+ hasRotationChangeListeners.set(false);
72
+ return;
73
+ }
74
+
75
+ return manager.subscribeToEventListenerPresence((eventType, hasListeners) => {
76
+ if (eventType === 'zoomChange') {
77
+ hasZoomChangeListeners.set(hasListeners);
78
+ return;
79
+ }
80
+
81
+ if (eventType === 'rotationChange') {
82
+ hasRotationChangeListeners.set(hasListeners);
83
+ }
84
+ });
85
+ }, [hasRotationChangeListeners, hasZoomChangeListeners, manager]);
86
+
87
+ useEffect(() => {
88
+ if (!manager) {
89
+ return;
90
+ }
91
+
92
+ manager.setStateReader(() => ({
93
+ currentIndex: currentIndexRef.current,
94
+ totalCount: dataLengthRef.current,
95
+ }));
96
+ manager.notifyStateChange();
97
+
98
+ return () => {
99
+ manager.setStateReader(null);
100
+ };
101
+ }, [currentIndexRef, dataLengthRef, manager]);
102
+
103
+ useEffect(() => {
104
+ if (!manager) {
105
+ return;
106
+ }
107
+
108
+ manager.setNavigationAdapter({
109
+ goToIndex,
110
+ goToNext,
111
+ goToPrevious,
112
+ });
113
+
114
+ return () => {
115
+ manager.setNavigationAdapter(null);
116
+ };
117
+ }, [goToIndex, goToNext, goToPrevious, manager]);
118
+ }
@@ -1,128 +1,307 @@
1
- import { useCallback, useEffect } from 'react';
2
- import type { NativeScrollEvent, NativeSyntheticEvent } from 'react-native';
1
+ import { useCallback, useMemo, useRef } from 'react';
2
+ import { Gesture } from 'react-native-gesture-handler';
3
+ import { cancelAnimation, useSharedValue, withSpring, withTiming } from 'react-native-reanimated';
4
+ import { scheduleOnRN } from 'react-native-worklets';
3
5
 
4
- import type {
5
- UseGestureViewerPagingArgs,
6
- UseGestureViewerPagingResult,
7
- } from './useGestureViewerPaging.types';
8
- import { getLoopAdjustedIndex } from './utils';
6
+ import {
7
+ EDGE_RESISTANCE,
8
+ PAGE_SPRING_CONFIG,
9
+ PAGE_TRANSITION_CONFIG,
10
+ } from './gestureViewerAnimation';
11
+ import {
12
+ applyHorizontalEdgeResistance,
13
+ resolveHorizontalPagingTarget,
14
+ resolveHorizontalSwipeDirection,
15
+ } from './gestureViewerPaging';
9
16
 
17
+ type UseGestureViewerPagingOptions = {
18
+ centerVirtualIndex: number;
19
+ clearPendingWebSingleTap: () => void;
20
+ commitVirtualIndexOnly: (targetVirtualIndex: number) => void;
21
+ currentIndex: number;
22
+ dataLength: number;
23
+ enableLoop: boolean;
24
+ horizontalSwipeDistanceThresholdRatio: number;
25
+ horizontalSwipeEnabled: boolean;
26
+ horizontalSwipeVelocityThreshold: number;
27
+ initialPage: number;
28
+ isPinching: boolean;
29
+ isRotated: boolean;
30
+ isTriggerOpening: boolean;
31
+ isZoomed: boolean;
32
+ pageStride: number;
33
+ width: number;
34
+ };
35
+
36
+ /**
37
+ * Owns horizontal paging shared values, transition state, and paging commands.
38
+ */
10
39
  export function useGestureViewerPaging({
11
- autoPlay,
12
- autoPlayInterval,
40
+ centerVirtualIndex,
41
+ clearPendingWebSingleTap,
42
+ commitVirtualIndexOnly,
13
43
  currentIndex,
14
44
  dataLength,
15
- enableHorizontalSwipe,
16
45
  enableLoop,
46
+ horizontalSwipeDistanceThresholdRatio,
47
+ horizontalSwipeEnabled,
48
+ horizontalSwipeVelocityThreshold,
49
+ initialPage,
50
+ isPinching,
17
51
  isRotated,
52
+ isTriggerOpening,
18
53
  isZoomed,
19
- itemSpacing,
20
- manager,
21
- scrollTo,
22
- syncCurrentIndex,
23
- syncPendingIndex,
54
+ pageStride,
24
55
  width,
25
- }: UseGestureViewerPagingArgs): UseGestureViewerPagingResult {
26
- useEffect(() => {
27
- if (
28
- !autoPlay ||
29
- !manager ||
30
- dataLength <= 1 ||
31
- isZoomed ||
32
- isRotated ||
33
- (!enableLoop && currentIndex === dataLength - 1)
34
- ) {
35
- return;
36
- }
37
-
38
- const intervalMs = Math.max(250, Math.floor(autoPlayInterval || 0));
39
-
40
- if (!Number.isFinite(intervalMs)) {
41
- return;
42
- }
43
-
44
- const interval = setInterval(() => {
45
- if (isZoomed || isRotated) {
46
- return;
47
- }
56
+ }: UseGestureViewerPagingOptions) {
57
+ const visualPage = useSharedValue(initialPage);
58
+ const pagingStartPage = useSharedValue(initialPage);
59
+ const pagingAnimationActive = useSharedValue(false);
60
+ const pagingGestureActive = useSharedValue(false);
61
+ const pageTransitionLocked = useSharedValue(false);
62
+ const isPageTransitioningRef = useRef(false);
48
63
 
49
- manager.goToNext();
50
- }, intervalMs);
64
+ const setPageTransitioning = useCallback(
65
+ (nextTransitioning: boolean) => {
66
+ isPageTransitioningRef.current = nextTransitioning;
67
+ pageTransitionLocked.set(nextTransitioning);
51
68
 
52
- return () => clearInterval(interval);
53
- }, [
54
- autoPlay,
55
- autoPlayInterval,
56
- currentIndex,
57
- dataLength,
58
- enableLoop,
59
- isRotated,
60
- isZoomed,
61
- manager,
62
- ]);
63
-
64
- const onMomentumScrollEnd = useCallback(
65
- (event: NativeSyntheticEvent<NativeScrollEvent>) => {
66
- if (!enableHorizontalSwipe) {
67
- return;
69
+ if (nextTransitioning) {
70
+ clearPendingWebSingleTap();
68
71
  }
72
+ },
73
+ [clearPendingWebSingleTap, pageTransitionLocked],
74
+ );
69
75
 
70
- const { contentOffset } = event.nativeEvent;
71
- const scrollIndex = Math.round(contentOffset.x / (width + itemSpacing));
76
+ const clearPagingFlags = useCallback(() => {
77
+ pagingAnimationActive.set(false);
78
+ pagingGestureActive.set(false);
79
+ }, [pagingAnimationActive, pagingGestureActive]);
72
80
 
73
- const isLoopHandled = manager?.handleMomentumScrollEnd(scrollIndex);
81
+ const cancelPagingInteraction = useCallback(() => {
82
+ clearPendingWebSingleTap();
83
+ cancelAnimation(visualPage);
84
+ clearPagingFlags();
85
+ setPageTransitioning(false);
86
+ }, [clearPendingWebSingleTap, clearPagingFlags, setPageTransitioning, visualPage]);
74
87
 
75
- if (isLoopHandled) {
76
- return;
77
- }
88
+ const snapToVirtualPage = useCallback(
89
+ (targetVirtualIndex: number) => {
90
+ clearPendingWebSingleTap();
91
+ cancelAnimation(visualPage);
92
+ clearPagingFlags();
93
+ visualPage.set(targetVirtualIndex);
94
+ commitVirtualIndexOnly(targetVirtualIndex);
95
+ setPageTransitioning(false);
96
+ },
97
+ [
98
+ clearPendingWebSingleTap,
99
+ clearPagingFlags,
100
+ commitVirtualIndexOnly,
101
+ setPageTransitioning,
102
+ visualPage,
103
+ ],
104
+ );
78
105
 
79
- const { realIndex, needsJump, jumpToIndex } = getLoopAdjustedIndex(
80
- scrollIndex,
81
- dataLength,
82
- enableLoop,
83
- );
106
+ const completeAnimatedVirtualPage = useCallback(
107
+ (targetVirtualIndex: number) => {
108
+ commitVirtualIndexOnly(targetVirtualIndex);
109
+ setPageTransitioning(false);
110
+ },
111
+ [commitVirtualIndexOnly, setPageTransitioning],
112
+ );
84
113
 
85
- if (needsJump && jumpToIndex !== undefined) {
86
- scrollTo(jumpToIndex, false);
87
- }
114
+ const cancelAnimatedVirtualPage = useCallback(() => {
115
+ setPageTransitioning(false);
116
+ }, [setPageTransitioning]);
117
+
118
+ const animateToVirtualPage = useCallback(
119
+ (targetVirtualIndex: number) => {
120
+ cancelAnimation(visualPage);
121
+ clearPagingFlags();
122
+ pagingAnimationActive.set(true);
123
+ setPageTransitioning(true);
124
+ visualPage.set(
125
+ withTiming(targetVirtualIndex, PAGE_TRANSITION_CONFIG, (finished) => {
126
+ pagingAnimationActive.set(false);
88
127
 
89
- syncCurrentIndex(realIndex);
128
+ if (finished) {
129
+ scheduleOnRN(completeAnimatedVirtualPage, targetVirtualIndex);
130
+ return;
131
+ }
132
+
133
+ scheduleOnRN(cancelAnimatedVirtualPage);
134
+ }),
135
+ );
90
136
  },
91
137
  [
92
- dataLength,
93
- enableHorizontalSwipe,
94
- enableLoop,
95
- itemSpacing,
96
- manager,
97
- scrollTo,
98
- syncCurrentIndex,
99
- width,
138
+ cancelAnimatedVirtualPage,
139
+ clearPagingFlags,
140
+ completeAnimatedVirtualPage,
141
+ pagingAnimationActive,
142
+ setPageTransitioning,
143
+ visualPage,
100
144
  ],
101
145
  );
102
146
 
103
- const onScroll = useCallback(
104
- (event: NativeSyntheticEvent<NativeScrollEvent>) => {
105
- if (!enableHorizontalSwipe) {
106
- return;
107
- }
147
+ const horizontalPagingGesture = useMemo(() => {
148
+ const canSwipe =
149
+ horizontalSwipeEnabled &&
150
+ dataLength > 1 &&
151
+ !isTriggerOpening &&
152
+ !isZoomed &&
153
+ !isRotated &&
154
+ !isPinching &&
155
+ pageStride > 0;
156
+ const settleToCenter = () => {
157
+ 'worklet';
158
+ pagingAnimationActive.set(true);
159
+ visualPage.set(
160
+ withSpring(centerVirtualIndex, PAGE_SPRING_CONFIG, (finished) => {
161
+ pagingAnimationActive.set(false);
108
162
 
109
- const { contentOffset } = event.nativeEvent;
110
- const scrollIndex = Math.round(contentOffset.x / (width + itemSpacing));
111
- const { realIndex } = getLoopAdjustedIndex(scrollIndex, dataLength, enableLoop);
163
+ if (finished) {
164
+ scheduleOnRN(setPageTransitioning, false);
165
+ }
166
+ }),
167
+ );
168
+ };
112
169
 
113
- syncPendingIndex(realIndex);
114
- },
115
- [dataLength, enableHorizontalSwipe, enableLoop, itemSpacing, syncPendingIndex, width],
116
- );
170
+ return Gesture.Pan()
171
+ .minDistance(10)
172
+ .averageTouches(true)
173
+ .activeCursor('grabbing')
174
+ .activeOffsetX([-10, 10])
175
+ .failOffsetY([-10, 10])
176
+ .enabled(canSwipe)
177
+ .onStart(() => {
178
+ if (pageTransitionLocked.get()) {
179
+ return;
180
+ }
181
+
182
+ // Pan enters BEGAN for ordinary taps, so lock paging only after swipe activation.
183
+ cancelAnimation(visualPage);
184
+ pagingAnimationActive.set(false);
185
+ pagingGestureActive.set(true);
186
+ pagingStartPage.set(visualPage.get());
187
+ scheduleOnRN(setPageTransitioning, true);
188
+ })
189
+ .onUpdate((event) => {
190
+ if (!pagingGestureActive.get()) {
191
+ return;
192
+ }
193
+
194
+ const dragPageDelta = -event.translationX / pageStride;
195
+ const basePage = pagingStartPage.get();
196
+ const nextPage = applyHorizontalEdgeResistance(
197
+ basePage + dragPageDelta,
198
+ currentIndex,
199
+ dataLength,
200
+ centerVirtualIndex,
201
+ enableLoop,
202
+ EDGE_RESISTANCE,
203
+ );
204
+
205
+ visualPage.set(nextPage);
206
+ })
207
+ .onEnd((event) => {
208
+ if (!pagingGestureActive.get()) {
209
+ return;
210
+ }
211
+
212
+ pagingGestureActive.set(false);
213
+
214
+ const direction = resolveHorizontalSwipeDirection(
215
+ event.translationX,
216
+ event.velocityX,
217
+ width,
218
+ horizontalSwipeDistanceThresholdRatio,
219
+ horizontalSwipeVelocityThreshold,
220
+ );
221
+
222
+ if (direction === 0) {
223
+ settleToCenter();
224
+ return;
225
+ }
226
+
227
+ const pagingTarget = resolveHorizontalPagingTarget(
228
+ centerVirtualIndex,
229
+ currentIndex,
230
+ dataLength,
231
+ direction,
232
+ enableLoop,
233
+ );
234
+
235
+ if (pagingTarget.kind === 'settle') {
236
+ settleToCenter();
237
+ return;
238
+ }
239
+
240
+ const targetVirtualIndex = pagingTarget.targetVirtualIndex;
241
+
242
+ pagingAnimationActive.set(true);
243
+ visualPage.set(
244
+ withTiming(targetVirtualIndex, PAGE_TRANSITION_CONFIG, (finished) => {
245
+ pagingAnimationActive.set(false);
117
246
 
118
- const onScrollBeginDrag = useCallback(() => {
119
- manager?.handleScrollBeginDrag();
120
- }, [manager]);
247
+ if (finished) {
248
+ scheduleOnRN(completeAnimatedVirtualPage, targetVirtualIndex);
249
+ return;
250
+ }
251
+
252
+ scheduleOnRN(cancelAnimatedVirtualPage);
253
+ }),
254
+ );
255
+ })
256
+ .onFinalize(() => {
257
+ if (pagingAnimationActive.get()) {
258
+ return;
259
+ }
260
+
261
+ if (!pagingGestureActive.get()) {
262
+ return;
263
+ }
264
+
265
+ pagingGestureActive.set(false);
266
+
267
+ if (Math.abs(visualPage.get() - centerVirtualIndex) > 0.001) {
268
+ settleToCenter();
269
+ return;
270
+ }
271
+
272
+ scheduleOnRN(setPageTransitioning, false);
273
+ });
274
+ }, [
275
+ cancelAnimatedVirtualPage,
276
+ centerVirtualIndex,
277
+ completeAnimatedVirtualPage,
278
+ currentIndex,
279
+ dataLength,
280
+ enableLoop,
281
+ horizontalSwipeDistanceThresholdRatio,
282
+ horizontalSwipeEnabled,
283
+ horizontalSwipeVelocityThreshold,
284
+ isTriggerOpening,
285
+ isPinching,
286
+ isRotated,
287
+ isZoomed,
288
+ pageTransitionLocked,
289
+ pageStride,
290
+ pagingAnimationActive,
291
+ pagingGestureActive,
292
+ pagingStartPage,
293
+ setPageTransitioning,
294
+ visualPage,
295
+ width,
296
+ ]);
121
297
 
122
298
  return {
123
- onMomentumScrollEnd,
124
- onScroll,
125
- onScrollBeginDrag,
126
- onWebClick: undefined,
299
+ animateToVirtualPage,
300
+ cancelPagingInteraction,
301
+ horizontalPagingGesture,
302
+ isPageTransitioningRef,
303
+ pageTransitionLocked,
304
+ snapToVirtualPage,
305
+ visualPage,
127
306
  };
128
307
  }
@@ -0,0 +1,44 @@
1
+ import { useCallback } from 'react';
2
+ import type { SharedValue } from 'react-native-reanimated';
3
+
4
+ import type { ScheduleWebSingleTap } from './useWebSingleTapTimer';
5
+
6
+ export type WebTapTarget<ItemT> = {
7
+ index: number;
8
+ item: ItemT;
9
+ };
10
+
11
+ export type WebClickEvent = {
12
+ clientX: number;
13
+ clientY: number;
14
+ currentTarget: {
15
+ getBoundingClientRect: () => {
16
+ left: number;
17
+ top: number;
18
+ };
19
+ };
20
+ detail: number;
21
+ };
22
+
23
+ export type WebClickHandler = (event: WebClickEvent) => void;
24
+
25
+ export type EmitSingleTap<ItemT> = (x: number, y: number, tapTarget?: WebTapTarget<ItemT>) => void;
26
+
27
+ export type WebClickHandlerConfig<ItemT> = {
28
+ clearPendingWebSingleTap: () => void;
29
+ emitSingleTap: EmitSingleTap<ItemT>;
30
+ enableDoubleTapZoom: boolean;
31
+ getCurrentTapTarget: () => WebTapTarget<ItemT> | null;
32
+ height: number;
33
+ isInteractionLocked: () => boolean;
34
+ maxZoomScale: number;
35
+ scale: SharedValue<number>;
36
+ scheduleWebSingleTap: ScheduleWebSingleTap;
37
+ translateX: SharedValue<number>;
38
+ translateY: SharedValue<number>;
39
+ width: number;
40
+ };
41
+
42
+ export function useWebClickHandler<ItemT>(_config: WebClickHandlerConfig<ItemT>): WebClickHandler {
43
+ return useCallback(() => {}, []);
44
+ }