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.
Files changed (55) hide show
  1. package/lib/module/GestureTrigger.js +22 -5
  2. package/lib/module/GestureTrigger.js.map +1 -1
  3. package/lib/module/GestureViewer.js +50 -40
  4. package/lib/module/GestureViewer.js.map +1 -1
  5. package/lib/module/GestureViewerManager.js +26 -6
  6. package/lib/module/GestureViewerManager.js.map +1 -1
  7. package/lib/module/GestureViewerRegistry.js +52 -8
  8. package/lib/module/GestureViewerRegistry.js.map +1 -1
  9. package/lib/module/useGestureViewer.js +149 -113
  10. package/lib/module/useGestureViewer.js.map +1 -1
  11. package/lib/module/useGestureViewerPaging.js +82 -0
  12. package/lib/module/useGestureViewerPaging.js.map +1 -0
  13. package/lib/module/useGestureViewerPaging.types.js +4 -0
  14. package/lib/module/useGestureViewerPaging.types.js.map +1 -0
  15. package/lib/module/useGestureViewerPaging.web.js +254 -0
  16. package/lib/module/useGestureViewerPaging.web.js.map +1 -0
  17. package/lib/module/utils/index.js +9 -0
  18. package/lib/module/utils/index.js.map +1 -1
  19. package/lib/module/utils/tapZoom.js +34 -0
  20. package/lib/module/utils/tapZoom.js.map +1 -0
  21. package/lib/module/utils/webScroll.js +70 -0
  22. package/lib/module/utils/webScroll.js.map +1 -0
  23. package/lib/typescript/src/GestureTrigger.d.ts +9 -3
  24. package/lib/typescript/src/GestureTrigger.d.ts.map +1 -1
  25. package/lib/typescript/src/GestureViewer.d.ts.map +1 -1
  26. package/lib/typescript/src/GestureViewerManager.d.ts +5 -0
  27. package/lib/typescript/src/GestureViewerManager.d.ts.map +1 -1
  28. package/lib/typescript/src/GestureViewerRegistry.d.ts +11 -4
  29. package/lib/typescript/src/GestureViewerRegistry.d.ts.map +1 -1
  30. package/lib/typescript/src/useGestureViewer.d.ts +4 -3
  31. package/lib/typescript/src/useGestureViewer.d.ts.map +1 -1
  32. package/lib/typescript/src/useGestureViewerPaging.d.ts +3 -0
  33. package/lib/typescript/src/useGestureViewerPaging.d.ts.map +1 -0
  34. package/lib/typescript/src/useGestureViewerPaging.types.d.ts +33 -0
  35. package/lib/typescript/src/useGestureViewerPaging.types.d.ts.map +1 -0
  36. package/lib/typescript/src/useGestureViewerPaging.web.d.ts +3 -0
  37. package/lib/typescript/src/useGestureViewerPaging.web.d.ts.map +1 -0
  38. package/lib/typescript/src/utils/index.d.ts +7 -0
  39. package/lib/typescript/src/utils/index.d.ts.map +1 -1
  40. package/lib/typescript/src/utils/tapZoom.d.ts +12 -0
  41. package/lib/typescript/src/utils/tapZoom.d.ts.map +1 -0
  42. package/lib/typescript/src/utils/webScroll.d.ts +18 -0
  43. package/lib/typescript/src/utils/webScroll.d.ts.map +1 -0
  44. package/package.json +1 -1
  45. package/src/GestureTrigger.tsx +31 -6
  46. package/src/GestureViewer.tsx +39 -15
  47. package/src/GestureViewerManager.ts +29 -6
  48. package/src/GestureViewerRegistry.ts +67 -8
  49. package/src/useGestureViewer.ts +174 -165
  50. package/src/useGestureViewerPaging.ts +128 -0
  51. package/src/useGestureViewerPaging.types.ts +35 -0
  52. package/src/useGestureViewerPaging.web.ts +410 -0
  53. package/src/utils/index.ts +17 -1
  54. package/src/utils/tapZoom.ts +44 -0
  55. package/src/utils/webScroll.ts +96 -0
@@ -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 { createBoundsConstraint, createScrollAction, getLoopAdjustedIndex } from './utils';
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) => setCurrentIndex(state.currentIndex));
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
- const node = registry.getTriggerNode(id);
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
- }, [id, triggerOpacity]);
390
+ }, []);
359
391
 
360
- const handleDismiss = useCallback(() => {
361
- onDismissStart?.();
362
-
363
- if (triggerRectRef.current) {
364
- const endX = triggerRectRef.current.x + triggerRectRef.current.width / 2 - width / 2;
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
- return;
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 onMomentumScrollEnd = useCallback(
398
- (event: NativeSyntheticEvent<NativeScrollEvent>) => {
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
- const isLoopHandled = manager?.handleMomentumScrollEnd(scrollIndex);
428
+ const dismissTargetIndex = pendingIndexRef.current;
429
+ const indexedTriggerNode = registry.getIndexedTriggerNode(id, dismissTargetIndex);
407
430
 
408
- if (isLoopHandled) {
409
- return;
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
- const { realIndex, needsJump, jumpToIndex } = getLoopAdjustedIndex(
413
- scrollIndex,
414
- dataLength,
415
- enableLoop,
416
- );
440
+ if (isValidTriggerRect(currentTriggerRect)) {
441
+ animateDismissToRect(currentTriggerRect);
442
+ return;
443
+ }
417
444
 
418
- if (needsJump && jumpToIndex !== undefined) {
419
- scrollTo(jumpToIndex, false);
420
- }
445
+ if (isValidTriggerRect(triggerRectRef.current)) {
446
+ animateDismissToRect(triggerRectRef.current);
447
+ return;
448
+ }
421
449
 
422
- const currentIndex = manager?.getState()?.currentIndex;
450
+ dismissWithoutTrigger();
451
+ });
452
+ return;
453
+ }
423
454
 
424
- if (realIndex !== currentIndex && realIndex >= 0 && realIndex < dataLength) {
425
- if (manager) {
426
- manager.setCurrentIndex(realIndex);
427
- manager.notifyStateChange();
428
- }
455
+ if (isValidTriggerRect(triggerRectRef.current)) {
456
+ animateDismissToRect(triggerRectRef.current);
457
+ return;
458
+ }
429
459
 
430
- translateX.value = withTiming(0);
431
- translateY.value = withTiming(0);
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
- const nextScale = scale.value > 1 ? 1 : maxZoomScale;
651
-
652
- if (nextScale > 1) {
653
- const centerX = event.x - width / 2;
654
- const centerY = event.y - height / 2;
655
-
656
- // NOTE 확대로 밀려난 거리만큼 반대로 이동해서 탭 지점을 제자리에 유지
657
- translateX.value = withTiming(-centerX * (nextScale - 1), {
658
- duration: 300,
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
- [scale, enableDoubleTapZoom, maxZoomScale, translateX, translateY, width, height],
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 = useCallback(() => {
720
- manager?.handleScrollBeginDrag();
721
- }, [manager]);
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
+ };