react-native-gesture-image-viewer 2.2.0 → 2.3.1
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 +6 -1
- package/lib/module/GestureViewer.js +2 -2
- package/lib/module/GestureViewer.js.map +1 -1
- package/lib/module/GestureViewerManager.js +27 -23
- package/lib/module/GestureViewerManager.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/useGestureViewer.js +156 -106
- package/lib/module/useGestureViewer.js.map +1 -1
- package/lib/module/useGestureViewerEvent.js +15 -1
- package/lib/module/useGestureViewerEvent.js.map +1 -1
- package/lib/module/useGestureViewerPaging.js +1 -1
- package/lib/module/useGestureViewerPaging.js.map +1 -1
- package/lib/module/useGestureViewerPaging.web.js +47 -26
- package/lib/module/useGestureViewerPaging.web.js.map +1 -1
- package/lib/module/utils/dismiss.js +25 -0
- package/lib/module/utils/dismiss.js.map +1 -0
- package/lib/module/utils/tapZoom.js +6 -6
- package/lib/module/utils/tapZoom.js.map +1 -1
- package/lib/typescript/src/GestureViewer.d.ts.map +1 -1
- package/lib/typescript/src/GestureViewerManager.d.ts +2 -1
- package/lib/typescript/src/GestureViewerManager.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +33 -4
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/useGestureViewer.d.ts +2 -2
- package/lib/typescript/src/useGestureViewer.d.ts.map +1 -1
- package/lib/typescript/src/useGestureViewerEvent.d.ts +15 -1
- package/lib/typescript/src/useGestureViewerEvent.d.ts.map +1 -1
- package/lib/typescript/src/useGestureViewerPaging.types.d.ts +9 -1
- package/lib/typescript/src/useGestureViewerPaging.types.d.ts.map +1 -1
- package/lib/typescript/src/useGestureViewerPaging.web.d.ts +1 -1
- package/lib/typescript/src/useGestureViewerPaging.web.d.ts.map +1 -1
- package/lib/typescript/src/utils/dismiss.d.ts +4 -0
- package/lib/typescript/src/utils/dismiss.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/GestureViewer.tsx +4 -2
- package/src/GestureViewerManager.ts +31 -25
- package/src/index.tsx +1 -0
- package/src/types.ts +31 -4
- package/src/useGestureViewer.ts +239 -157
- package/src/useGestureViewerEvent.ts +15 -1
- package/src/useGestureViewerPaging.ts +1 -1
- package/src/useGestureViewerPaging.types.ts +7 -1
- package/src/useGestureViewerPaging.web.ts +56 -32
- package/src/utils/dismiss.ts +36 -0
- package/src/utils/tapZoom.ts +6 -6
package/src/useGestureViewer.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { registry } from './GestureViewerRegistry';
|
|
|
17
17
|
import type { GestureViewerProps, TriggerRect } from './types';
|
|
18
18
|
import { useGestureViewerPaging } from './useGestureViewerPaging';
|
|
19
19
|
import { createBoundsConstraint, createScrollAction } from './utils';
|
|
20
|
+
import { getDismissDistance, shouldDismissByDirection } from './utils/dismiss';
|
|
20
21
|
import { applyTapZoomAtPoint } from './utils/tapZoom';
|
|
21
22
|
|
|
22
23
|
type UseGestureViewerProps<ItemT, LC> = Omit<
|
|
@@ -34,6 +35,7 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
34
35
|
data,
|
|
35
36
|
initialIndex = 0,
|
|
36
37
|
onDismiss,
|
|
38
|
+
onSingleTap,
|
|
37
39
|
width: customWidth,
|
|
38
40
|
dismiss,
|
|
39
41
|
enableDoubleTapZoom = true,
|
|
@@ -68,6 +70,9 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
68
70
|
const triggerRectRef = useRef<TriggerRect | null>(null);
|
|
69
71
|
const pendingIndexRef = useRef(initialIndex);
|
|
70
72
|
const onAnimationCompleteRef = useRef(triggerAnimation?.onAnimationComplete);
|
|
73
|
+
const onSingleTapRef = useRef(onSingleTap);
|
|
74
|
+
const dataRef = useRef(data);
|
|
75
|
+
const managerRef = useRef(manager);
|
|
71
76
|
|
|
72
77
|
const isValidTriggerRect = useCallback((rect: TriggerRect | null): rect is TriggerRect => {
|
|
73
78
|
return !!rect && rect.width > 0 && rect.height > 0;
|
|
@@ -104,12 +109,19 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
104
109
|
|
|
105
110
|
const dismissOptions = useMemo(
|
|
106
111
|
() => ({
|
|
112
|
+
direction: dismiss?.direction ?? 'down',
|
|
107
113
|
enabled: dismiss?.enabled ?? true,
|
|
108
114
|
fadeBackdrop: dismiss?.fadeBackdrop ?? true,
|
|
109
115
|
resistance: dismiss?.resistance ?? 2,
|
|
110
116
|
threshold: dismiss?.threshold ?? 80,
|
|
111
117
|
}),
|
|
112
|
-
[
|
|
118
|
+
[
|
|
119
|
+
dismiss?.direction,
|
|
120
|
+
dismiss?.enabled,
|
|
121
|
+
dismiss?.threshold,
|
|
122
|
+
dismiss?.resistance,
|
|
123
|
+
dismiss?.fadeBackdrop,
|
|
124
|
+
],
|
|
113
125
|
);
|
|
114
126
|
|
|
115
127
|
const adjustedInitialIndex = useMemo(() => {
|
|
@@ -135,13 +147,13 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
135
147
|
);
|
|
136
148
|
|
|
137
149
|
const resetTransformState = useCallback(() => {
|
|
138
|
-
translateX.
|
|
139
|
-
translateY.
|
|
140
|
-
initialTranslateX.
|
|
141
|
-
initialTranslateY.
|
|
142
|
-
startScale.
|
|
143
|
-
scale.
|
|
144
|
-
rotation.
|
|
150
|
+
translateX.set(withTiming(0));
|
|
151
|
+
translateY.set(withTiming(0));
|
|
152
|
+
initialTranslateX.set(withTiming(0));
|
|
153
|
+
initialTranslateY.set(withTiming(0));
|
|
154
|
+
startScale.set(withTiming(1));
|
|
155
|
+
scale.set(withTiming(1));
|
|
156
|
+
rotation.set(0);
|
|
145
157
|
}, [initialTranslateX, initialTranslateY, rotation, scale, startScale, translateX, translateY]);
|
|
146
158
|
|
|
147
159
|
const syncPendingIndex = useCallback(
|
|
@@ -176,30 +188,20 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
176
188
|
[dataLength, manager, resetTransformState],
|
|
177
189
|
);
|
|
178
190
|
|
|
179
|
-
const emitZoomChange = useCallback(
|
|
180
|
-
(currentScale
|
|
181
|
-
|
|
182
|
-
manager.emitZoomChange(currentScale, prevScale);
|
|
183
|
-
}
|
|
184
|
-
},
|
|
185
|
-
[manager],
|
|
186
|
-
);
|
|
191
|
+
const emitZoomChange = useCallback((currentScale: number, prevScale: number | null) => {
|
|
192
|
+
managerRef.current?.emitZoomChange(currentScale, prevScale);
|
|
193
|
+
}, []);
|
|
187
194
|
|
|
188
|
-
const emitRotationChange = useCallback(
|
|
189
|
-
(currentRotation
|
|
190
|
-
|
|
191
|
-
manager.emitRotationChange(currentRotation, prevRotation);
|
|
192
|
-
}
|
|
193
|
-
},
|
|
194
|
-
[manager],
|
|
195
|
-
);
|
|
195
|
+
const emitRotationChange = useCallback((currentRotation: number, prevRotation: number | null) => {
|
|
196
|
+
managerRef.current?.emitRotationChange(currentRotation, prevRotation);
|
|
197
|
+
}, []);
|
|
196
198
|
|
|
197
199
|
const onAnimationComplete = useCallback(() => {
|
|
198
200
|
onAnimationCompleteRef.current?.();
|
|
199
201
|
}, []);
|
|
200
202
|
|
|
201
203
|
useAnimatedReaction(
|
|
202
|
-
() => scale.
|
|
204
|
+
() => scale.get(),
|
|
203
205
|
(currentScale, previousScale) => {
|
|
204
206
|
if (currentScale !== previousScale) {
|
|
205
207
|
scheduleOnRN(emitZoomChange, currentScale, previousScale);
|
|
@@ -210,7 +212,7 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
210
212
|
);
|
|
211
213
|
|
|
212
214
|
useAnimatedReaction(
|
|
213
|
-
() => rotation.
|
|
215
|
+
() => rotation.get(),
|
|
214
216
|
(currentRotation, previousRotation) => {
|
|
215
217
|
if (currentRotation !== previousRotation) {
|
|
216
218
|
scheduleOnRN(emitRotationChange, currentRotation, previousRotation);
|
|
@@ -284,12 +286,12 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
284
286
|
}, [manager]);
|
|
285
287
|
|
|
286
288
|
useEffect(() => {
|
|
287
|
-
translateY.
|
|
288
|
-
translateX.
|
|
289
|
-
scale.
|
|
290
|
-
backdropOpacity.
|
|
291
|
-
startScale.
|
|
292
|
-
rotation.
|
|
289
|
+
translateY.set(0);
|
|
290
|
+
translateX.set(0);
|
|
291
|
+
scale.set(1);
|
|
292
|
+
backdropOpacity.set(1);
|
|
293
|
+
startScale.set(1);
|
|
294
|
+
rotation.set(0);
|
|
293
295
|
|
|
294
296
|
if (adjustedInitialIndex <= 0 || !listRef.current) {
|
|
295
297
|
return;
|
|
@@ -317,6 +319,18 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
317
319
|
onAnimationCompleteRef.current = triggerAnimation?.onAnimationComplete;
|
|
318
320
|
}, [triggerAnimation?.onAnimationComplete]);
|
|
319
321
|
|
|
322
|
+
useEffect(() => {
|
|
323
|
+
dataRef.current = data;
|
|
324
|
+
}, [data]);
|
|
325
|
+
|
|
326
|
+
useEffect(() => {
|
|
327
|
+
managerRef.current = manager;
|
|
328
|
+
}, [manager]);
|
|
329
|
+
|
|
330
|
+
useEffect(() => {
|
|
331
|
+
onSingleTapRef.current = onSingleTap;
|
|
332
|
+
}, [onSingleTap]);
|
|
333
|
+
|
|
320
334
|
useEffect(() => {
|
|
321
335
|
if (shouldStartTriggerAnimation && triggerRectRef.current) {
|
|
322
336
|
const startX = triggerRectRef.current.x + triggerRectRef.current.width / 2 - width / 2;
|
|
@@ -326,23 +340,27 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
326
340
|
triggerRectRef.current.height / height,
|
|
327
341
|
);
|
|
328
342
|
|
|
329
|
-
triggerScale.
|
|
330
|
-
triggerTranslateX.
|
|
331
|
-
triggerTranslateY.
|
|
332
|
-
triggerOpacity.
|
|
343
|
+
triggerScale.set(initialScaleFromTrigger);
|
|
344
|
+
triggerTranslateX.set(startX);
|
|
345
|
+
triggerTranslateY.set(startY);
|
|
346
|
+
triggerOpacity.set(0);
|
|
333
347
|
|
|
334
|
-
triggerScale.
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
348
|
+
triggerScale.set(
|
|
349
|
+
withTiming(1, animationConfig, (finished) => {
|
|
350
|
+
if (finished) {
|
|
351
|
+
scheduleOnRN(onAnimationComplete);
|
|
352
|
+
}
|
|
353
|
+
}),
|
|
354
|
+
);
|
|
355
|
+
triggerTranslateX.set(withTiming(0, animationConfig));
|
|
356
|
+
triggerTranslateY.set(withTiming(0, animationConfig));
|
|
357
|
+
triggerOpacity.set(
|
|
358
|
+
withTiming(1, {
|
|
359
|
+
duration: animationConfig.duration / 2,
|
|
360
|
+
easing: animationConfig.easing,
|
|
361
|
+
reduceMotion: animationConfig.reduceMotion,
|
|
362
|
+
}),
|
|
363
|
+
);
|
|
346
364
|
|
|
347
365
|
setShouldStartTriggerAnimation(false);
|
|
348
366
|
}
|
|
@@ -377,7 +395,7 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
377
395
|
}
|
|
378
396
|
|
|
379
397
|
triggerRectRef.current = nextTriggerRect;
|
|
380
|
-
triggerOpacity.
|
|
398
|
+
triggerOpacity.set(0);
|
|
381
399
|
setShouldStartTriggerAnimation(true);
|
|
382
400
|
registry.clearActiveTriggerNode(id);
|
|
383
401
|
});
|
|
@@ -395,14 +413,16 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
395
413
|
const endY = rect.y + rect.height / 2 - height / 2;
|
|
396
414
|
const endScale = Math.min(rect.width / width, rect.height / height);
|
|
397
415
|
|
|
398
|
-
triggerScale.
|
|
399
|
-
triggerTranslateX.
|
|
400
|
-
triggerTranslateY.
|
|
401
|
-
triggerOpacity.
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
416
|
+
triggerScale.set(withTiming(endScale, animationConfig));
|
|
417
|
+
triggerTranslateX.set(withTiming(endX, animationConfig));
|
|
418
|
+
triggerTranslateY.set(withTiming(endY, animationConfig));
|
|
419
|
+
triggerOpacity.set(
|
|
420
|
+
withTiming(0, animationConfig, (finished) => {
|
|
421
|
+
if (finished && onDismiss) {
|
|
422
|
+
scheduleOnRN(onDismiss);
|
|
423
|
+
}
|
|
424
|
+
}),
|
|
425
|
+
);
|
|
406
426
|
},
|
|
407
427
|
[
|
|
408
428
|
animationConfig,
|
|
@@ -472,21 +492,30 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
472
492
|
.withRef(dismissGestureRef)
|
|
473
493
|
.enabled(canDismiss)
|
|
474
494
|
.onUpdate((event) => {
|
|
475
|
-
translateY.
|
|
495
|
+
translateY.set(event.translationY / dismissOptions.resistance);
|
|
476
496
|
})
|
|
477
497
|
.onEnd((event) => {
|
|
478
|
-
if (
|
|
498
|
+
if (
|
|
499
|
+
canDismiss &&
|
|
500
|
+
shouldDismissByDirection(
|
|
501
|
+
event.translationY,
|
|
502
|
+
dismissOptions.threshold,
|
|
503
|
+
dismissOptions.direction,
|
|
504
|
+
)
|
|
505
|
+
) {
|
|
479
506
|
scheduleOnRN(handleDismiss);
|
|
480
507
|
return;
|
|
481
508
|
}
|
|
482
509
|
|
|
483
|
-
translateY.
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
510
|
+
translateY.set(
|
|
511
|
+
withSpring(0, {
|
|
512
|
+
damping: 50,
|
|
513
|
+
energyThreshold: 6e-9,
|
|
514
|
+
mass: 4,
|
|
515
|
+
overshootClamping: false,
|
|
516
|
+
stiffness: 600,
|
|
517
|
+
}),
|
|
518
|
+
);
|
|
490
519
|
});
|
|
491
520
|
}, [translateY, dismissOptions, handleDismiss, isZoomed]);
|
|
492
521
|
|
|
@@ -500,39 +529,44 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
500
529
|
}
|
|
501
530
|
})
|
|
502
531
|
.onBegin((event) => {
|
|
503
|
-
startScale.
|
|
504
|
-
initialTranslateX.
|
|
505
|
-
initialTranslateY.
|
|
506
|
-
lastFocalX.
|
|
507
|
-
lastFocalY.
|
|
532
|
+
startScale.set(scale.get());
|
|
533
|
+
initialTranslateX.set(translateX.get());
|
|
534
|
+
initialTranslateY.set(translateY.get());
|
|
535
|
+
lastFocalX.set(event.focalX);
|
|
536
|
+
lastFocalY.set(event.focalY);
|
|
508
537
|
})
|
|
509
538
|
.onUpdate((event) => {
|
|
510
|
-
const
|
|
539
|
+
const initialScale = startScale.get();
|
|
540
|
+
const newScale = initialScale * event.scale;
|
|
511
541
|
|
|
512
|
-
scale.
|
|
542
|
+
scale.set(newScale);
|
|
513
543
|
|
|
514
544
|
if (newScale <= 1) {
|
|
515
|
-
translateX.
|
|
516
|
-
translateY.
|
|
545
|
+
translateX.set(withTiming(0));
|
|
546
|
+
translateY.set(withTiming(0));
|
|
517
547
|
return;
|
|
518
548
|
}
|
|
519
549
|
|
|
520
|
-
const
|
|
521
|
-
const
|
|
550
|
+
const currentLastFocalX = lastFocalX.get();
|
|
551
|
+
const currentLastFocalY = lastFocalY.get();
|
|
552
|
+
const focalDeltaX = Math.abs(event.focalX - currentLastFocalX);
|
|
553
|
+
const focalDeltaY = Math.abs(event.focalY - currentLastFocalY);
|
|
522
554
|
const threshold = 50;
|
|
523
555
|
|
|
524
556
|
if (focalDeltaX < threshold && focalDeltaY < threshold) {
|
|
525
|
-
lastFocalX.
|
|
526
|
-
lastFocalY.
|
|
557
|
+
lastFocalX.set(event.focalX);
|
|
558
|
+
lastFocalY.set(event.focalY);
|
|
527
559
|
}
|
|
528
560
|
|
|
529
|
-
const
|
|
530
|
-
const
|
|
531
|
-
const
|
|
561
|
+
const nextLastFocalX = lastFocalX.get();
|
|
562
|
+
const nextLastFocalY = lastFocalY.get();
|
|
563
|
+
const deltaScale = newScale - initialScale;
|
|
564
|
+
const centerX = nextLastFocalX - width / 2;
|
|
565
|
+
const centerY = nextLastFocalY - height / 2;
|
|
532
566
|
|
|
533
567
|
// NOTE 새로운 이동값 = 기존 이동값 - (중심점 거리 × 스케일 변화량) / 원래 스케일 (중심점이 화면 중심에서 멀수록, 확대 배율이 클수록 더 많이 이동)
|
|
534
|
-
const newTranslateX = initialTranslateX.
|
|
535
|
-
const newTranslateY = initialTranslateY.
|
|
568
|
+
const newTranslateX = initialTranslateX.get() - (centerX * deltaScale) / initialScale;
|
|
569
|
+
const newTranslateY = initialTranslateY.get() - (centerY * deltaScale) / initialScale;
|
|
536
570
|
|
|
537
571
|
const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } =
|
|
538
572
|
constrainTranslation({
|
|
@@ -541,50 +575,56 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
541
575
|
translateY: newTranslateY,
|
|
542
576
|
});
|
|
543
577
|
|
|
544
|
-
translateX.
|
|
545
|
-
translateY.
|
|
578
|
+
translateX.set(constrainedTranslateX);
|
|
579
|
+
translateY.set(constrainedTranslateY);
|
|
546
580
|
})
|
|
547
581
|
.onEnd(() => {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
582
|
+
const currentScale = scale.get();
|
|
583
|
+
|
|
584
|
+
if (currentScale > maxZoomScale) {
|
|
585
|
+
scale.set(
|
|
586
|
+
withTiming(maxZoomScale, {
|
|
587
|
+
duration: 300,
|
|
588
|
+
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
|
|
589
|
+
}),
|
|
590
|
+
);
|
|
553
591
|
|
|
554
592
|
const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } =
|
|
555
593
|
constrainTranslation({
|
|
556
594
|
scale: maxZoomScale,
|
|
557
|
-
translateX: translateX.
|
|
558
|
-
translateY: translateY.
|
|
595
|
+
translateX: translateX.get(),
|
|
596
|
+
translateY: translateY.get(),
|
|
559
597
|
});
|
|
560
598
|
|
|
561
|
-
translateX.
|
|
562
|
-
translateY.
|
|
599
|
+
translateX.set(withTiming(constrainedTranslateX));
|
|
600
|
+
translateY.set(withTiming(constrainedTranslateY));
|
|
563
601
|
|
|
564
602
|
return;
|
|
565
603
|
}
|
|
566
604
|
|
|
567
|
-
if (
|
|
568
|
-
scale.
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
605
|
+
if (currentScale < 1) {
|
|
606
|
+
scale.set(
|
|
607
|
+
withTiming(1, {
|
|
608
|
+
duration: 300,
|
|
609
|
+
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
|
|
610
|
+
}),
|
|
611
|
+
);
|
|
612
|
+
translateX.set(withTiming(0));
|
|
613
|
+
translateY.set(withTiming(0));
|
|
614
|
+
initialTranslateX.set(withTiming(0));
|
|
615
|
+
initialTranslateY.set(withTiming(0));
|
|
576
616
|
return;
|
|
577
617
|
}
|
|
578
618
|
|
|
579
619
|
const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } =
|
|
580
620
|
constrainTranslation({
|
|
581
|
-
scale:
|
|
582
|
-
translateX: translateX.
|
|
583
|
-
translateY: translateY.
|
|
621
|
+
scale: currentScale,
|
|
622
|
+
translateX: translateX.get(),
|
|
623
|
+
translateY: translateY.get(),
|
|
584
624
|
});
|
|
585
625
|
|
|
586
|
-
translateX.
|
|
587
|
-
translateY.
|
|
626
|
+
translateX.set(withTiming(constrainedTranslateX));
|
|
627
|
+
translateY.set(withTiming(constrainedTranslateY));
|
|
588
628
|
})
|
|
589
629
|
.onTouchesUp(() => {
|
|
590
630
|
scheduleOnRN(setIsPinching, false);
|
|
@@ -616,23 +656,25 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
616
656
|
.activeCursor('grabbing')
|
|
617
657
|
.averageTouches(true)
|
|
618
658
|
.onBegin(() => {
|
|
619
|
-
initialTranslateX.
|
|
620
|
-
initialTranslateY.
|
|
659
|
+
initialTranslateX.set(translateX.get());
|
|
660
|
+
initialTranslateY.set(translateY.get());
|
|
621
661
|
})
|
|
622
662
|
.onUpdate((event) => {
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
663
|
+
const currentScale = scale.get();
|
|
664
|
+
|
|
665
|
+
if (currentScale > 1) {
|
|
666
|
+
const newTranslateX = initialTranslateX.get() + event.translationX;
|
|
667
|
+
const newTranslateY = initialTranslateY.get() + event.translationY;
|
|
626
668
|
|
|
627
669
|
const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } =
|
|
628
670
|
constrainTranslation({
|
|
629
|
-
scale:
|
|
671
|
+
scale: currentScale,
|
|
630
672
|
translateX: newTranslateX,
|
|
631
673
|
translateY: newTranslateY,
|
|
632
674
|
});
|
|
633
675
|
|
|
634
|
-
translateX.
|
|
635
|
-
translateY.
|
|
676
|
+
translateX.set(constrainedTranslateX);
|
|
677
|
+
translateY.set(constrainedTranslateY);
|
|
636
678
|
}
|
|
637
679
|
}),
|
|
638
680
|
[
|
|
@@ -647,6 +689,40 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
647
689
|
],
|
|
648
690
|
);
|
|
649
691
|
|
|
692
|
+
const emitSingleTap = useCallback((x: number, y: number) => {
|
|
693
|
+
const index = pendingIndexRef.current;
|
|
694
|
+
const currentData = dataRef.current;
|
|
695
|
+
|
|
696
|
+
if (index < 0 || index >= currentData.length) {
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
managerRef.current?.emitTap({ kind: 'single', x, y, index });
|
|
701
|
+
|
|
702
|
+
const item = currentData[index];
|
|
703
|
+
|
|
704
|
+
if (item === undefined) {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
onSingleTapRef.current?.({ x, y, index, item });
|
|
709
|
+
}, []);
|
|
710
|
+
|
|
711
|
+
const singleTapGesture = useMemo(
|
|
712
|
+
() =>
|
|
713
|
+
Gesture.Tap()
|
|
714
|
+
.enabled(Platform.OS !== 'web')
|
|
715
|
+
.numberOfTaps(1)
|
|
716
|
+
.onEnd((event, success) => {
|
|
717
|
+
if (!success) {
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
scheduleOnRN(emitSingleTap, event.x, event.y);
|
|
722
|
+
}),
|
|
723
|
+
[emitSingleTap],
|
|
724
|
+
);
|
|
725
|
+
|
|
650
726
|
const doubleTapGesture = useMemo(
|
|
651
727
|
() =>
|
|
652
728
|
Gesture.Tap()
|
|
@@ -667,65 +743,71 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
667
743
|
[enableDoubleTapZoom, height, maxZoomScale, scale, translateX, translateY, width],
|
|
668
744
|
);
|
|
669
745
|
|
|
746
|
+
const tapGesture = useMemo(
|
|
747
|
+
() => Gesture.Exclusive(doubleTapGesture, singleTapGesture),
|
|
748
|
+
[doubleTapGesture, singleTapGesture],
|
|
749
|
+
);
|
|
750
|
+
|
|
670
751
|
const zoomGesture = useMemo(
|
|
671
|
-
() => Gesture.Race(zoomPinchGesture, Gesture.Exclusive(zoomPanGesture,
|
|
672
|
-
[zoomPinchGesture, zoomPanGesture,
|
|
752
|
+
() => Gesture.Race(zoomPinchGesture, Gesture.Exclusive(zoomPanGesture, tapGesture)),
|
|
753
|
+
[zoomPinchGesture, zoomPanGesture, tapGesture],
|
|
673
754
|
);
|
|
674
755
|
|
|
675
756
|
const animatedStyle = useAnimatedStyle(() => ({
|
|
676
|
-
opacity: triggerOpacity.
|
|
757
|
+
opacity: triggerOpacity.get(),
|
|
677
758
|
transform: [
|
|
678
|
-
{ translateX: triggerTranslateX.
|
|
679
|
-
{ translateY: triggerTranslateY.
|
|
680
|
-
{ scale: triggerScale.
|
|
681
|
-
|
|
682
|
-
{ translateY: translateY.
|
|
683
|
-
{ translateX: translateX.
|
|
684
|
-
{ scale: scale.
|
|
685
|
-
{ rotate: `${rotation.
|
|
759
|
+
{ translateX: triggerTranslateX.get() },
|
|
760
|
+
{ translateY: triggerTranslateY.get() },
|
|
761
|
+
{ scale: triggerScale.get() },
|
|
762
|
+
|
|
763
|
+
{ translateY: translateY.get() },
|
|
764
|
+
{ translateX: translateX.get() },
|
|
765
|
+
{ scale: scale.get() },
|
|
766
|
+
{ rotate: `${rotation.get()}deg` },
|
|
686
767
|
],
|
|
687
768
|
}));
|
|
688
769
|
|
|
689
770
|
const backdropStyle = useAnimatedStyle(() => {
|
|
690
|
-
const baseOpacity = triggerOpacity.
|
|
771
|
+
const baseOpacity = triggerOpacity.get();
|
|
691
772
|
|
|
692
|
-
if (!dismissOptions.fadeBackdrop || scale.
|
|
773
|
+
if (!dismissOptions.fadeBackdrop || scale.get() !== 1) {
|
|
693
774
|
return { opacity: baseOpacity };
|
|
694
775
|
}
|
|
695
776
|
|
|
696
|
-
const
|
|
777
|
+
const dismissDistance = getDismissDistance(translateY.get(), dismissOptions.direction);
|
|
778
|
+
const dismissOpacity = interpolate(dismissDistance, [0, 200], [1, 0], 'clamp');
|
|
697
779
|
|
|
698
780
|
return { opacity: baseOpacity * dismissOpacity };
|
|
699
|
-
}, [dismissOptions.fadeBackdrop]);
|
|
781
|
+
}, [dismissOptions.direction, dismissOptions.fadeBackdrop]);
|
|
700
782
|
|
|
701
783
|
const nativeScrollGesture = useMemo(() => {
|
|
702
784
|
return Gesture.Native().requireExternalGestureToFail(dismissGestureRef);
|
|
703
785
|
}, []);
|
|
704
786
|
|
|
705
|
-
const { onMomentumScrollEnd, onScroll, onScrollBeginDrag,
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
787
|
+
const { onMomentumScrollEnd, onScroll, onScrollBeginDrag, onWebClick } = useGestureViewerPaging({
|
|
788
|
+
adjustedInitialIndex,
|
|
789
|
+
autoPlay,
|
|
790
|
+
autoPlayInterval,
|
|
791
|
+
currentIndex,
|
|
792
|
+
dataLength,
|
|
793
|
+
enableDoubleTapZoom,
|
|
794
|
+
enableHorizontalSwipe,
|
|
795
|
+
enableLoop,
|
|
796
|
+
height,
|
|
797
|
+
isRotated,
|
|
798
|
+
isZoomed,
|
|
799
|
+
itemSpacing,
|
|
800
|
+
manager,
|
|
801
|
+
maxZoomScale,
|
|
802
|
+
onSingleTap: emitSingleTap,
|
|
803
|
+
scale,
|
|
804
|
+
scrollTo,
|
|
805
|
+
syncCurrentIndex,
|
|
806
|
+
syncPendingIndex,
|
|
807
|
+
translateX,
|
|
808
|
+
translateY,
|
|
809
|
+
width,
|
|
810
|
+
});
|
|
729
811
|
|
|
730
812
|
return {
|
|
731
813
|
animatedStyle,
|
|
@@ -739,7 +821,7 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
739
821
|
isZoomed,
|
|
740
822
|
listRef,
|
|
741
823
|
nativeScrollGesture,
|
|
742
|
-
|
|
824
|
+
onWebClick,
|
|
743
825
|
onMomentumScrollEnd,
|
|
744
826
|
onScroll,
|
|
745
827
|
|
|
@@ -8,7 +8,7 @@ import type { GestureViewerEventCallback, GestureViewerEventType } from './types
|
|
|
8
8
|
* Hook for subscribing to GestureViewer events on the default instance.
|
|
9
9
|
*
|
|
10
10
|
* This hook allows you to listen to specific events from the default GestureViewer instance
|
|
11
|
-
* (with ID 'default'), such as zoom changes or
|
|
11
|
+
* (with ID 'default'), such as zoom changes, rotation changes, or confirmed tap events. Events are automatically
|
|
12
12
|
* throttled to prevent excessive callback invocations during gestures.
|
|
13
13
|
*
|
|
14
14
|
* @param eventType - The type of event to listen for
|
|
@@ -25,6 +25,13 @@ import type { GestureViewerEventCallback, GestureViewerEventType } from './types
|
|
|
25
25
|
* useGestureViewerEvent('rotationChange', (data) => {
|
|
26
26
|
* console.log(`Rotation changed from ${data.previousRotation}° to ${data.rotation}°`);
|
|
27
27
|
* });
|
|
28
|
+
*
|
|
29
|
+
* // Listen to confirmed single taps on the default instance (ID: 'default')
|
|
30
|
+
* useGestureViewerEvent('tap', (data) => {
|
|
31
|
+
* if (data.kind === 'single') {
|
|
32
|
+
* console.log(`Tapped item ${data.index} at (${data.x}, ${data.y})`);
|
|
33
|
+
* }
|
|
34
|
+
* });
|
|
28
35
|
* ```
|
|
29
36
|
*/
|
|
30
37
|
export function useGestureViewerEvent<T extends GestureViewerEventType>(
|
|
@@ -53,6 +60,13 @@ export function useGestureViewerEvent<T extends GestureViewerEventType>(
|
|
|
53
60
|
* useGestureViewerEvent('modal-viewer', 'rotationChange', (data) => {
|
|
54
61
|
* updateRotationIndicator(data.rotation);
|
|
55
62
|
* });
|
|
63
|
+
*
|
|
64
|
+
* // Listen to confirmed single taps on a specific instance
|
|
65
|
+
* useGestureViewerEvent('gallery', 'tap', (data) => {
|
|
66
|
+
* if (data.kind === 'single') {
|
|
67
|
+
* console.log(`Gallery tap on item ${data.index}`);
|
|
68
|
+
* }
|
|
69
|
+
* });
|
|
56
70
|
* ```
|
|
57
71
|
*/
|
|
58
72
|
export function useGestureViewerEvent<T extends GestureViewerEventType>(
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { MouseEvent } from 'react';
|
|
1
2
|
import type { ScrollViewProps } from 'react-native';
|
|
2
3
|
import type { SharedValue } from 'react-native-reanimated';
|
|
3
4
|
|
|
@@ -18,6 +19,7 @@ export type UseGestureViewerPagingArgs = {
|
|
|
18
19
|
itemSpacing: number;
|
|
19
20
|
manager: GestureViewerManager | null;
|
|
20
21
|
maxZoomScale: number;
|
|
22
|
+
onSingleTap?: (x: number, y: number) => void;
|
|
21
23
|
scale: SharedValue<number>;
|
|
22
24
|
scrollTo: (index: number, animated: boolean) => void;
|
|
23
25
|
syncCurrentIndex: (nextIndex: number) => void;
|
|
@@ -27,9 +29,13 @@ export type UseGestureViewerPagingArgs = {
|
|
|
27
29
|
width: number;
|
|
28
30
|
};
|
|
29
31
|
|
|
32
|
+
export type WebClickTarget = {
|
|
33
|
+
getBoundingClientRect: () => { left: number; top: number };
|
|
34
|
+
};
|
|
35
|
+
|
|
30
36
|
export type UseGestureViewerPagingResult = {
|
|
31
37
|
onMomentumScrollEnd?: ScrollViewProps['onMomentumScrollEnd'];
|
|
32
38
|
onScroll?: ScrollViewProps['onScroll'];
|
|
33
39
|
onScrollBeginDrag?: ScrollViewProps['onScrollBeginDrag'];
|
|
34
|
-
|
|
40
|
+
onWebClick?: (event: MouseEvent<WebClickTarget>) => void;
|
|
35
41
|
};
|