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.
Files changed (67) hide show
  1. package/README.md +1 -1
  2. package/lib/module/GestureTrigger.js.map +1 -1
  3. package/lib/module/GestureViewer.js +23 -6
  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.map +1 -1
  8. package/lib/module/WebPagingFixStyle.js.map +1 -1
  9. package/lib/module/useGestureViewer.js +205 -229
  10. package/lib/module/useGestureViewer.js.map +1 -1
  11. package/lib/module/useGestureViewerController.js.map +1 -1
  12. package/lib/module/useGestureViewerEvent.js.map +1 -1
  13. package/lib/module/useGestureViewerPaging.js +68 -0
  14. package/lib/module/useGestureViewerPaging.js.map +1 -0
  15. package/lib/module/useGestureViewerPaging.types.js +4 -0
  16. package/lib/module/useGestureViewerPaging.types.js.map +1 -0
  17. package/lib/module/useGestureViewerPaging.web.js +241 -0
  18. package/lib/module/useGestureViewerPaging.web.js.map +1 -0
  19. package/lib/module/useGestureViewerState.js.map +1 -1
  20. package/lib/module/utils/index.js +23 -16
  21. package/lib/module/utils/index.js.map +1 -1
  22. package/lib/module/utils/tapZoom.js +34 -0
  23. package/lib/module/utils/tapZoom.js.map +1 -0
  24. package/lib/module/utils/webScroll.js +70 -0
  25. package/lib/module/utils/webScroll.js.map +1 -0
  26. package/lib/typescript/src/GestureTrigger.d.ts +1 -1
  27. package/lib/typescript/src/GestureTrigger.d.ts.map +1 -1
  28. package/lib/typescript/src/GestureViewer.d.ts.map +1 -1
  29. package/lib/typescript/src/GestureViewerManager.d.ts +5 -0
  30. package/lib/typescript/src/GestureViewerManager.d.ts.map +1 -1
  31. package/lib/typescript/src/GestureViewerRegistry.d.ts.map +1 -1
  32. package/lib/typescript/src/WebPagingFixStyle.d.ts.map +1 -1
  33. package/lib/typescript/src/types.d.ts.map +1 -1
  34. package/lib/typescript/src/useGestureViewer.d.ts +13 -11
  35. package/lib/typescript/src/useGestureViewer.d.ts.map +1 -1
  36. package/lib/typescript/src/useGestureViewerController.d.ts.map +1 -1
  37. package/lib/typescript/src/useGestureViewerEvent.d.ts.map +1 -1
  38. package/lib/typescript/src/useGestureViewerPaging.d.ts +3 -0
  39. package/lib/typescript/src/useGestureViewerPaging.d.ts.map +1 -0
  40. package/lib/typescript/src/useGestureViewerPaging.types.d.ts +32 -0
  41. package/lib/typescript/src/useGestureViewerPaging.types.d.ts.map +1 -0
  42. package/lib/typescript/src/useGestureViewerPaging.web.d.ts +3 -0
  43. package/lib/typescript/src/useGestureViewerPaging.web.d.ts.map +1 -0
  44. package/lib/typescript/src/useGestureViewerState.d.ts.map +1 -1
  45. package/lib/typescript/src/utils/index.d.ts +8 -1
  46. package/lib/typescript/src/utils/index.d.ts.map +1 -1
  47. package/lib/typescript/src/utils/tapZoom.d.ts +12 -0
  48. package/lib/typescript/src/utils/tapZoom.d.ts.map +1 -0
  49. package/lib/typescript/src/utils/webScroll.d.ts +18 -0
  50. package/lib/typescript/src/utils/webScroll.d.ts.map +1 -0
  51. package/package.json +86 -80
  52. package/src/GestureTrigger.tsx +9 -2
  53. package/src/GestureViewer.tsx +77 -28
  54. package/src/GestureViewerManager.ts +62 -12
  55. package/src/GestureViewerRegistry.ts +1 -0
  56. package/src/WebPagingFixStyle.tsx +1 -0
  57. package/src/types.ts +44 -32
  58. package/src/useGestureViewer.ts +292 -313
  59. package/src/useGestureViewerController.ts +1 -0
  60. package/src/useGestureViewerEvent.ts +5 -1
  61. package/src/useGestureViewerPaging.ts +112 -0
  62. package/src/useGestureViewerPaging.types.ts +34 -0
  63. package/src/useGestureViewerPaging.web.ts +390 -0
  64. package/src/useGestureViewerState.ts +1 -0
  65. package/src/utils/index.ts +48 -16
  66. package/src/utils/tapZoom.ts +44 -0
  67. package/src/utils/webScroll.ts +96 -0
@@ -1,11 +1,6 @@
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';
8
- import { Gesture } from 'react-native-gesture-handler';
2
+ import { InteractionManager, Platform, useWindowDimensions } from 'react-native';
3
+ import { Gesture, type GestureType } from 'react-native-gesture-handler';
9
4
  import {
10
5
  Easing,
11
6
  interpolate,
@@ -16,10 +11,13 @@ import {
16
11
  withTiming,
17
12
  } from 'react-native-reanimated';
18
13
  import { scheduleOnRN } from 'react-native-worklets';
14
+
19
15
  import type GestureViewerManager from './GestureViewerManager';
20
16
  import { registry } from './GestureViewerRegistry';
21
17
  import type { GestureViewerProps, TriggerRect } from './types';
22
- import { createBoundsConstraint, createScrollAction, getLoopAdjustedIndex } from './utils';
18
+ import { useGestureViewerPaging } from './useGestureViewerPaging';
19
+ import { createBoundsConstraint, createScrollAction } from './utils';
20
+ import { applyTapZoomAtPoint } from './utils/tapZoom';
23
21
 
24
22
  type UseGestureViewerProps<ItemT, LC> = Omit<
25
23
  GestureViewerProps<ItemT, LC>,
@@ -56,6 +54,8 @@ export const useGestureViewer = <ItemT, LC>({
56
54
  const width = customWidth || screenWidth;
57
55
  const height = customHeight || screenHeight;
58
56
 
57
+ const dismissGestureRef = useRef<GestureType>(undefined);
58
+
59
59
  const [isZoomed, setIsZoomed] = useState(false);
60
60
  const [isRotated, setIsRotated] = useState(false);
61
61
  const [isPinching, setIsPinching] = useState(false);
@@ -90,20 +90,21 @@ export const useGestureViewer = <ItemT, LC>({
90
90
  const animationConfig = useMemo(
91
91
  () => ({
92
92
  duration: triggerAnimation?.duration ?? 300,
93
- easing: triggerAnimation?.easing ?? Easing.bezier(0.25, 0.1, 0.25, 1.0),
93
+ easing: triggerAnimation?.easing ?? Easing.bezier(0.25, 0.1, 0.25, 1),
94
94
  reduceMotion: triggerAnimation?.reduceMotion,
95
95
  }),
96
96
  [triggerAnimation?.duration, triggerAnimation?.easing, triggerAnimation?.reduceMotion],
97
97
  );
98
98
 
99
- const dismissOptions = useMemo(() => {
100
- return {
99
+ const dismissOptions = useMemo(
100
+ () => ({
101
101
  enabled: dismiss?.enabled ?? true,
102
- threshold: dismiss?.threshold ?? 80,
103
- resistance: dismiss?.resistance ?? 2,
104
102
  fadeBackdrop: dismiss?.fadeBackdrop ?? true,
105
- };
106
- }, [dismiss?.enabled, dismiss?.threshold, dismiss?.resistance, dismiss?.fadeBackdrop]);
103
+ resistance: dismiss?.resistance ?? 2,
104
+ threshold: dismiss?.threshold ?? 80,
105
+ }),
106
+ [dismiss?.enabled, dismiss?.threshold, dismiss?.resistance, dismiss?.fadeBackdrop],
107
+ );
107
108
 
108
109
  const adjustedInitialIndex = useMemo(() => {
109
110
  if (enableLoop && dataLength > 1) {
@@ -113,7 +114,10 @@ export const useGestureViewer = <ItemT, LC>({
113
114
  return initialIndex;
114
115
  }, [enableLoop, dataLength, initialIndex]);
115
116
 
116
- const constrainTranslation = useMemo(() => createBoundsConstraint({ width, height }), [width, height]);
117
+ const constrainTranslation = useMemo(
118
+ () => createBoundsConstraint({ height, width }),
119
+ [width, height],
120
+ );
117
121
 
118
122
  const scrollTo = useCallback(
119
123
  (index: number, animated: boolean) => {
@@ -124,6 +128,35 @@ export const useGestureViewer = <ItemT, LC>({
124
128
  [width, itemSpacing],
125
129
  );
126
130
 
131
+ const resetTransformState = useCallback(() => {
132
+ translateX.value = withTiming(0);
133
+ translateY.value = withTiming(0);
134
+ initialTranslateX.value = withTiming(0);
135
+ initialTranslateY.value = withTiming(0);
136
+ startScale.value = withTiming(1);
137
+ scale.value = withTiming(1);
138
+ rotation.value = 0;
139
+ }, [initialTranslateX, initialTranslateY, rotation, scale, startScale, translateX, translateY]);
140
+
141
+ const syncCurrentIndex = useCallback(
142
+ (nextIndex: number) => {
143
+ if (!manager || nextIndex < 0 || nextIndex >= dataLength) {
144
+ return;
145
+ }
146
+
147
+ const managerCurrentIndex = manager.getState().currentIndex;
148
+
149
+ if (nextIndex === managerCurrentIndex) {
150
+ return;
151
+ }
152
+
153
+ manager.setCurrentIndex(nextIndex);
154
+ manager.notifyStateChange();
155
+ resetTransformState();
156
+ },
157
+ [dataLength, manager, resetTransformState],
158
+ );
159
+
127
160
  const emitZoomChange = useCallback(
128
161
  (currentScale: number, prevScale: number | null) => {
129
162
  if (manager) {
@@ -193,6 +226,7 @@ export const useGestureViewer = <ItemT, LC>({
193
226
  manager.setWidth(width + itemSpacing);
194
227
  manager.setHeight(height);
195
228
  manager.setZoomSharedValues(scale, translateX, translateY, maxZoomScale);
229
+ manager.setResetTransformCallback(resetTransformState);
196
230
  manager.setRotation(rotation);
197
231
  manager.setEnableLoop(enableLoop);
198
232
  manager.notifyStateChange();
@@ -207,6 +241,7 @@ export const useGestureViewer = <ItemT, LC>({
207
241
  enableLoop,
208
242
  scale,
209
243
  height,
244
+ resetTransformState,
210
245
  translateX,
211
246
  translateY,
212
247
  rotation,
@@ -239,36 +274,16 @@ export const useGestureViewer = <ItemT, LC>({
239
274
  return () => {
240
275
  runAfterInteractions?.cancel();
241
276
  };
242
- }, [adjustedInitialIndex, translateY, backdropOpacity, translateX, scale, startScale, rotation, scrollTo]);
243
-
244
- useEffect(() => {
245
- if (
246
- !autoPlay ||
247
- !manager ||
248
- dataLength <= 1 ||
249
- isZoomed ||
250
- isRotated ||
251
- (!enableLoop && currentIndex === dataLength - 1)
252
- ) {
253
- return;
254
- }
255
-
256
- const intervalMs = Math.max(250, Math.floor(autoPlayInterval || 0));
257
-
258
- if (!Number.isFinite(intervalMs)) {
259
- return;
260
- }
261
-
262
- const interval = setInterval(() => {
263
- if (isZoomed || isRotated) {
264
- return;
265
- }
266
-
267
- manager.goToNext();
268
- }, intervalMs);
269
-
270
- return () => clearInterval(interval);
271
- }, [autoPlay, autoPlayInterval, manager, dataLength, currentIndex, enableLoop, isZoomed, isRotated]);
277
+ }, [
278
+ adjustedInitialIndex,
279
+ translateY,
280
+ backdropOpacity,
281
+ translateX,
282
+ scale,
283
+ startScale,
284
+ rotation,
285
+ scrollTo,
286
+ ]);
272
287
 
273
288
  useEffect(() => {
274
289
  onAnimationCompleteRef.current = triggerAnimation?.onAnimationComplete;
@@ -320,7 +335,7 @@ export const useGestureViewer = <ItemT, LC>({
320
335
 
321
336
  if (node && typeof node.measure === 'function') {
322
337
  node.measure((_x, _y, width, height, pageX, pageY) => {
323
- triggerRectRef.current = { x: pageX, y: pageY, width, height };
338
+ triggerRectRef.current = { height, width, x: pageX, y: pageY };
324
339
  triggerOpacity.value = 0;
325
340
  setShouldStartTriggerAnimation(true);
326
341
  registry.clearTriggerNode(id);
@@ -338,7 +353,10 @@ export const useGestureViewer = <ItemT, LC>({
338
353
  if (triggerRectRef.current) {
339
354
  const endX = triggerRectRef.current.x + triggerRectRef.current.width / 2 - width / 2;
340
355
  const endY = triggerRectRef.current.y + triggerRectRef.current.height / 2 - height / 2;
341
- const endScale = Math.min(triggerRectRef.current.width / width, triggerRectRef.current.height / height);
356
+ const endScale = Math.min(
357
+ triggerRectRef.current.width / width,
358
+ triggerRectRef.current.height / height,
359
+ );
342
360
 
343
361
  triggerScale.value = withTiming(endScale, animationConfig);
344
362
  triggerTranslateX.value = withTiming(endX, animationConfig);
@@ -366,62 +384,6 @@ export const useGestureViewer = <ItemT, LC>({
366
384
  triggerOpacity,
367
385
  ]);
368
386
 
369
- const onMomentumScrollEnd = useCallback(
370
- (event: NativeSyntheticEvent<NativeScrollEvent>) => {
371
- if (!enableHorizontalSwipe) {
372
- return;
373
- }
374
-
375
- const contentOffset = event.nativeEvent.contentOffset;
376
- const scrollIndex = Math.round(contentOffset.x / (width + itemSpacing));
377
-
378
- const isLoopHandled = manager?.handleMomentumScrollEnd(scrollIndex);
379
-
380
- if (isLoopHandled) {
381
- return;
382
- }
383
-
384
- const { realIndex, needsJump, jumpToIndex } = getLoopAdjustedIndex(scrollIndex, dataLength, enableLoop);
385
-
386
- if (needsJump && jumpToIndex !== undefined) {
387
- scrollTo(jumpToIndex, false);
388
- }
389
-
390
- const currentIndex = manager?.getState()?.currentIndex;
391
-
392
- if (realIndex !== currentIndex && realIndex >= 0 && realIndex < dataLength) {
393
- if (manager) {
394
- manager.setCurrentIndex(realIndex);
395
- manager.notifyStateChange();
396
- }
397
-
398
- translateX.value = withTiming(0);
399
- translateY.value = withTiming(0);
400
- initialTranslateX.value = withTiming(0);
401
- initialTranslateY.value = withTiming(0);
402
- startScale.value = withTiming(1);
403
- scale.value = withTiming(1);
404
- rotation.value = 0;
405
- }
406
- },
407
- [
408
- scrollTo,
409
- manager,
410
- dataLength,
411
- width,
412
- itemSpacing,
413
- enableHorizontalSwipe,
414
- enableLoop,
415
- translateX,
416
- translateY,
417
- scale,
418
- initialTranslateX,
419
- initialTranslateY,
420
- startScale,
421
- rotation,
422
- ],
423
- );
424
-
425
387
  const dismissGesture = useMemo(() => {
426
388
  const canDismiss = !isZoomed && dismissOptions.enabled;
427
389
 
@@ -431,6 +393,7 @@ export const useGestureViewer = <ItemT, LC>({
431
393
  .activeCursor('grabbing')
432
394
  .activeOffsetY([-10, 10])
433
395
  .failOffsetX([-10, 10])
396
+ .withRef(dismissGestureRef)
434
397
  .enabled(canDismiss)
435
398
  .onUpdate((event) => {
436
399
  translateY.value = event.translationY / dismissOptions.resistance;
@@ -442,221 +405,210 @@ export const useGestureViewer = <ItemT, LC>({
442
405
  }
443
406
 
444
407
  translateY.value = withSpring(0, {
445
- stiffness: 600,
446
408
  damping: 50,
409
+ energyThreshold: 6e-9,
447
410
  mass: 4,
448
411
  overshootClamping: false,
449
- energyThreshold: 6e-9,
412
+ stiffness: 600,
450
413
  });
451
414
  });
452
415
  }, [translateY, dismissOptions, handleDismiss, isZoomed]);
453
416
 
454
- const zoomPinchGesture = useMemo(() => {
455
- return Gesture.Pinch()
456
- .enabled(enablePinchZoom)
457
- .onTouchesDown((event) => {
458
- if (event.numberOfTouches === 2) {
459
- scheduleOnRN(setIsPinching, true);
460
- }
461
- })
462
- .onBegin((event) => {
463
- startScale.value = scale.value;
464
- initialTranslateX.value = translateX.value;
465
- initialTranslateY.value = translateY.value;
466
- lastFocalX.value = event.focalX;
467
- lastFocalY.value = event.focalY;
468
- })
469
- .onUpdate((event) => {
470
- const newScale = startScale.value * event.scale;
471
-
472
- scale.value = newScale;
473
-
474
- if (newScale <= 1) {
475
- translateX.value = withTiming(0);
476
- translateY.value = withTiming(0);
477
- return;
478
- }
479
-
480
- const focalDeltaX = Math.abs(event.focalX - lastFocalX.value);
481
- const focalDeltaY = Math.abs(event.focalY - lastFocalY.value);
482
- const threshold = 50;
483
-
484
- if (focalDeltaX < threshold && focalDeltaY < threshold) {
417
+ const zoomPinchGesture = useMemo(
418
+ () =>
419
+ Gesture.Pinch()
420
+ .enabled(enablePinchZoom)
421
+ .onTouchesDown((event) => {
422
+ if (event.numberOfTouches === 2) {
423
+ scheduleOnRN(setIsPinching, true);
424
+ }
425
+ })
426
+ .onBegin((event) => {
427
+ startScale.value = scale.value;
428
+ initialTranslateX.value = translateX.value;
429
+ initialTranslateY.value = translateY.value;
485
430
  lastFocalX.value = event.focalX;
486
431
  lastFocalY.value = event.focalY;
487
- }
488
-
489
- const deltaScale = newScale - startScale.value;
490
- const centerX = lastFocalX.value - width / 2;
491
- const centerY = lastFocalY.value - height / 2;
492
-
493
- // NOTE 새로운 이동값 = 기존 이동값 - (중심점 거리 × 스케일 변화량) / 원래 스케일 (중심점이 화면 중심에서 멀수록, 확대 배율이 클수록 더 많이 이동)
494
- const newTranslateX = initialTranslateX.value - (centerX * deltaScale) / startScale.value;
495
- const newTranslateY = initialTranslateY.value - (centerY * deltaScale) / startScale.value;
432
+ })
433
+ .onUpdate((event) => {
434
+ const newScale = startScale.value * event.scale;
435
+
436
+ scale.value = newScale;
437
+
438
+ if (newScale <= 1) {
439
+ translateX.value = withTiming(0);
440
+ translateY.value = withTiming(0);
441
+ return;
442
+ }
443
+
444
+ const focalDeltaX = Math.abs(event.focalX - lastFocalX.value);
445
+ const focalDeltaY = Math.abs(event.focalY - lastFocalY.value);
446
+ const threshold = 50;
447
+
448
+ if (focalDeltaX < threshold && focalDeltaY < threshold) {
449
+ lastFocalX.value = event.focalX;
450
+ lastFocalY.value = event.focalY;
451
+ }
452
+
453
+ const deltaScale = newScale - startScale.value;
454
+ const centerX = lastFocalX.value - width / 2;
455
+ const centerY = lastFocalY.value - height / 2;
456
+
457
+ // NOTE 새로운 이동값 = 기존 이동값 - (중심점 거리 × 스케일 변화량) / 원래 스케일 (중심점이 화면 중심에서 멀수록, 확대 배율이 클수록 더 많이 이동)
458
+ const newTranslateX = initialTranslateX.value - (centerX * deltaScale) / startScale.value;
459
+ const newTranslateY = initialTranslateY.value - (centerY * deltaScale) / startScale.value;
460
+
461
+ const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } =
462
+ constrainTranslation({
463
+ scale: newScale,
464
+ translateX: newTranslateX,
465
+ translateY: newTranslateY,
466
+ });
496
467
 
497
- const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } = constrainTranslation({
498
- translateX: newTranslateX,
499
- translateY: newTranslateY,
500
- scale: newScale,
501
- });
502
-
503
- translateX.value = constrainedTranslateX;
504
- translateY.value = constrainedTranslateY;
505
- })
506
- .onEnd(() => {
507
- if (scale.value > maxZoomScale) {
508
- scale.value = withTiming(maxZoomScale, {
509
- duration: 300,
510
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
511
- });
512
-
513
- const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } = constrainTranslation({
514
- translateX: translateX.value,
515
- translateY: translateY.value,
516
- scale: maxZoomScale,
517
- });
468
+ translateX.value = constrainedTranslateX;
469
+ translateY.value = constrainedTranslateY;
470
+ })
471
+ .onEnd(() => {
472
+ if (scale.value > maxZoomScale) {
473
+ scale.value = withTiming(maxZoomScale, {
474
+ duration: 300,
475
+ easing: Easing.bezier(0.25, 0.1, 0.25, 1),
476
+ });
477
+
478
+ const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } =
479
+ constrainTranslation({
480
+ scale: maxZoomScale,
481
+ translateX: translateX.value,
482
+ translateY: translateY.value,
483
+ });
484
+
485
+ translateX.value = withTiming(constrainedTranslateX);
486
+ translateY.value = withTiming(constrainedTranslateY);
487
+
488
+ return;
489
+ }
490
+
491
+ if (scale.value < 1) {
492
+ scale.value = withTiming(1, {
493
+ duration: 300,
494
+ easing: Easing.bezier(0.25, 0.1, 0.25, 1),
495
+ });
496
+ translateX.value = withTiming(0);
497
+ translateY.value = withTiming(0);
498
+ initialTranslateX.value = withTiming(0);
499
+ initialTranslateY.value = withTiming(0);
500
+ return;
501
+ }
502
+
503
+ const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } =
504
+ constrainTranslation({
505
+ scale: scale.value,
506
+ translateX: translateX.value,
507
+ translateY: translateY.value,
508
+ });
518
509
 
519
510
  translateX.value = withTiming(constrainedTranslateX);
520
511
  translateY.value = withTiming(constrainedTranslateY);
512
+ })
513
+ .onTouchesUp(() => {
514
+ scheduleOnRN(setIsPinching, false);
515
+ })
516
+ .onFinalize(() => {
517
+ scheduleOnRN(setIsPinching, false);
518
+ }),
519
+ [
520
+ scale,
521
+ enablePinchZoom,
522
+ maxZoomScale,
523
+ translateX,
524
+ translateY,
525
+ startScale,
526
+ initialTranslateX,
527
+ initialTranslateY,
528
+ width,
529
+ height,
530
+ constrainTranslation,
531
+ lastFocalX,
532
+ lastFocalY,
533
+ ],
534
+ );
521
535
 
522
- return;
523
- }
524
-
525
- if (scale.value < 1) {
526
- scale.value = withTiming(1, {
527
- duration: 300,
528
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
529
- });
530
- translateX.value = withTiming(0);
531
- translateY.value = withTiming(0);
532
- initialTranslateX.value = withTiming(0);
533
- initialTranslateY.value = withTiming(0);
534
- return;
535
- }
536
-
537
- const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } = constrainTranslation({
538
- translateX: translateX.value,
539
- translateY: translateY.value,
540
- scale: scale.value,
541
- });
542
-
543
- translateX.value = withTiming(constrainedTranslateX);
544
- translateY.value = withTiming(constrainedTranslateY);
545
- })
546
- .onTouchesUp(() => {
547
- scheduleOnRN(setIsPinching, false);
548
- })
549
- .onFinalize(() => {
550
- scheduleOnRN(setIsPinching, false);
551
- });
552
- }, [
553
- scale,
554
- enablePinchZoom,
555
- maxZoomScale,
556
- translateX,
557
- translateY,
558
- startScale,
559
- initialTranslateX,
560
- initialTranslateY,
561
- width,
562
- height,
563
- constrainTranslation,
564
- lastFocalX,
565
- lastFocalY,
566
- ]);
536
+ const zoomPanGesture = useMemo(
537
+ () =>
538
+ Gesture.Pan()
539
+ .enabled(enablePanWhenZoomed && isZoomed)
540
+ .activeCursor('grabbing')
541
+ .averageTouches(true)
542
+ .onBegin(() => {
543
+ initialTranslateX.value = translateX.value;
544
+ initialTranslateY.value = translateY.value;
545
+ })
546
+ .onUpdate((event) => {
547
+ if (scale.value > 1) {
548
+ const newTranslateX = initialTranslateX.value + event.translationX;
549
+ const newTranslateY = initialTranslateY.value + event.translationY;
550
+
551
+ const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } =
552
+ constrainTranslation({
553
+ scale: scale.value,
554
+ translateX: newTranslateX,
555
+ translateY: newTranslateY,
556
+ });
557
+
558
+ translateX.value = constrainedTranslateX;
559
+ translateY.value = constrainedTranslateY;
560
+ }
561
+ }),
562
+ [
563
+ translateX,
564
+ translateY,
565
+ enablePanWhenZoomed,
566
+ isZoomed,
567
+ scale,
568
+ initialTranslateX,
569
+ initialTranslateY,
570
+ constrainTranslation,
571
+ ],
572
+ );
567
573
 
568
- const zoomPanGesture = useMemo(() => {
569
- return Gesture.Pan()
570
- .enabled(enablePanWhenZoomed && isZoomed)
571
- .activeCursor('grabbing')
572
- .averageTouches(true)
573
- .onBegin(() => {
574
- initialTranslateX.value = translateX.value;
575
- initialTranslateY.value = translateY.value;
576
- })
577
- .onUpdate((event) => {
578
- if (scale.value > 1) {
579
- const newTranslateX = initialTranslateX.value + event.translationX;
580
- const newTranslateY = initialTranslateY.value + event.translationY;
581
-
582
- const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } = constrainTranslation({
583
- translateX: newTranslateX,
584
- translateY: newTranslateY,
585
- scale: scale.value,
574
+ const doubleTapGesture = useMemo(
575
+ () =>
576
+ Gesture.Tap()
577
+ .enabled(enableDoubleTapZoom && Platform.OS !== 'web')
578
+ .numberOfTaps(2)
579
+ .onEnd((event) => {
580
+ applyTapZoomAtPoint({
581
+ x: event.x,
582
+ y: event.y,
583
+ width,
584
+ height,
585
+ maxZoomScale,
586
+ scale,
587
+ translateX,
588
+ translateY,
586
589
  });
590
+ }),
591
+ [enableDoubleTapZoom, height, maxZoomScale, scale, translateX, translateY, width],
592
+ );
587
593
 
588
- translateX.value = constrainedTranslateX;
589
- translateY.value = constrainedTranslateY;
590
- }
591
- });
592
- }, [
593
- translateX,
594
- translateY,
595
- enablePanWhenZoomed,
596
- isZoomed,
597
- scale,
598
- initialTranslateX,
599
- initialTranslateY,
600
- constrainTranslation,
601
- ]);
602
-
603
- const doubleTapGesture = useMemo(() => {
604
- return Gesture.Tap()
605
- .enabled(enableDoubleTapZoom)
606
- .numberOfTaps(2)
607
- .onEnd((event) => {
608
- const nextScale = scale.value > 1 ? 1 : maxZoomScale;
609
-
610
- if (nextScale > 1) {
611
- const centerX = event.x - width / 2;
612
- const centerY = event.y - height / 2;
613
-
614
- // NOTE 확대로 밀려난 거리만큼 반대로 이동해서 탭 지점을 제자리에 유지
615
- translateX.value = withTiming(-centerX * (nextScale - 1), {
616
- duration: 300,
617
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
618
- });
619
- translateY.value = withTiming(-centerY * (nextScale - 1), {
620
- duration: 300,
621
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
622
- });
623
- } else {
624
- translateX.value = withTiming(0, {
625
- duration: 300,
626
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
627
- });
628
- translateY.value = withTiming(0, {
629
- duration: 300,
630
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
631
- });
632
- }
594
+ const zoomGesture = useMemo(
595
+ () => Gesture.Race(zoomPinchGesture, Gesture.Exclusive(zoomPanGesture, doubleTapGesture)),
596
+ [zoomPinchGesture, zoomPanGesture, doubleTapGesture],
597
+ );
633
598
 
634
- scale.value = withTiming(nextScale, {
635
- duration: 300,
636
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
637
- });
638
- });
639
- }, [scale, enableDoubleTapZoom, maxZoomScale, translateX, translateY, width, height]);
640
-
641
- const zoomGesture = useMemo(() => {
642
- return Gesture.Race(zoomPinchGesture, Gesture.Exclusive(zoomPanGesture, doubleTapGesture));
643
- }, [zoomPinchGesture, zoomPanGesture, doubleTapGesture]);
644
-
645
- const animatedStyle = useAnimatedStyle(() => {
646
- return {
647
- opacity: triggerOpacity.value,
648
- transform: [
649
- { translateX: triggerTranslateX.value },
650
- { translateY: triggerTranslateY.value },
651
- { scale: triggerScale.value },
652
-
653
- { translateY: translateY.value },
654
- { translateX: translateX.value },
655
- { scale: scale.value },
656
- { rotate: `${rotation.value}deg` },
657
- ],
658
- };
659
- });
599
+ const animatedStyle = useAnimatedStyle(() => ({
600
+ opacity: triggerOpacity.value,
601
+ transform: [
602
+ { translateX: triggerTranslateX.value },
603
+ { translateY: triggerTranslateY.value },
604
+ { scale: triggerScale.value },
605
+
606
+ { translateY: translateY.value },
607
+ { translateX: translateX.value },
608
+ { scale: scale.value },
609
+ { rotate: `${rotation.value}deg` },
610
+ ],
611
+ }));
660
612
 
661
613
  const backdropStyle = useAnimatedStyle(() => {
662
614
  const baseOpacity = triggerOpacity.value;
@@ -670,24 +622,51 @@ export const useGestureViewer = <ItemT, LC>({
670
622
  return { opacity: baseOpacity * dismissOpacity };
671
623
  }, [dismissOptions.fadeBackdrop]);
672
624
 
673
- const onScrollBeginDrag = useCallback(() => {
674
- manager?.handleScrollBeginDrag();
675
- }, [manager]);
625
+ const nativeScrollGesture = useMemo(() => {
626
+ return Gesture.Native().requireExternalGestureToFail(dismissGestureRef);
627
+ }, []);
628
+
629
+ const { onMomentumScrollEnd, onScroll, onScrollBeginDrag, onWebDoubleClick } =
630
+ useGestureViewerPaging({
631
+ adjustedInitialIndex,
632
+ autoPlay,
633
+ autoPlayInterval,
634
+ currentIndex,
635
+ dataLength,
636
+ enableDoubleTapZoom,
637
+ enableHorizontalSwipe,
638
+ enableLoop,
639
+ height,
640
+ isRotated,
641
+ isZoomed,
642
+ itemSpacing,
643
+ manager,
644
+ maxZoomScale,
645
+ scale,
646
+ scrollTo,
647
+ syncCurrentIndex,
648
+ translateX,
649
+ translateY,
650
+ width,
651
+ });
676
652
 
677
653
  return {
654
+ animatedStyle,
655
+ backdropStyle,
678
656
  dataLength,
679
- listRef,
680
- isZoomed,
681
- isRotated,
682
- isPinching,
683
657
  dismissGesture,
684
- zoomGesture,
658
+ handleDismiss,
659
+ isPinching,
660
+ isRotated,
685
661
 
662
+ isZoomed,
663
+ listRef,
664
+ nativeScrollGesture,
665
+ onWebDoubleClick,
686
666
  onMomentumScrollEnd,
687
- onScrollBeginDrag,
688
- handleDismiss,
667
+ onScroll,
689
668
 
690
- animatedStyle,
691
- backdropStyle,
669
+ onScrollBeginDrag,
670
+ zoomGesture,
692
671
  };
693
672
  };