react-native-gesture-image-viewer 2.1.3 → 2.2.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.
- package/lib/module/GestureTrigger.js +22 -5
- package/lib/module/GestureTrigger.js.map +1 -1
- package/lib/module/GestureViewer.js +50 -40
- 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 +52 -8
- package/lib/module/GestureViewerRegistry.js.map +1 -1
- package/lib/module/useGestureViewer.js +149 -113
- package/lib/module/useGestureViewer.js.map +1 -1
- package/lib/module/useGestureViewerPaging.js +82 -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 +254 -0
- package/lib/module/useGestureViewerPaging.web.js.map +1 -0
- package/lib/module/utils/index.js +9 -0
- 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 +9 -3
- 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 +11 -4
- package/lib/typescript/src/GestureViewerRegistry.d.ts.map +1 -1
- package/lib/typescript/src/useGestureViewer.d.ts +4 -3
- package/lib/typescript/src/useGestureViewer.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 +33 -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/utils/index.d.ts +7 -0
- 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 +1 -1
- package/src/GestureTrigger.tsx +31 -6
- package/src/GestureViewer.tsx +39 -15
- package/src/GestureViewerManager.ts +29 -6
- package/src/GestureViewerRegistry.ts +67 -8
- package/src/useGestureViewer.ts +174 -165
- package/src/useGestureViewerPaging.ts +128 -0
- package/src/useGestureViewerPaging.types.ts +35 -0
- package/src/useGestureViewerPaging.web.ts +410 -0
- package/src/utils/index.ts +17 -1
- package/src/utils/tapZoom.ts +44 -0
- package/src/utils/webScroll.ts +96 -0
package/src/useGestureViewer.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
InteractionManager,
|
|
4
|
-
type NativeScrollEvent,
|
|
5
|
-
type NativeSyntheticEvent,
|
|
6
|
-
useWindowDimensions,
|
|
7
|
-
} from 'react-native';
|
|
2
|
+
import { InteractionManager, Platform, type View, useWindowDimensions } from 'react-native';
|
|
8
3
|
import { Gesture, type GestureType } from 'react-native-gesture-handler';
|
|
9
4
|
import {
|
|
10
5
|
Easing,
|
|
@@ -20,7 +15,9 @@ import { scheduleOnRN } from 'react-native-worklets';
|
|
|
20
15
|
import type GestureViewerManager from './GestureViewerManager';
|
|
21
16
|
import { registry } from './GestureViewerRegistry';
|
|
22
17
|
import type { GestureViewerProps, TriggerRect } from './types';
|
|
23
|
-
import {
|
|
18
|
+
import { useGestureViewerPaging } from './useGestureViewerPaging';
|
|
19
|
+
import { createBoundsConstraint, createScrollAction } from './utils';
|
|
20
|
+
import { applyTapZoomAtPoint } from './utils/tapZoom';
|
|
24
21
|
|
|
25
22
|
type UseGestureViewerProps<ItemT, LC> = Omit<
|
|
26
23
|
GestureViewerProps<ItemT, LC>,
|
|
@@ -65,11 +62,17 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
65
62
|
const [shouldStartTriggerAnimation, setShouldStartTriggerAnimation] = useState(false);
|
|
66
63
|
const [manager, setManager] = useState<GestureViewerManager | null>(null);
|
|
67
64
|
const [currentIndex, setCurrentIndex] = useState(initialIndex);
|
|
65
|
+
const [activeTriggerNode, setActiveTriggerNode] = useState<View | null>(null);
|
|
68
66
|
|
|
69
67
|
const listRef = useRef<any>(null);
|
|
70
68
|
const triggerRectRef = useRef<TriggerRect | null>(null);
|
|
69
|
+
const pendingIndexRef = useRef(initialIndex);
|
|
71
70
|
const onAnimationCompleteRef = useRef(triggerAnimation?.onAnimationComplete);
|
|
72
71
|
|
|
72
|
+
const isValidTriggerRect = useCallback((rect: TriggerRect | null): rect is TriggerRect => {
|
|
73
|
+
return !!rect && rect.width > 0 && rect.height > 0;
|
|
74
|
+
}, []);
|
|
75
|
+
|
|
73
76
|
const initialTranslateY = useSharedValue(0);
|
|
74
77
|
const initialTranslateX = useSharedValue(0);
|
|
75
78
|
const startScale = useSharedValue(1);
|
|
@@ -131,6 +134,48 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
131
134
|
[width, itemSpacing],
|
|
132
135
|
);
|
|
133
136
|
|
|
137
|
+
const resetTransformState = useCallback(() => {
|
|
138
|
+
translateX.value = withTiming(0);
|
|
139
|
+
translateY.value = withTiming(0);
|
|
140
|
+
initialTranslateX.value = withTiming(0);
|
|
141
|
+
initialTranslateY.value = withTiming(0);
|
|
142
|
+
startScale.value = withTiming(1);
|
|
143
|
+
scale.value = withTiming(1);
|
|
144
|
+
rotation.value = 0;
|
|
145
|
+
}, [initialTranslateX, initialTranslateY, rotation, scale, startScale, translateX, translateY]);
|
|
146
|
+
|
|
147
|
+
const syncPendingIndex = useCallback(
|
|
148
|
+
(nextIndex: number) => {
|
|
149
|
+
if (nextIndex < 0 || nextIndex >= dataLength) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
pendingIndexRef.current = nextIndex;
|
|
154
|
+
},
|
|
155
|
+
[dataLength],
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const syncCurrentIndex = useCallback(
|
|
159
|
+
(nextIndex: number) => {
|
|
160
|
+
if (!manager || nextIndex < 0 || nextIndex >= dataLength) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
pendingIndexRef.current = nextIndex;
|
|
165
|
+
|
|
166
|
+
const managerCurrentIndex = manager.getState().currentIndex;
|
|
167
|
+
|
|
168
|
+
if (nextIndex === managerCurrentIndex) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
manager.setCurrentIndex(nextIndex);
|
|
173
|
+
manager.notifyStateChange();
|
|
174
|
+
resetTransformState();
|
|
175
|
+
},
|
|
176
|
+
[dataLength, manager, resetTransformState],
|
|
177
|
+
);
|
|
178
|
+
|
|
134
179
|
const emitZoomChange = useCallback(
|
|
135
180
|
(currentScale: number, prevScale: number | null) => {
|
|
136
181
|
if (manager) {
|
|
@@ -180,7 +225,10 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
180
225
|
|
|
181
226
|
const unsubscribeFromRegistry = registry.subscribeToManager(id, (managerInstance) => {
|
|
182
227
|
setManager(managerInstance);
|
|
183
|
-
unsubscribe = managerInstance?.subscribe((state) =>
|
|
228
|
+
unsubscribe = managerInstance?.subscribe((state) => {
|
|
229
|
+
pendingIndexRef.current = state.currentIndex;
|
|
230
|
+
setCurrentIndex(state.currentIndex);
|
|
231
|
+
});
|
|
184
232
|
});
|
|
185
233
|
|
|
186
234
|
return () => {
|
|
@@ -190,6 +238,12 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
190
238
|
}, [id]);
|
|
191
239
|
|
|
192
240
|
useEffect(() => {
|
|
241
|
+
return registry.subscribeToActiveTrigger(id, setActiveTriggerNode);
|
|
242
|
+
}, [id]);
|
|
243
|
+
|
|
244
|
+
useEffect(() => {
|
|
245
|
+
pendingIndexRef.current = initialIndex;
|
|
246
|
+
|
|
193
247
|
if (!manager) {
|
|
194
248
|
return;
|
|
195
249
|
}
|
|
@@ -200,6 +254,7 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
200
254
|
manager.setWidth(width + itemSpacing);
|
|
201
255
|
manager.setHeight(height);
|
|
202
256
|
manager.setZoomSharedValues(scale, translateX, translateY, maxZoomScale);
|
|
257
|
+
manager.setResetTransformCallback(resetTransformState);
|
|
203
258
|
manager.setRotation(rotation);
|
|
204
259
|
manager.setEnableLoop(enableLoop);
|
|
205
260
|
manager.notifyStateChange();
|
|
@@ -214,6 +269,7 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
214
269
|
enableLoop,
|
|
215
270
|
scale,
|
|
216
271
|
height,
|
|
272
|
+
resetTransformState,
|
|
217
273
|
translateX,
|
|
218
274
|
translateY,
|
|
219
275
|
rotation,
|
|
@@ -257,44 +313,6 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
257
313
|
scrollTo,
|
|
258
314
|
]);
|
|
259
315
|
|
|
260
|
-
useEffect(() => {
|
|
261
|
-
if (
|
|
262
|
-
!autoPlay ||
|
|
263
|
-
!manager ||
|
|
264
|
-
dataLength <= 1 ||
|
|
265
|
-
isZoomed ||
|
|
266
|
-
isRotated ||
|
|
267
|
-
(!enableLoop && currentIndex === dataLength - 1)
|
|
268
|
-
) {
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const intervalMs = Math.max(250, Math.floor(autoPlayInterval || 0));
|
|
273
|
-
|
|
274
|
-
if (!Number.isFinite(intervalMs)) {
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const interval = setInterval(() => {
|
|
279
|
-
if (isZoomed || isRotated) {
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
manager.goToNext();
|
|
284
|
-
}, intervalMs);
|
|
285
|
-
|
|
286
|
-
return () => clearInterval(interval);
|
|
287
|
-
}, [
|
|
288
|
-
autoPlay,
|
|
289
|
-
autoPlayInterval,
|
|
290
|
-
manager,
|
|
291
|
-
dataLength,
|
|
292
|
-
currentIndex,
|
|
293
|
-
enableLoop,
|
|
294
|
-
isZoomed,
|
|
295
|
-
isRotated,
|
|
296
|
-
]);
|
|
297
|
-
|
|
298
316
|
useEffect(() => {
|
|
299
317
|
onAnimationCompleteRef.current = triggerAnimation?.onAnimationComplete;
|
|
300
318
|
}, [triggerAnimation?.onAnimationComplete]);
|
|
@@ -341,32 +359,41 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
341
359
|
]);
|
|
342
360
|
|
|
343
361
|
useEffect(() => {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if (node && typeof node.measure === 'function') {
|
|
347
|
-
node.measure((_x, _y, width, height, pageX, pageY) => {
|
|
348
|
-
triggerRectRef.current = { height, width, x: pageX, y: pageY };
|
|
349
|
-
triggerOpacity.value = 0;
|
|
350
|
-
setShouldStartTriggerAnimation(true);
|
|
351
|
-
registry.clearTriggerNode(id);
|
|
352
|
-
});
|
|
362
|
+
if (!activeTriggerNode || typeof activeTriggerNode.measure !== 'function') {
|
|
363
|
+
return;
|
|
353
364
|
}
|
|
354
365
|
|
|
366
|
+
activeTriggerNode.measure((_x, _y, measuredWidth, measuredHeight, pageX, pageY) => {
|
|
367
|
+
const nextTriggerRect = {
|
|
368
|
+
height: measuredHeight,
|
|
369
|
+
width: measuredWidth,
|
|
370
|
+
x: pageX,
|
|
371
|
+
y: pageY,
|
|
372
|
+
} satisfies TriggerRect;
|
|
373
|
+
|
|
374
|
+
if (!isValidTriggerRect(nextTriggerRect)) {
|
|
375
|
+
registry.clearActiveTriggerNode(id);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
triggerRectRef.current = nextTriggerRect;
|
|
380
|
+
triggerOpacity.value = 0;
|
|
381
|
+
setShouldStartTriggerAnimation(true);
|
|
382
|
+
registry.clearActiveTriggerNode(id);
|
|
383
|
+
});
|
|
384
|
+
}, [activeTriggerNode, id, isValidTriggerRect, triggerOpacity]);
|
|
385
|
+
|
|
386
|
+
useEffect(() => {
|
|
355
387
|
return () => {
|
|
356
388
|
triggerRectRef.current = null;
|
|
357
389
|
};
|
|
358
|
-
}, [
|
|
390
|
+
}, []);
|
|
359
391
|
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
const
|
|
365
|
-
const endY = triggerRectRef.current.y + triggerRectRef.current.height / 2 - height / 2;
|
|
366
|
-
const endScale = Math.min(
|
|
367
|
-
triggerRectRef.current.width / width,
|
|
368
|
-
triggerRectRef.current.height / height,
|
|
369
|
-
);
|
|
392
|
+
const animateDismissToRect = useCallback(
|
|
393
|
+
(rect: TriggerRect) => {
|
|
394
|
+
const endX = rect.x + rect.width / 2 - width / 2;
|
|
395
|
+
const endY = rect.y + rect.height / 2 - height / 2;
|
|
396
|
+
const endScale = Math.min(rect.width / width, rect.height / height);
|
|
370
397
|
|
|
371
398
|
triggerScale.value = withTiming(endScale, animationConfig);
|
|
372
399
|
triggerTranslateX.value = withTiming(endX, animationConfig);
|
|
@@ -376,83 +403,62 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
376
403
|
scheduleOnRN(onDismiss);
|
|
377
404
|
}
|
|
378
405
|
});
|
|
379
|
-
|
|
380
|
-
|
|
406
|
+
},
|
|
407
|
+
[
|
|
408
|
+
animationConfig,
|
|
409
|
+
height,
|
|
410
|
+
onDismiss,
|
|
411
|
+
triggerOpacity,
|
|
412
|
+
triggerScale,
|
|
413
|
+
triggerTranslateX,
|
|
414
|
+
triggerTranslateY,
|
|
415
|
+
width,
|
|
416
|
+
],
|
|
417
|
+
);
|
|
381
418
|
|
|
419
|
+
const dismissWithoutTrigger = useCallback(() => {
|
|
382
420
|
if (onDismiss) {
|
|
383
421
|
scheduleOnRN(onDismiss);
|
|
384
422
|
}
|
|
385
|
-
}, [
|
|
386
|
-
animationConfig,
|
|
387
|
-
onDismiss,
|
|
388
|
-
onDismissStart,
|
|
389
|
-
width,
|
|
390
|
-
height,
|
|
391
|
-
triggerTranslateX,
|
|
392
|
-
triggerScale,
|
|
393
|
-
triggerTranslateY,
|
|
394
|
-
triggerOpacity,
|
|
395
|
-
]);
|
|
423
|
+
}, [onDismiss]);
|
|
396
424
|
|
|
397
|
-
const
|
|
398
|
-
(
|
|
399
|
-
if (!enableHorizontalSwipe) {
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
const { contentOffset } = event.nativeEvent;
|
|
404
|
-
const scrollIndex = Math.round(contentOffset.x / (width + itemSpacing));
|
|
425
|
+
const handleDismiss = useCallback(() => {
|
|
426
|
+
onDismissStart?.();
|
|
405
427
|
|
|
406
|
-
|
|
428
|
+
const dismissTargetIndex = pendingIndexRef.current;
|
|
429
|
+
const indexedTriggerNode = registry.getIndexedTriggerNode(id, dismissTargetIndex);
|
|
407
430
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
431
|
+
if (indexedTriggerNode && typeof indexedTriggerNode.measure === 'function') {
|
|
432
|
+
indexedTriggerNode.measure((_x, _y, measuredWidth, measuredHeight, pageX, pageY) => {
|
|
433
|
+
const currentTriggerRect = {
|
|
434
|
+
height: measuredHeight,
|
|
435
|
+
width: measuredWidth,
|
|
436
|
+
x: pageX,
|
|
437
|
+
y: pageY,
|
|
438
|
+
} satisfies TriggerRect;
|
|
411
439
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
);
|
|
440
|
+
if (isValidTriggerRect(currentTriggerRect)) {
|
|
441
|
+
animateDismissToRect(currentTriggerRect);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
417
444
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
445
|
+
if (isValidTriggerRect(triggerRectRef.current)) {
|
|
446
|
+
animateDismissToRect(triggerRectRef.current);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
421
449
|
|
|
422
|
-
|
|
450
|
+
dismissWithoutTrigger();
|
|
451
|
+
});
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
423
454
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
}
|
|
455
|
+
if (isValidTriggerRect(triggerRectRef.current)) {
|
|
456
|
+
animateDismissToRect(triggerRectRef.current);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
429
459
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
initialTranslateX.value = withTiming(0);
|
|
433
|
-
initialTranslateY.value = withTiming(0);
|
|
434
|
-
startScale.value = withTiming(1);
|
|
435
|
-
scale.value = withTiming(1);
|
|
436
|
-
rotation.value = 0;
|
|
437
|
-
}
|
|
438
|
-
},
|
|
439
|
-
[
|
|
440
|
-
scrollTo,
|
|
441
|
-
manager,
|
|
442
|
-
dataLength,
|
|
443
|
-
width,
|
|
444
|
-
itemSpacing,
|
|
445
|
-
enableHorizontalSwipe,
|
|
446
|
-
enableLoop,
|
|
447
|
-
translateX,
|
|
448
|
-
translateY,
|
|
449
|
-
scale,
|
|
450
|
-
initialTranslateX,
|
|
451
|
-
initialTranslateY,
|
|
452
|
-
startScale,
|
|
453
|
-
rotation,
|
|
454
|
-
],
|
|
455
|
-
);
|
|
460
|
+
dismissWithoutTrigger();
|
|
461
|
+
}, [animateDismissToRect, dismissWithoutTrigger, id, isValidTriggerRect, onDismissStart]);
|
|
456
462
|
|
|
457
463
|
const dismissGesture = useMemo(() => {
|
|
458
464
|
const canDismiss = !isZoomed && dismissOptions.enabled;
|
|
@@ -644,41 +650,21 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
644
650
|
const doubleTapGesture = useMemo(
|
|
645
651
|
() =>
|
|
646
652
|
Gesture.Tap()
|
|
647
|
-
.enabled(enableDoubleTapZoom)
|
|
653
|
+
.enabled(enableDoubleTapZoom && Platform.OS !== 'web')
|
|
648
654
|
.numberOfTaps(2)
|
|
649
655
|
.onEnd((event) => {
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
translateX
|
|
658
|
-
|
|
659
|
-
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
|
|
660
|
-
});
|
|
661
|
-
translateY.value = withTiming(-centerY * (nextScale - 1), {
|
|
662
|
-
duration: 300,
|
|
663
|
-
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
|
|
664
|
-
});
|
|
665
|
-
} else {
|
|
666
|
-
translateX.value = withTiming(0, {
|
|
667
|
-
duration: 300,
|
|
668
|
-
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
|
|
669
|
-
});
|
|
670
|
-
translateY.value = withTiming(0, {
|
|
671
|
-
duration: 300,
|
|
672
|
-
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
|
|
673
|
-
});
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
scale.value = withTiming(nextScale, {
|
|
677
|
-
duration: 300,
|
|
678
|
-
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
|
|
656
|
+
applyTapZoomAtPoint({
|
|
657
|
+
x: event.x,
|
|
658
|
+
y: event.y,
|
|
659
|
+
width,
|
|
660
|
+
height,
|
|
661
|
+
maxZoomScale,
|
|
662
|
+
scale,
|
|
663
|
+
translateX,
|
|
664
|
+
translateY,
|
|
679
665
|
});
|
|
680
666
|
}),
|
|
681
|
-
[
|
|
667
|
+
[enableDoubleTapZoom, height, maxZoomScale, scale, translateX, translateY, width],
|
|
682
668
|
);
|
|
683
669
|
|
|
684
670
|
const zoomGesture = useMemo(
|
|
@@ -716,9 +702,30 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
716
702
|
return Gesture.Native().requireExternalGestureToFail(dismissGestureRef);
|
|
717
703
|
}, []);
|
|
718
704
|
|
|
719
|
-
const onScrollBeginDrag
|
|
720
|
-
|
|
721
|
-
|
|
705
|
+
const { onMomentumScrollEnd, onScroll, onScrollBeginDrag, onWebDoubleClick } =
|
|
706
|
+
useGestureViewerPaging({
|
|
707
|
+
adjustedInitialIndex,
|
|
708
|
+
autoPlay,
|
|
709
|
+
autoPlayInterval,
|
|
710
|
+
currentIndex,
|
|
711
|
+
dataLength,
|
|
712
|
+
enableDoubleTapZoom,
|
|
713
|
+
enableHorizontalSwipe,
|
|
714
|
+
enableLoop,
|
|
715
|
+
height,
|
|
716
|
+
isRotated,
|
|
717
|
+
isZoomed,
|
|
718
|
+
itemSpacing,
|
|
719
|
+
manager,
|
|
720
|
+
maxZoomScale,
|
|
721
|
+
scale,
|
|
722
|
+
scrollTo,
|
|
723
|
+
syncCurrentIndex,
|
|
724
|
+
syncPendingIndex,
|
|
725
|
+
translateX,
|
|
726
|
+
translateY,
|
|
727
|
+
width,
|
|
728
|
+
});
|
|
722
729
|
|
|
723
730
|
return {
|
|
724
731
|
animatedStyle,
|
|
@@ -732,7 +739,9 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
732
739
|
isZoomed,
|
|
733
740
|
listRef,
|
|
734
741
|
nativeScrollGesture,
|
|
742
|
+
onWebDoubleClick,
|
|
735
743
|
onMomentumScrollEnd,
|
|
744
|
+
onScroll,
|
|
736
745
|
|
|
737
746
|
onScrollBeginDrag,
|
|
738
747
|
zoomGesture,
|
|
@@ -0,0 +1,128 @@
|
|
|
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
|
+
syncPendingIndex,
|
|
24
|
+
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
|
+
}
|
|
48
|
+
|
|
49
|
+
manager.goToNext();
|
|
50
|
+
}, intervalMs);
|
|
51
|
+
|
|
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;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const { contentOffset } = event.nativeEvent;
|
|
71
|
+
const scrollIndex = Math.round(contentOffset.x / (width + itemSpacing));
|
|
72
|
+
|
|
73
|
+
const isLoopHandled = manager?.handleMomentumScrollEnd(scrollIndex);
|
|
74
|
+
|
|
75
|
+
if (isLoopHandled) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const { realIndex, needsJump, jumpToIndex } = getLoopAdjustedIndex(
|
|
80
|
+
scrollIndex,
|
|
81
|
+
dataLength,
|
|
82
|
+
enableLoop,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
if (needsJump && jumpToIndex !== undefined) {
|
|
86
|
+
scrollTo(jumpToIndex, false);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
syncCurrentIndex(realIndex);
|
|
90
|
+
},
|
|
91
|
+
[
|
|
92
|
+
dataLength,
|
|
93
|
+
enableHorizontalSwipe,
|
|
94
|
+
enableLoop,
|
|
95
|
+
itemSpacing,
|
|
96
|
+
manager,
|
|
97
|
+
scrollTo,
|
|
98
|
+
syncCurrentIndex,
|
|
99
|
+
width,
|
|
100
|
+
],
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const onScroll = useCallback(
|
|
104
|
+
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
105
|
+
if (!enableHorizontalSwipe) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const { contentOffset } = event.nativeEvent;
|
|
110
|
+
const scrollIndex = Math.round(contentOffset.x / (width + itemSpacing));
|
|
111
|
+
const { realIndex } = getLoopAdjustedIndex(scrollIndex, dataLength, enableLoop);
|
|
112
|
+
|
|
113
|
+
syncPendingIndex(realIndex);
|
|
114
|
+
},
|
|
115
|
+
[dataLength, enableHorizontalSwipe, enableLoop, itemSpacing, syncPendingIndex, width],
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const onScrollBeginDrag = useCallback(() => {
|
|
119
|
+
manager?.handleScrollBeginDrag();
|
|
120
|
+
}, [manager]);
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
onMomentumScrollEnd,
|
|
124
|
+
onScroll,
|
|
125
|
+
onScrollBeginDrag,
|
|
126
|
+
onWebDoubleClick: undefined,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
syncPendingIndex: (nextIndex: number) => void;
|
|
25
|
+
translateX: SharedValue<number>;
|
|
26
|
+
translateY: SharedValue<number>;
|
|
27
|
+
width: number;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type UseGestureViewerPagingResult = {
|
|
31
|
+
onMomentumScrollEnd?: ScrollViewProps['onMomentumScrollEnd'];
|
|
32
|
+
onScroll?: ScrollViewProps['onScroll'];
|
|
33
|
+
onScrollBeginDrag?: ScrollViewProps['onScrollBeginDrag'];
|
|
34
|
+
onWebDoubleClick?: (event: any) => void;
|
|
35
|
+
};
|