react-native-gesture-image-viewer 2.1.2 → 2.1.4
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.
- package/README.md +1 -1
- package/lib/module/GestureTrigger.js.map +1 -1
- package/lib/module/GestureViewer.js +23 -6
- package/lib/module/GestureViewer.js.map +1 -1
- package/lib/module/GestureViewerManager.js +26 -6
- package/lib/module/GestureViewerManager.js.map +1 -1
- package/lib/module/GestureViewerRegistry.js.map +1 -1
- package/lib/module/WebPagingFixStyle.js.map +1 -1
- package/lib/module/useGestureViewer.js +205 -229
- package/lib/module/useGestureViewer.js.map +1 -1
- package/lib/module/useGestureViewerController.js.map +1 -1
- package/lib/module/useGestureViewerEvent.js.map +1 -1
- package/lib/module/useGestureViewerPaging.js +68 -0
- package/lib/module/useGestureViewerPaging.js.map +1 -0
- package/lib/module/useGestureViewerPaging.types.js +4 -0
- package/lib/module/useGestureViewerPaging.types.js.map +1 -0
- package/lib/module/useGestureViewerPaging.web.js +241 -0
- package/lib/module/useGestureViewerPaging.web.js.map +1 -0
- package/lib/module/useGestureViewerState.js.map +1 -1
- package/lib/module/utils/index.js +23 -16
- package/lib/module/utils/index.js.map +1 -1
- package/lib/module/utils/tapZoom.js +34 -0
- package/lib/module/utils/tapZoom.js.map +1 -0
- package/lib/module/utils/webScroll.js +70 -0
- package/lib/module/utils/webScroll.js.map +1 -0
- package/lib/typescript/src/GestureTrigger.d.ts +1 -1
- package/lib/typescript/src/GestureTrigger.d.ts.map +1 -1
- package/lib/typescript/src/GestureViewer.d.ts.map +1 -1
- package/lib/typescript/src/GestureViewerManager.d.ts +5 -0
- package/lib/typescript/src/GestureViewerManager.d.ts.map +1 -1
- package/lib/typescript/src/GestureViewerRegistry.d.ts.map +1 -1
- package/lib/typescript/src/WebPagingFixStyle.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/useGestureViewer.d.ts +13 -11
- package/lib/typescript/src/useGestureViewer.d.ts.map +1 -1
- package/lib/typescript/src/useGestureViewerController.d.ts.map +1 -1
- package/lib/typescript/src/useGestureViewerEvent.d.ts.map +1 -1
- package/lib/typescript/src/useGestureViewerPaging.d.ts +3 -0
- package/lib/typescript/src/useGestureViewerPaging.d.ts.map +1 -0
- package/lib/typescript/src/useGestureViewerPaging.types.d.ts +32 -0
- package/lib/typescript/src/useGestureViewerPaging.types.d.ts.map +1 -0
- package/lib/typescript/src/useGestureViewerPaging.web.d.ts +3 -0
- package/lib/typescript/src/useGestureViewerPaging.web.d.ts.map +1 -0
- package/lib/typescript/src/useGestureViewerState.d.ts.map +1 -1
- package/lib/typescript/src/utils/index.d.ts +8 -1
- package/lib/typescript/src/utils/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/tapZoom.d.ts +12 -0
- package/lib/typescript/src/utils/tapZoom.d.ts.map +1 -0
- package/lib/typescript/src/utils/webScroll.d.ts +18 -0
- package/lib/typescript/src/utils/webScroll.d.ts.map +1 -0
- package/package.json +86 -80
- package/src/GestureTrigger.tsx +9 -2
- package/src/GestureViewer.tsx +77 -28
- package/src/GestureViewerManager.ts +62 -12
- package/src/GestureViewerRegistry.ts +1 -0
- package/src/WebPagingFixStyle.tsx +1 -0
- package/src/types.ts +44 -32
- package/src/useGestureViewer.ts +292 -313
- package/src/useGestureViewerController.ts +1 -0
- package/src/useGestureViewerEvent.ts +5 -1
- package/src/useGestureViewerPaging.ts +112 -0
- package/src/useGestureViewerPaging.types.ts +34 -0
- package/src/useGestureViewerPaging.web.ts +390 -0
- package/src/useGestureViewerState.ts +1 -0
- package/src/utils/index.ts +48 -16
- package/src/utils/tapZoom.ts +44 -0
- package/src/utils/webScroll.ts +96 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
|
+
|
|
2
3
|
import type GestureViewerManager from './GestureViewerManager';
|
|
3
4
|
import { registry } from './GestureViewerRegistry';
|
|
4
5
|
import type { GestureViewerEventCallback, GestureViewerEventType } from './types';
|
|
@@ -71,7 +72,10 @@ export function useGestureViewerEvent<T extends GestureViewerEventType>(
|
|
|
71
72
|
callback?: GestureViewerEventCallback<T>,
|
|
72
73
|
) {
|
|
73
74
|
const id = typeof idOrEventType === 'string' && callback ? idOrEventType : 'default';
|
|
74
|
-
const eventType =
|
|
75
|
+
const eventType =
|
|
76
|
+
typeof idOrEventType === 'string' && callback
|
|
77
|
+
? (eventTypeOrCallback as T)
|
|
78
|
+
: (idOrEventType as T);
|
|
75
79
|
const finalCallback = callback || (eventTypeOrCallback as GestureViewerEventCallback<T>);
|
|
76
80
|
|
|
77
81
|
useEffect(() => {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { useCallback, useEffect } from 'react';
|
|
2
|
+
import type { NativeScrollEvent, NativeSyntheticEvent } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
UseGestureViewerPagingArgs,
|
|
6
|
+
UseGestureViewerPagingResult,
|
|
7
|
+
} from './useGestureViewerPaging.types';
|
|
8
|
+
import { getLoopAdjustedIndex } from './utils';
|
|
9
|
+
|
|
10
|
+
export function useGestureViewerPaging({
|
|
11
|
+
autoPlay,
|
|
12
|
+
autoPlayInterval,
|
|
13
|
+
currentIndex,
|
|
14
|
+
dataLength,
|
|
15
|
+
enableHorizontalSwipe,
|
|
16
|
+
enableLoop,
|
|
17
|
+
isRotated,
|
|
18
|
+
isZoomed,
|
|
19
|
+
itemSpacing,
|
|
20
|
+
manager,
|
|
21
|
+
scrollTo,
|
|
22
|
+
syncCurrentIndex,
|
|
23
|
+
width,
|
|
24
|
+
}: UseGestureViewerPagingArgs): UseGestureViewerPagingResult {
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (
|
|
27
|
+
!autoPlay ||
|
|
28
|
+
!manager ||
|
|
29
|
+
dataLength <= 1 ||
|
|
30
|
+
isZoomed ||
|
|
31
|
+
isRotated ||
|
|
32
|
+
(!enableLoop && currentIndex === dataLength - 1)
|
|
33
|
+
) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const intervalMs = Math.max(250, Math.floor(autoPlayInterval || 0));
|
|
38
|
+
|
|
39
|
+
if (!Number.isFinite(intervalMs)) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const interval = setInterval(() => {
|
|
44
|
+
if (isZoomed || isRotated) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
manager.goToNext();
|
|
49
|
+
}, intervalMs);
|
|
50
|
+
|
|
51
|
+
return () => clearInterval(interval);
|
|
52
|
+
}, [
|
|
53
|
+
autoPlay,
|
|
54
|
+
autoPlayInterval,
|
|
55
|
+
currentIndex,
|
|
56
|
+
dataLength,
|
|
57
|
+
enableLoop,
|
|
58
|
+
isRotated,
|
|
59
|
+
isZoomed,
|
|
60
|
+
manager,
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
const onMomentumScrollEnd = useCallback(
|
|
64
|
+
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
65
|
+
if (!enableHorizontalSwipe) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const { contentOffset } = event.nativeEvent;
|
|
70
|
+
const scrollIndex = Math.round(contentOffset.x / (width + itemSpacing));
|
|
71
|
+
|
|
72
|
+
const isLoopHandled = manager?.handleMomentumScrollEnd(scrollIndex);
|
|
73
|
+
|
|
74
|
+
if (isLoopHandled) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const { realIndex, needsJump, jumpToIndex } = getLoopAdjustedIndex(
|
|
79
|
+
scrollIndex,
|
|
80
|
+
dataLength,
|
|
81
|
+
enableLoop,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
if (needsJump && jumpToIndex !== undefined) {
|
|
85
|
+
scrollTo(jumpToIndex, false);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
syncCurrentIndex(realIndex);
|
|
89
|
+
},
|
|
90
|
+
[
|
|
91
|
+
dataLength,
|
|
92
|
+
enableHorizontalSwipe,
|
|
93
|
+
enableLoop,
|
|
94
|
+
itemSpacing,
|
|
95
|
+
manager,
|
|
96
|
+
scrollTo,
|
|
97
|
+
syncCurrentIndex,
|
|
98
|
+
width,
|
|
99
|
+
],
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const onScrollBeginDrag = useCallback(() => {
|
|
103
|
+
manager?.handleScrollBeginDrag();
|
|
104
|
+
}, [manager]);
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
onMomentumScrollEnd,
|
|
108
|
+
onScroll: undefined,
|
|
109
|
+
onScrollBeginDrag,
|
|
110
|
+
onWebDoubleClick: undefined,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ScrollViewProps } from 'react-native';
|
|
2
|
+
import type { SharedValue } from 'react-native-reanimated';
|
|
3
|
+
|
|
4
|
+
import type GestureViewerManager from './GestureViewerManager';
|
|
5
|
+
|
|
6
|
+
export type UseGestureViewerPagingArgs = {
|
|
7
|
+
adjustedInitialIndex: number;
|
|
8
|
+
autoPlay: boolean;
|
|
9
|
+
autoPlayInterval: number;
|
|
10
|
+
currentIndex: number;
|
|
11
|
+
dataLength: number;
|
|
12
|
+
enableDoubleTapZoom: boolean;
|
|
13
|
+
enableHorizontalSwipe: boolean;
|
|
14
|
+
enableLoop: boolean;
|
|
15
|
+
height: number;
|
|
16
|
+
isRotated: boolean;
|
|
17
|
+
isZoomed: boolean;
|
|
18
|
+
itemSpacing: number;
|
|
19
|
+
manager: GestureViewerManager | null;
|
|
20
|
+
maxZoomScale: number;
|
|
21
|
+
scale: SharedValue<number>;
|
|
22
|
+
scrollTo: (index: number, animated: boolean) => void;
|
|
23
|
+
syncCurrentIndex: (nextIndex: number) => void;
|
|
24
|
+
translateX: SharedValue<number>;
|
|
25
|
+
translateY: SharedValue<number>;
|
|
26
|
+
width: number;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type UseGestureViewerPagingResult = {
|
|
30
|
+
onMomentumScrollEnd?: ScrollViewProps['onMomentumScrollEnd'];
|
|
31
|
+
onScroll?: ScrollViewProps['onScroll'];
|
|
32
|
+
onScrollBeginDrag?: ScrollViewProps['onScrollBeginDrag'];
|
|
33
|
+
onWebDoubleClick?: (event: any) => void;
|
|
34
|
+
};
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
2
|
+
import type { NativeScrollEvent, NativeSyntheticEvent } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
UseGestureViewerPagingArgs,
|
|
6
|
+
UseGestureViewerPagingResult,
|
|
7
|
+
} from './useGestureViewerPaging.types';
|
|
8
|
+
import { applyTapZoomAtPoint } from './utils/tapZoom';
|
|
9
|
+
import {
|
|
10
|
+
getWebAutoPlayTargetPhysicalIndex,
|
|
11
|
+
getWebScrollPhysicalIndex,
|
|
12
|
+
resolveWebScrollFinalState,
|
|
13
|
+
} from './utils/webScroll';
|
|
14
|
+
|
|
15
|
+
type WebScrollActor = 'idle' | 'user' | 'autoplay';
|
|
16
|
+
|
|
17
|
+
type WebScrollRuntime = {
|
|
18
|
+
actor: WebScrollActor;
|
|
19
|
+
isAutoplayPausedByUser: boolean;
|
|
20
|
+
lastSettledPhysicalIndex: number;
|
|
21
|
+
latestOffsetX: number;
|
|
22
|
+
latestRawPhysicalIndex: number;
|
|
23
|
+
lastProgrammaticScrollVersion: number;
|
|
24
|
+
settleTimer: ReturnType<typeof setTimeout> | null;
|
|
25
|
+
resumeAutoplayTimer: ReturnType<typeof setTimeout> | null;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function useGestureViewerPaging({
|
|
29
|
+
adjustedInitialIndex,
|
|
30
|
+
autoPlay,
|
|
31
|
+
autoPlayInterval,
|
|
32
|
+
currentIndex,
|
|
33
|
+
dataLength,
|
|
34
|
+
enableDoubleTapZoom,
|
|
35
|
+
enableHorizontalSwipe,
|
|
36
|
+
enableLoop,
|
|
37
|
+
height,
|
|
38
|
+
isRotated,
|
|
39
|
+
isZoomed,
|
|
40
|
+
itemSpacing,
|
|
41
|
+
manager,
|
|
42
|
+
maxZoomScale,
|
|
43
|
+
scale,
|
|
44
|
+
scrollTo,
|
|
45
|
+
syncCurrentIndex,
|
|
46
|
+
translateX,
|
|
47
|
+
translateY,
|
|
48
|
+
width,
|
|
49
|
+
}: UseGestureViewerPagingArgs): UseGestureViewerPagingResult {
|
|
50
|
+
const webScrollRuntimeRef = useRef<WebScrollRuntime>({
|
|
51
|
+
actor: 'idle',
|
|
52
|
+
isAutoplayPausedByUser: false,
|
|
53
|
+
lastSettledPhysicalIndex: adjustedInitialIndex,
|
|
54
|
+
latestOffsetX: adjustedInitialIndex * (width + itemSpacing),
|
|
55
|
+
latestRawPhysicalIndex: adjustedInitialIndex,
|
|
56
|
+
lastProgrammaticScrollVersion: 0,
|
|
57
|
+
settleTimer: null,
|
|
58
|
+
resumeAutoplayTimer: null,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const clearWebSettleTimer = useCallback(() => {
|
|
62
|
+
const runtime = webScrollRuntimeRef.current;
|
|
63
|
+
|
|
64
|
+
if (runtime.settleTimer) {
|
|
65
|
+
clearTimeout(runtime.settleTimer);
|
|
66
|
+
runtime.settleTimer = null;
|
|
67
|
+
}
|
|
68
|
+
}, []);
|
|
69
|
+
|
|
70
|
+
const clearWebAutoplayResumeTimer = useCallback(() => {
|
|
71
|
+
const runtime = webScrollRuntimeRef.current;
|
|
72
|
+
|
|
73
|
+
if (runtime.resumeAutoplayTimer) {
|
|
74
|
+
clearTimeout(runtime.resumeAutoplayTimer);
|
|
75
|
+
runtime.resumeAutoplayTimer = null;
|
|
76
|
+
}
|
|
77
|
+
}, []);
|
|
78
|
+
|
|
79
|
+
const scheduleWebAutoplayResume = useCallback(() => {
|
|
80
|
+
const runtime = webScrollRuntimeRef.current;
|
|
81
|
+
|
|
82
|
+
clearWebAutoplayResumeTimer();
|
|
83
|
+
runtime.resumeAutoplayTimer = setTimeout(() => {
|
|
84
|
+
runtime.isAutoplayPausedByUser = false;
|
|
85
|
+
runtime.resumeAutoplayTimer = null;
|
|
86
|
+
}, 800);
|
|
87
|
+
}, [clearWebAutoplayResumeTimer]);
|
|
88
|
+
|
|
89
|
+
const pauseWebAutoplayWithoutPagingInteraction = useCallback(() => {
|
|
90
|
+
const runtime = webScrollRuntimeRef.current;
|
|
91
|
+
|
|
92
|
+
runtime.isAutoplayPausedByUser = true;
|
|
93
|
+
runtime.actor = 'idle';
|
|
94
|
+
clearWebAutoplayResumeTimer();
|
|
95
|
+
}, [clearWebAutoplayResumeTimer]);
|
|
96
|
+
|
|
97
|
+
const beginWebUserInteraction = useCallback(
|
|
98
|
+
(force = false) => {
|
|
99
|
+
const runtime = webScrollRuntimeRef.current;
|
|
100
|
+
|
|
101
|
+
if (!force && runtime.actor === 'autoplay') {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
runtime.actor = 'user';
|
|
106
|
+
runtime.isAutoplayPausedByUser = true;
|
|
107
|
+
clearWebAutoplayResumeTimer();
|
|
108
|
+
},
|
|
109
|
+
[clearWebAutoplayResumeTimer],
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const beginWebAutoplayScroll = useCallback(() => {
|
|
113
|
+
const runtime = webScrollRuntimeRef.current;
|
|
114
|
+
|
|
115
|
+
runtime.actor = 'autoplay';
|
|
116
|
+
runtime.isAutoplayPausedByUser = false;
|
|
117
|
+
runtime.lastProgrammaticScrollVersion = manager?.getProgrammaticScrollVersion() ?? 0;
|
|
118
|
+
clearWebAutoplayResumeTimer();
|
|
119
|
+
}, [clearWebAutoplayResumeTimer, manager]);
|
|
120
|
+
|
|
121
|
+
const syncProgrammaticActorFromManager = useCallback(() => {
|
|
122
|
+
const runtime = webScrollRuntimeRef.current;
|
|
123
|
+
const nextVersion = manager?.getProgrammaticScrollVersion() ?? 0;
|
|
124
|
+
|
|
125
|
+
if (nextVersion === runtime.lastProgrammaticScrollVersion) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
runtime.actor = 'autoplay';
|
|
130
|
+
runtime.isAutoplayPausedByUser = false;
|
|
131
|
+
runtime.lastProgrammaticScrollVersion = nextVersion;
|
|
132
|
+
clearWebAutoplayResumeTimer();
|
|
133
|
+
}, [clearWebAutoplayResumeTimer, manager]);
|
|
134
|
+
|
|
135
|
+
const finalizeWebScroll = useCallback(
|
|
136
|
+
(offsetX: number, source: 'scroll' | 'momentum' = 'scroll') => {
|
|
137
|
+
if (!enableHorizontalSwipe || dataLength <= 0) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const runtime = webScrollRuntimeRef.current;
|
|
142
|
+
const settledByActor = runtime.actor;
|
|
143
|
+
|
|
144
|
+
clearWebSettleTimer();
|
|
145
|
+
runtime.latestOffsetX = offsetX;
|
|
146
|
+
|
|
147
|
+
const settledState = resolveWebScrollFinalState({
|
|
148
|
+
dataLength,
|
|
149
|
+
enableLoop,
|
|
150
|
+
lastSettledPhysicalIndex: runtime.lastSettledPhysicalIndex,
|
|
151
|
+
offsetX,
|
|
152
|
+
pageWidth: width + itemSpacing,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
if (!settledState) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const { logicalIndex, rawPhysicalIndex, settledPhysicalIndex } = settledState;
|
|
160
|
+
|
|
161
|
+
if (source === 'scroll' && runtime.latestRawPhysicalIndex !== rawPhysicalIndex) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (rawPhysicalIndex !== settledPhysicalIndex) {
|
|
166
|
+
const crossedLoopBoundary =
|
|
167
|
+
enableLoop &&
|
|
168
|
+
dataLength > 1 &&
|
|
169
|
+
(rawPhysicalIndex === 0 || rawPhysicalIndex === dataLength + 1);
|
|
170
|
+
|
|
171
|
+
scrollTo(settledPhysicalIndex, !crossedLoopBoundary);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
runtime.lastSettledPhysicalIndex = settledPhysicalIndex;
|
|
175
|
+
runtime.latestOffsetX = settledPhysicalIndex * (width + itemSpacing);
|
|
176
|
+
runtime.actor = 'idle';
|
|
177
|
+
manager?.cancelPendingLoopTransition();
|
|
178
|
+
syncCurrentIndex(logicalIndex);
|
|
179
|
+
|
|
180
|
+
if (settledByActor === 'user') {
|
|
181
|
+
scheduleWebAutoplayResume();
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
[
|
|
185
|
+
clearWebSettleTimer,
|
|
186
|
+
dataLength,
|
|
187
|
+
enableHorizontalSwipe,
|
|
188
|
+
enableLoop,
|
|
189
|
+
itemSpacing,
|
|
190
|
+
manager,
|
|
191
|
+
scheduleWebAutoplayResume,
|
|
192
|
+
scrollTo,
|
|
193
|
+
syncCurrentIndex,
|
|
194
|
+
width,
|
|
195
|
+
],
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const scheduleWebScrollSettle = useCallback(
|
|
199
|
+
(offsetX: number) => {
|
|
200
|
+
const runtime = webScrollRuntimeRef.current;
|
|
201
|
+
|
|
202
|
+
runtime.latestOffsetX = offsetX;
|
|
203
|
+
runtime.latestRawPhysicalIndex = getWebScrollPhysicalIndex(offsetX, width + itemSpacing);
|
|
204
|
+
clearWebSettleTimer();
|
|
205
|
+
runtime.settleTimer = setTimeout(() => {
|
|
206
|
+
finalizeWebScroll(runtime.latestOffsetX, 'scroll');
|
|
207
|
+
}, 180);
|
|
208
|
+
},
|
|
209
|
+
[clearWebSettleTimer, finalizeWebScroll, itemSpacing, width],
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
const runtime = webScrollRuntimeRef.current;
|
|
214
|
+
|
|
215
|
+
runtime.actor = 'idle';
|
|
216
|
+
runtime.isAutoplayPausedByUser = false;
|
|
217
|
+
runtime.lastSettledPhysicalIndex = adjustedInitialIndex;
|
|
218
|
+
runtime.latestOffsetX = adjustedInitialIndex * (width + itemSpacing);
|
|
219
|
+
runtime.latestRawPhysicalIndex = adjustedInitialIndex;
|
|
220
|
+
runtime.lastProgrammaticScrollVersion = manager?.getProgrammaticScrollVersion() ?? 0;
|
|
221
|
+
clearWebSettleTimer();
|
|
222
|
+
clearWebAutoplayResumeTimer();
|
|
223
|
+
}, [
|
|
224
|
+
adjustedInitialIndex,
|
|
225
|
+
clearWebAutoplayResumeTimer,
|
|
226
|
+
clearWebSettleTimer,
|
|
227
|
+
itemSpacing,
|
|
228
|
+
manager,
|
|
229
|
+
width,
|
|
230
|
+
]);
|
|
231
|
+
|
|
232
|
+
useEffect(() => {
|
|
233
|
+
return () => {
|
|
234
|
+
clearWebSettleTimer();
|
|
235
|
+
clearWebAutoplayResumeTimer();
|
|
236
|
+
};
|
|
237
|
+
}, [clearWebAutoplayResumeTimer, clearWebSettleTimer]);
|
|
238
|
+
|
|
239
|
+
useEffect(() => {
|
|
240
|
+
if (
|
|
241
|
+
!autoPlay ||
|
|
242
|
+
dataLength <= 1 ||
|
|
243
|
+
isZoomed ||
|
|
244
|
+
isRotated ||
|
|
245
|
+
(!enableLoop && currentIndex === dataLength - 1)
|
|
246
|
+
) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const intervalMs = Math.max(250, Math.floor(autoPlayInterval || 0));
|
|
251
|
+
|
|
252
|
+
if (!Number.isFinite(intervalMs)) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const interval = setInterval(() => {
|
|
257
|
+
if (isZoomed || isRotated) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const runtime = webScrollRuntimeRef.current;
|
|
262
|
+
|
|
263
|
+
if (runtime.actor !== 'idle' || runtime.isAutoplayPausedByUser || runtime.settleTimer) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const targetPhysicalIndex = getWebAutoPlayTargetPhysicalIndex({
|
|
268
|
+
currentIndex,
|
|
269
|
+
dataLength,
|
|
270
|
+
enableLoop,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
if (targetPhysicalIndex === null) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
beginWebAutoplayScroll();
|
|
278
|
+
runtime.latestOffsetX = targetPhysicalIndex * (width + itemSpacing);
|
|
279
|
+
scrollTo(targetPhysicalIndex, true);
|
|
280
|
+
scheduleWebScrollSettle(runtime.latestOffsetX);
|
|
281
|
+
}, intervalMs);
|
|
282
|
+
|
|
283
|
+
return () => clearInterval(interval);
|
|
284
|
+
}, [
|
|
285
|
+
autoPlay,
|
|
286
|
+
autoPlayInterval,
|
|
287
|
+
beginWebAutoplayScroll,
|
|
288
|
+
currentIndex,
|
|
289
|
+
dataLength,
|
|
290
|
+
enableLoop,
|
|
291
|
+
isRotated,
|
|
292
|
+
isZoomed,
|
|
293
|
+
itemSpacing,
|
|
294
|
+
scheduleWebScrollSettle,
|
|
295
|
+
scrollTo,
|
|
296
|
+
width,
|
|
297
|
+
]);
|
|
298
|
+
|
|
299
|
+
const onScroll = useCallback(
|
|
300
|
+
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
301
|
+
if (!enableHorizontalSwipe) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
syncProgrammaticActorFromManager();
|
|
306
|
+
beginWebUserInteraction();
|
|
307
|
+
scheduleWebScrollSettle(event.nativeEvent.contentOffset.x);
|
|
308
|
+
},
|
|
309
|
+
[
|
|
310
|
+
beginWebUserInteraction,
|
|
311
|
+
enableHorizontalSwipe,
|
|
312
|
+
scheduleWebScrollSettle,
|
|
313
|
+
syncProgrammaticActorFromManager,
|
|
314
|
+
],
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
const onMomentumScrollEnd = useCallback(
|
|
318
|
+
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
319
|
+
if (!enableHorizontalSwipe) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
finalizeWebScroll(event.nativeEvent.contentOffset.x, 'momentum');
|
|
324
|
+
},
|
|
325
|
+
[enableHorizontalSwipe, finalizeWebScroll],
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
const onScrollBeginDrag = useCallback(() => {
|
|
329
|
+
beginWebUserInteraction(true);
|
|
330
|
+
clearWebSettleTimer();
|
|
331
|
+
manager?.handleScrollBeginDrag();
|
|
332
|
+
}, [beginWebUserInteraction, clearWebSettleTimer, manager]);
|
|
333
|
+
|
|
334
|
+
const onWebDoubleClick = useCallback(
|
|
335
|
+
(event: any) => {
|
|
336
|
+
if (!enableDoubleTapZoom || event?.nativeEvent?.detail !== 2) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
pauseWebAutoplayWithoutPagingInteraction();
|
|
341
|
+
scheduleWebAutoplayResume();
|
|
342
|
+
|
|
343
|
+
const nativeEvent = event?.nativeEvent;
|
|
344
|
+
const eventTarget = event?.currentTarget ?? nativeEvent?.currentTarget ?? nativeEvent?.target;
|
|
345
|
+
let locationX = nativeEvent?.locationX;
|
|
346
|
+
let locationY = nativeEvent?.locationY;
|
|
347
|
+
|
|
348
|
+
if (
|
|
349
|
+
(!Number.isFinite(locationX) || !Number.isFinite(locationY)) &&
|
|
350
|
+
typeof eventTarget?.getBoundingClientRect === 'function' &&
|
|
351
|
+
Number.isFinite(nativeEvent?.clientX) &&
|
|
352
|
+
Number.isFinite(nativeEvent?.clientY)
|
|
353
|
+
) {
|
|
354
|
+
const rect = eventTarget.getBoundingClientRect();
|
|
355
|
+
|
|
356
|
+
locationX = nativeEvent.clientX - rect.left;
|
|
357
|
+
locationY = nativeEvent.clientY - rect.top;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
applyTapZoomAtPoint({
|
|
361
|
+
x: Number.isFinite(locationX) ? locationX : width / 2,
|
|
362
|
+
y: Number.isFinite(locationY) ? locationY : height / 2,
|
|
363
|
+
width,
|
|
364
|
+
height,
|
|
365
|
+
maxZoomScale,
|
|
366
|
+
scale,
|
|
367
|
+
translateX,
|
|
368
|
+
translateY,
|
|
369
|
+
});
|
|
370
|
+
},
|
|
371
|
+
[
|
|
372
|
+
enableDoubleTapZoom,
|
|
373
|
+
height,
|
|
374
|
+
maxZoomScale,
|
|
375
|
+
pauseWebAutoplayWithoutPagingInteraction,
|
|
376
|
+
scheduleWebAutoplayResume,
|
|
377
|
+
scale,
|
|
378
|
+
translateX,
|
|
379
|
+
translateY,
|
|
380
|
+
width,
|
|
381
|
+
],
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
return {
|
|
385
|
+
onMomentumScrollEnd,
|
|
386
|
+
onScroll,
|
|
387
|
+
onScrollBeginDrag,
|
|
388
|
+
onWebDoubleClick,
|
|
389
|
+
};
|
|
390
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
FlatList as RNFlatList,
|
|
3
|
+
ScrollView as RNScrollView,
|
|
4
|
+
type PlatformOSType,
|
|
5
|
+
} from 'react-native';
|
|
6
|
+
import {
|
|
7
|
+
FlatList as GestureFlatList,
|
|
8
|
+
ScrollView as GestureScrollView,
|
|
9
|
+
} from 'react-native-gesture-handler';
|
|
10
|
+
|
|
3
11
|
import type { FlatListComponent, ScrollViewComponent } from '../types';
|
|
12
|
+
|
|
4
13
|
import { FlashList } from './FlashList';
|
|
5
14
|
|
|
6
|
-
export const isScrollViewLike = (
|
|
7
|
-
|
|
8
|
-
|
|
15
|
+
export const isScrollViewLike = (
|
|
16
|
+
component: React.ComponentType<any>,
|
|
17
|
+
): component is ScrollViewComponent =>
|
|
18
|
+
component === RNScrollView || component === GestureScrollView;
|
|
9
19
|
|
|
10
|
-
export const isFlatListLike = (
|
|
20
|
+
export const isFlatListLike = (
|
|
21
|
+
component: React.ComponentType<any>,
|
|
22
|
+
): component is FlatListComponent<any> => {
|
|
11
23
|
if (component === RNFlatList || component === GestureFlatList || isFlashListLike(component)) {
|
|
12
24
|
return true;
|
|
13
25
|
}
|
|
@@ -23,9 +35,29 @@ export const isFlashListLike = (component: React.ComponentType<any>): boolean =>
|
|
|
23
35
|
return component?.displayName === 'FlashList' || component?.name === 'FlashList';
|
|
24
36
|
};
|
|
25
37
|
|
|
38
|
+
/**
|
|
39
|
+
* iOS needs the extra Native gesture wrapper so the dismiss pan can resolve before the scrollable claims touches.
|
|
40
|
+
* We intentionally skip Android because the same require-to-fail relation regressed horizontal paging there,
|
|
41
|
+
* and we also avoid wrapping RNGH-provided scrollables to prevent double-applying Gesture.Native() to components that already participate in RNGH.
|
|
42
|
+
*/
|
|
43
|
+
export const shouldUseNativeScrollGesture = (
|
|
44
|
+
platformOS: PlatformOSType,
|
|
45
|
+
component: React.ComponentType<any>,
|
|
46
|
+
): boolean => {
|
|
47
|
+
return platformOS === 'ios' && component !== GestureScrollView && component !== GestureFlatList;
|
|
48
|
+
};
|
|
49
|
+
|
|
26
50
|
export const createBoundsConstraint =
|
|
27
51
|
({ width, height }: { width: number; height: number }) =>
|
|
28
|
-
({
|
|
52
|
+
({
|
|
53
|
+
scale,
|
|
54
|
+
translateX,
|
|
55
|
+
translateY,
|
|
56
|
+
}: {
|
|
57
|
+
translateX: number;
|
|
58
|
+
translateY: number;
|
|
59
|
+
scale: number;
|
|
60
|
+
}) => {
|
|
29
61
|
'worklet';
|
|
30
62
|
|
|
31
63
|
if (scale <= 1) {
|
|
@@ -51,7 +83,7 @@ export const createLoopData = <T>(dataRef: React.RefObject<T[]>, enableLoop: boo
|
|
|
51
83
|
return data;
|
|
52
84
|
}
|
|
53
85
|
|
|
54
|
-
const lastItem = data
|
|
86
|
+
const lastItem = data.at(-1);
|
|
55
87
|
const firstItem = data[0];
|
|
56
88
|
|
|
57
89
|
if (lastItem === undefined || firstItem === undefined) {
|
|
@@ -67,32 +99,32 @@ export const getLoopAdjustedIndex = (
|
|
|
67
99
|
enableLoop: boolean,
|
|
68
100
|
): { realIndex: number; needsJump: boolean; jumpToIndex?: number } => {
|
|
69
101
|
if (!enableLoop || originalDataLength <= 1) {
|
|
70
|
-
return {
|
|
102
|
+
return { needsJump: false, realIndex: scrollIndex };
|
|
71
103
|
}
|
|
72
104
|
|
|
73
105
|
if (scrollIndex === 0) {
|
|
74
106
|
return {
|
|
75
|
-
realIndex: originalDataLength - 1,
|
|
76
|
-
needsJump: true,
|
|
77
107
|
jumpToIndex: originalDataLength,
|
|
108
|
+
needsJump: true,
|
|
109
|
+
realIndex: originalDataLength - 1,
|
|
78
110
|
};
|
|
79
111
|
} else if (scrollIndex === originalDataLength + 1) {
|
|
80
112
|
return {
|
|
81
|
-
realIndex: 0,
|
|
82
|
-
needsJump: true,
|
|
83
113
|
jumpToIndex: 1,
|
|
114
|
+
needsJump: true,
|
|
115
|
+
realIndex: 0,
|
|
84
116
|
};
|
|
85
117
|
}
|
|
86
118
|
|
|
87
|
-
return { realIndex: scrollIndex - 1
|
|
119
|
+
return { needsJump: false, realIndex: scrollIndex - 1 };
|
|
88
120
|
};
|
|
89
121
|
|
|
90
122
|
export const createScrollAction = (listRef: any, width: number) => ({
|
|
91
123
|
scrollTo: (index: number, animated: boolean) => {
|
|
92
124
|
if (listRef?.scrollToIndex) {
|
|
93
|
-
listRef.scrollToIndex({
|
|
125
|
+
listRef.scrollToIndex({ animated, index });
|
|
94
126
|
} else if (listRef?.scrollTo) {
|
|
95
|
-
listRef.scrollTo({ x: index * width
|
|
127
|
+
listRef.scrollTo({ animated, x: index * width });
|
|
96
128
|
}
|
|
97
129
|
},
|
|
98
130
|
});
|