react-native-gesture-image-viewer 2.4.0 → 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 (133) hide show
  1. package/README.md +5 -3
  2. package/lib/module/GestureViewer.js +88 -146
  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/platformTapGestures.js +4 -0
  17. package/lib/module/platformTapGestures.js.map +1 -0
  18. package/lib/module/platformTapGestures.web.js +4 -0
  19. package/lib/module/platformTapGestures.web.js.map +1 -0
  20. package/lib/module/renderWindow.js +160 -0
  21. package/lib/module/renderWindow.js.map +1 -0
  22. package/lib/module/useGestureViewer.js +386 -206
  23. package/lib/module/useGestureViewer.js.map +1 -1
  24. package/lib/module/useGestureViewerController.js +3 -3
  25. package/lib/module/useGestureViewerController.js.map +1 -1
  26. package/lib/module/useGestureViewerManagerBridge.js +85 -0
  27. package/lib/module/useGestureViewerManagerBridge.js.map +1 -0
  28. package/lib/module/useGestureViewerPaging.js +147 -79
  29. package/lib/module/useGestureViewerPaging.js.map +1 -1
  30. package/lib/module/useWebClickHandler.js +7 -0
  31. package/lib/module/useWebClickHandler.js.map +1 -0
  32. package/lib/module/useWebClickHandler.web.js +85 -0
  33. package/lib/module/useWebClickHandler.web.js.map +1 -0
  34. package/lib/module/useWebSingleTapTimer.js +12 -0
  35. package/lib/module/useWebSingleTapTimer.js.map +1 -0
  36. package/lib/module/useWebSingleTapTimer.web.js +25 -0
  37. package/lib/module/useWebSingleTapTimer.web.js.map +1 -0
  38. package/lib/module/utils/index.js +0 -77
  39. package/lib/module/utils/index.js.map +1 -1
  40. package/lib/module/utils/tapZoom.js +4 -3
  41. package/lib/module/utils/tapZoom.js.map +1 -1
  42. package/lib/typescript/src/GestureViewer.d.ts +1 -1
  43. package/lib/typescript/src/GestureViewer.d.ts.map +1 -1
  44. package/lib/typescript/src/GestureViewerManager.d.ts +20 -20
  45. package/lib/typescript/src/GestureViewerManager.d.ts.map +1 -1
  46. package/lib/typescript/src/gestureViewerAnimation.d.ts +15 -0
  47. package/lib/typescript/src/gestureViewerAnimation.d.ts.map +1 -0
  48. package/lib/typescript/src/gestureViewerPaging.d.ts +13 -0
  49. package/lib/typescript/src/gestureViewerPaging.d.ts.map +1 -0
  50. package/lib/typescript/src/gestureViewerRenderWindow.d.ts +15 -0
  51. package/lib/typescript/src/gestureViewerRenderWindow.d.ts.map +1 -0
  52. package/lib/typescript/src/getWebContentProps.d.ts +3 -0
  53. package/lib/typescript/src/getWebContentProps.d.ts.map +1 -0
  54. package/lib/typescript/src/getWebContentProps.web.d.ts +5 -0
  55. package/lib/typescript/src/getWebContentProps.web.d.ts.map +1 -0
  56. package/lib/typescript/src/platformTapGestures.d.ts +2 -0
  57. package/lib/typescript/src/platformTapGestures.d.ts.map +1 -0
  58. package/lib/typescript/src/platformTapGestures.web.d.ts +2 -0
  59. package/lib/typescript/src/platformTapGestures.web.d.ts.map +1 -0
  60. package/lib/typescript/src/renderWindow.d.ts +44 -0
  61. package/lib/typescript/src/renderWindow.d.ts.map +1 -0
  62. package/lib/typescript/src/types.d.ts +36 -53
  63. package/lib/typescript/src/types.d.ts.map +1 -1
  64. package/lib/typescript/src/useGestureViewer.d.ts +9 -9
  65. package/lib/typescript/src/useGestureViewer.d.ts.map +1 -1
  66. package/lib/typescript/src/useGestureViewerController.d.ts +1 -1
  67. package/lib/typescript/src/useGestureViewerManagerBridge.d.ts +28 -0
  68. package/lib/typescript/src/useGestureViewerManagerBridge.d.ts.map +1 -0
  69. package/lib/typescript/src/useGestureViewerPaging.d.ts +31 -2
  70. package/lib/typescript/src/useGestureViewerPaging.d.ts.map +1 -1
  71. package/lib/typescript/src/useWebClickHandler.d.ts +35 -0
  72. package/lib/typescript/src/useWebClickHandler.d.ts.map +1 -0
  73. package/lib/typescript/src/useWebClickHandler.web.d.ts +36 -0
  74. package/lib/typescript/src/useWebClickHandler.web.d.ts.map +1 -0
  75. package/lib/typescript/src/useWebSingleTapTimer.d.ts +6 -0
  76. package/lib/typescript/src/useWebSingleTapTimer.d.ts.map +1 -0
  77. package/lib/typescript/src/useWebSingleTapTimer.web.d.ts +6 -0
  78. package/lib/typescript/src/useWebSingleTapTimer.web.d.ts.map +1 -0
  79. package/lib/typescript/src/utils/index.d.ts +0 -20
  80. package/lib/typescript/src/utils/index.d.ts.map +1 -1
  81. package/lib/typescript/src/utils/tapZoom.d.ts.map +1 -1
  82. package/package.json +1 -1
  83. package/src/GestureViewer.tsx +104 -206
  84. package/src/GestureViewerManager.ts +77 -122
  85. package/src/gestureViewerAnimation.ts +18 -0
  86. package/src/gestureViewerPaging.ts +116 -0
  87. package/src/gestureViewerRenderWindow.ts +56 -0
  88. package/src/getWebContentProps.ts +5 -0
  89. package/src/getWebContentProps.web.ts +5 -0
  90. package/src/platformTapGestures.ts +1 -0
  91. package/src/platformTapGestures.web.ts +1 -0
  92. package/src/renderWindow.ts +253 -0
  93. package/src/types.ts +34 -100
  94. package/src/useGestureViewer.ts +557 -271
  95. package/src/useGestureViewerController.ts +3 -3
  96. package/src/useGestureViewerManagerBridge.ts +118 -0
  97. package/src/useGestureViewerPaging.ts +277 -114
  98. package/src/useWebClickHandler.ts +44 -0
  99. package/src/useWebClickHandler.web.ts +146 -0
  100. package/src/useWebSingleTapTimer.ts +10 -0
  101. package/src/useWebSingleTapTimer.web.ts +30 -0
  102. package/src/utils/index.ts +0 -102
  103. package/src/utils/tapZoom.ts +4 -3
  104. package/lib/module/WebPagingFixStyle.js +0 -25
  105. package/lib/module/WebPagingFixStyle.js.map +0 -1
  106. package/lib/module/scheduleInitialScroll.js +0 -21
  107. package/lib/module/scheduleInitialScroll.js.map +0 -1
  108. package/lib/module/useGestureViewerPaging.types.js +0 -4
  109. package/lib/module/useGestureViewerPaging.types.js.map +0 -1
  110. package/lib/module/useGestureViewerPaging.web.js +0 -291
  111. package/lib/module/useGestureViewerPaging.web.js.map +0 -1
  112. package/lib/module/utils/FlashList.js +0 -9
  113. package/lib/module/utils/FlashList.js.map +0 -1
  114. package/lib/module/utils/webScroll.js +0 -70
  115. package/lib/module/utils/webScroll.js.map +0 -1
  116. package/lib/typescript/src/WebPagingFixStyle.d.ts +0 -6
  117. package/lib/typescript/src/WebPagingFixStyle.d.ts.map +0 -1
  118. package/lib/typescript/src/scheduleInitialScroll.d.ts +0 -3
  119. package/lib/typescript/src/scheduleInitialScroll.d.ts.map +0 -1
  120. package/lib/typescript/src/useGestureViewerPaging.types.d.ts +0 -42
  121. package/lib/typescript/src/useGestureViewerPaging.types.d.ts.map +0 -1
  122. package/lib/typescript/src/useGestureViewerPaging.web.d.ts +0 -3
  123. package/lib/typescript/src/useGestureViewerPaging.web.d.ts.map +0 -1
  124. package/lib/typescript/src/utils/FlashList.d.ts +0 -2
  125. package/lib/typescript/src/utils/FlashList.d.ts.map +0 -1
  126. package/lib/typescript/src/utils/webScroll.d.ts +0 -18
  127. package/lib/typescript/src/utils/webScroll.d.ts.map +0 -1
  128. package/src/WebPagingFixStyle.tsx +0 -25
  129. package/src/scheduleInitialScroll.ts +0 -36
  130. package/src/useGestureViewerPaging.types.ts +0 -42
  131. package/src/useGestureViewerPaging.web.ts +0 -453
  132. package/src/utils/FlashList.ts +0 -7
  133. 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,144 +1,307 @@
1
- import { useCallback, useEffect, useState } 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
- adjustedInitialIndex,
12
- autoPlay,
13
- autoPlayInterval,
40
+ centerVirtualIndex,
41
+ clearPendingWebSingleTap,
42
+ commitVirtualIndexOnly,
14
43
  currentIndex,
15
44
  dataLength,
16
- enableHorizontalSwipe,
17
45
  enableLoop,
46
+ horizontalSwipeDistanceThresholdRatio,
47
+ horizontalSwipeEnabled,
48
+ horizontalSwipeVelocityThreshold,
49
+ initialPage,
50
+ isPinching,
18
51
  isRotated,
52
+ isTriggerOpening,
19
53
  isZoomed,
20
- itemSpacing,
21
- manager,
22
- scrollTo,
23
- syncCurrentIndex,
24
- syncPendingIndex,
54
+ pageStride,
25
55
  width,
26
- }: UseGestureViewerPagingArgs): UseGestureViewerPagingResult {
27
- const [activeListIndex, setActiveListIndex] = useState(adjustedInitialIndex);
28
- const activeResetItemSpacing = adjustedInitialIndex > 0 ? itemSpacing : 0;
29
- const activeResetWidth = adjustedInitialIndex > 0 ? width : 0;
30
-
31
- useEffect(() => {
32
- setActiveListIndex(adjustedInitialIndex);
33
- }, [adjustedInitialIndex, activeResetItemSpacing, activeResetWidth, dataLength]);
34
-
35
- useEffect(() => {
36
- if (
37
- !autoPlay ||
38
- !manager ||
39
- dataLength <= 1 ||
40
- isZoomed ||
41
- isRotated ||
42
- (!enableLoop && currentIndex === dataLength - 1)
43
- ) {
44
- return;
45
- }
46
-
47
- const intervalMs = Math.max(250, Math.floor(autoPlayInterval || 0));
48
-
49
- if (!Number.isFinite(intervalMs)) {
50
- return;
51
- }
52
-
53
- const interval = setInterval(() => {
54
- if (isZoomed || isRotated) {
55
- return;
56
- }
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);
57
63
 
58
- manager.goToNext();
59
- }, intervalMs);
64
+ const setPageTransitioning = useCallback(
65
+ (nextTransitioning: boolean) => {
66
+ isPageTransitioningRef.current = nextTransitioning;
67
+ pageTransitionLocked.set(nextTransitioning);
60
68
 
61
- return () => clearInterval(interval);
62
- }, [
63
- autoPlay,
64
- autoPlayInterval,
65
- currentIndex,
66
- dataLength,
67
- enableLoop,
68
- isRotated,
69
- isZoomed,
70
- manager,
71
- ]);
72
-
73
- const onMomentumScrollEnd = useCallback(
74
- (event: NativeSyntheticEvent<NativeScrollEvent>) => {
75
- if (!enableHorizontalSwipe) {
76
- return;
69
+ if (nextTransitioning) {
70
+ clearPendingWebSingleTap();
77
71
  }
72
+ },
73
+ [clearPendingWebSingleTap, pageTransitionLocked],
74
+ );
78
75
 
79
- const { contentOffset } = event.nativeEvent;
80
- const scrollIndex = Math.round(contentOffset.x / (width + itemSpacing));
81
- const { realIndex, needsJump, jumpToIndex } = getLoopAdjustedIndex(
82
- scrollIndex,
83
- dataLength,
84
- enableLoop,
85
- );
76
+ const clearPagingFlags = useCallback(() => {
77
+ pagingAnimationActive.set(false);
78
+ pagingGestureActive.set(false);
79
+ }, [pagingAnimationActive, pagingGestureActive]);
86
80
 
87
- const isLoopHandled = manager?.handleMomentumScrollEnd(scrollIndex);
81
+ const cancelPagingInteraction = useCallback(() => {
82
+ clearPendingWebSingleTap();
83
+ cancelAnimation(visualPage);
84
+ clearPagingFlags();
85
+ setPageTransitioning(false);
86
+ }, [clearPendingWebSingleTap, clearPagingFlags, setPageTransitioning, visualPage]);
88
87
 
89
- if (realIndex < 0 || realIndex >= dataLength) {
90
- return;
91
- }
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
+ );
92
105
 
93
- if (isLoopHandled) {
94
- syncCurrentIndex(realIndex);
95
- setActiveListIndex(jumpToIndex ?? scrollIndex);
96
- return;
97
- }
106
+ const completeAnimatedVirtualPage = useCallback(
107
+ (targetVirtualIndex: number) => {
108
+ commitVirtualIndexOnly(targetVirtualIndex);
109
+ setPageTransitioning(false);
110
+ },
111
+ [commitVirtualIndexOnly, setPageTransitioning],
112
+ );
98
113
 
99
- if (needsJump && jumpToIndex !== undefined) {
100
- scrollTo(jumpToIndex, false);
101
- }
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);
102
127
 
103
- syncCurrentIndex(realIndex);
104
- setActiveListIndex(jumpToIndex ?? scrollIndex);
128
+ if (finished) {
129
+ scheduleOnRN(completeAnimatedVirtualPage, targetVirtualIndex);
130
+ return;
131
+ }
132
+
133
+ scheduleOnRN(cancelAnimatedVirtualPage);
134
+ }),
135
+ );
105
136
  },
106
137
  [
107
- dataLength,
108
- enableHorizontalSwipe,
109
- enableLoop,
110
- itemSpacing,
111
- manager,
112
- scrollTo,
113
- syncCurrentIndex,
114
- width,
138
+ cancelAnimatedVirtualPage,
139
+ clearPagingFlags,
140
+ completeAnimatedVirtualPage,
141
+ pagingAnimationActive,
142
+ setPageTransitioning,
143
+ visualPage,
115
144
  ],
116
145
  );
117
146
 
118
- const onScroll = useCallback(
119
- (event: NativeSyntheticEvent<NativeScrollEvent>) => {
120
- if (!enableHorizontalSwipe) {
121
- return;
122
- }
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);
123
162
 
124
- const { contentOffset } = event.nativeEvent;
125
- const scrollIndex = Math.round(contentOffset.x / (width + itemSpacing));
126
- const { realIndex } = getLoopAdjustedIndex(scrollIndex, dataLength, enableLoop);
163
+ if (finished) {
164
+ scheduleOnRN(setPageTransitioning, false);
165
+ }
166
+ }),
167
+ );
168
+ };
127
169
 
128
- syncPendingIndex(realIndex);
129
- },
130
- [dataLength, enableHorizontalSwipe, enableLoop, itemSpacing, syncPendingIndex, width],
131
- );
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);
132
246
 
133
- const onScrollBeginDrag = useCallback(() => {
134
- manager?.handleScrollBeginDrag();
135
- }, [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
+ ]);
136
297
 
137
298
  return {
138
- activeListIndex,
139
- onMomentumScrollEnd,
140
- onScroll,
141
- onScrollBeginDrag,
142
- onWebClick: undefined,
299
+ animateToVirtualPage,
300
+ cancelPagingInteraction,
301
+ horizontalPagingGesture,
302
+ isPageTransitioningRef,
303
+ pageTransitionLocked,
304
+ snapToVirtualPage,
305
+ visualPage,
143
306
  };
144
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
+ }