react-native-gesture-image-viewer 2.1.4 → 2.3.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/README.md +6 -1
- package/lib/module/GestureTrigger.js +22 -5
- package/lib/module/GestureTrigger.js.map +1 -1
- package/lib/module/GestureViewer.js +2 -2
- package/lib/module/GestureViewer.js.map +1 -1
- package/lib/module/GestureViewerManager.js +3 -0
- 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/index.js.map +1 -1
- package/lib/module/useGestureViewer.js +141 -43
- 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 +16 -2
- package/lib/module/useGestureViewerPaging.js.map +1 -1
- package/lib/module/useGestureViewerPaging.web.js +62 -28
- 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/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 +2 -1
- 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/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.d.ts +1 -1
- package/lib/typescript/src/useGestureViewerPaging.d.ts.map +1 -1
- package/lib/typescript/src/useGestureViewerPaging.types.d.ts +10 -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/GestureTrigger.tsx +31 -6
- package/src/GestureViewer.tsx +4 -2
- package/src/GestureViewerManager.ts +4 -0
- package/src/GestureViewerRegistry.ts +67 -8
- package/src/index.tsx +1 -0
- package/src/types.ts +31 -4
- package/src/useGestureViewer.ts +219 -81
- package/src/useGestureViewerEvent.ts +15 -1
- package/src/useGestureViewerPaging.ts +18 -2
- package/src/useGestureViewerPaging.types.ts +8 -1
- package/src/useGestureViewerPaging.web.ts +77 -33
- package/src/utils/dismiss.ts +36 -0
package/src/useGestureViewer.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
-
import { InteractionManager, Platform, useWindowDimensions } from 'react-native';
|
|
2
|
+
import { InteractionManager, Platform, type View, useWindowDimensions } from 'react-native';
|
|
3
3
|
import { Gesture, type GestureType } from 'react-native-gesture-handler';
|
|
4
4
|
import {
|
|
5
5
|
Easing,
|
|
@@ -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,
|
|
@@ -62,10 +64,19 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
62
64
|
const [shouldStartTriggerAnimation, setShouldStartTriggerAnimation] = useState(false);
|
|
63
65
|
const [manager, setManager] = useState<GestureViewerManager | null>(null);
|
|
64
66
|
const [currentIndex, setCurrentIndex] = useState(initialIndex);
|
|
67
|
+
const [activeTriggerNode, setActiveTriggerNode] = useState<View | null>(null);
|
|
65
68
|
|
|
66
69
|
const listRef = useRef<any>(null);
|
|
67
70
|
const triggerRectRef = useRef<TriggerRect | null>(null);
|
|
71
|
+
const pendingIndexRef = useRef(initialIndex);
|
|
68
72
|
const onAnimationCompleteRef = useRef(triggerAnimation?.onAnimationComplete);
|
|
73
|
+
const onSingleTapRef = useRef(onSingleTap);
|
|
74
|
+
const dataRef = useRef(data);
|
|
75
|
+
const managerRef = useRef(manager);
|
|
76
|
+
|
|
77
|
+
const isValidTriggerRect = useCallback((rect: TriggerRect | null): rect is TriggerRect => {
|
|
78
|
+
return !!rect && rect.width > 0 && rect.height > 0;
|
|
79
|
+
}, []);
|
|
69
80
|
|
|
70
81
|
const initialTranslateY = useSharedValue(0);
|
|
71
82
|
const initialTranslateX = useSharedValue(0);
|
|
@@ -98,12 +109,19 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
98
109
|
|
|
99
110
|
const dismissOptions = useMemo(
|
|
100
111
|
() => ({
|
|
112
|
+
direction: dismiss?.direction ?? 'down',
|
|
101
113
|
enabled: dismiss?.enabled ?? true,
|
|
102
114
|
fadeBackdrop: dismiss?.fadeBackdrop ?? true,
|
|
103
115
|
resistance: dismiss?.resistance ?? 2,
|
|
104
116
|
threshold: dismiss?.threshold ?? 80,
|
|
105
117
|
}),
|
|
106
|
-
[
|
|
118
|
+
[
|
|
119
|
+
dismiss?.direction,
|
|
120
|
+
dismiss?.enabled,
|
|
121
|
+
dismiss?.threshold,
|
|
122
|
+
dismiss?.resistance,
|
|
123
|
+
dismiss?.fadeBackdrop,
|
|
124
|
+
],
|
|
107
125
|
);
|
|
108
126
|
|
|
109
127
|
const adjustedInitialIndex = useMemo(() => {
|
|
@@ -138,12 +156,25 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
138
156
|
rotation.value = 0;
|
|
139
157
|
}, [initialTranslateX, initialTranslateY, rotation, scale, startScale, translateX, translateY]);
|
|
140
158
|
|
|
159
|
+
const syncPendingIndex = useCallback(
|
|
160
|
+
(nextIndex: number) => {
|
|
161
|
+
if (nextIndex < 0 || nextIndex >= dataLength) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
pendingIndexRef.current = nextIndex;
|
|
166
|
+
},
|
|
167
|
+
[dataLength],
|
|
168
|
+
);
|
|
169
|
+
|
|
141
170
|
const syncCurrentIndex = useCallback(
|
|
142
171
|
(nextIndex: number) => {
|
|
143
172
|
if (!manager || nextIndex < 0 || nextIndex >= dataLength) {
|
|
144
173
|
return;
|
|
145
174
|
}
|
|
146
175
|
|
|
176
|
+
pendingIndexRef.current = nextIndex;
|
|
177
|
+
|
|
147
178
|
const managerCurrentIndex = manager.getState().currentIndex;
|
|
148
179
|
|
|
149
180
|
if (nextIndex === managerCurrentIndex) {
|
|
@@ -157,23 +188,13 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
157
188
|
[dataLength, manager, resetTransformState],
|
|
158
189
|
);
|
|
159
190
|
|
|
160
|
-
const emitZoomChange = useCallback(
|
|
161
|
-
(currentScale
|
|
162
|
-
|
|
163
|
-
manager.emitZoomChange(currentScale, prevScale);
|
|
164
|
-
}
|
|
165
|
-
},
|
|
166
|
-
[manager],
|
|
167
|
-
);
|
|
191
|
+
const emitZoomChange = useCallback((currentScale: number, prevScale: number | null) => {
|
|
192
|
+
managerRef.current?.emitZoomChange(currentScale, prevScale);
|
|
193
|
+
}, []);
|
|
168
194
|
|
|
169
|
-
const emitRotationChange = useCallback(
|
|
170
|
-
(currentRotation
|
|
171
|
-
|
|
172
|
-
manager.emitRotationChange(currentRotation, prevRotation);
|
|
173
|
-
}
|
|
174
|
-
},
|
|
175
|
-
[manager],
|
|
176
|
-
);
|
|
195
|
+
const emitRotationChange = useCallback((currentRotation: number, prevRotation: number | null) => {
|
|
196
|
+
managerRef.current?.emitRotationChange(currentRotation, prevRotation);
|
|
197
|
+
}, []);
|
|
177
198
|
|
|
178
199
|
const onAnimationComplete = useCallback(() => {
|
|
179
200
|
onAnimationCompleteRef.current?.();
|
|
@@ -206,7 +227,10 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
206
227
|
|
|
207
228
|
const unsubscribeFromRegistry = registry.subscribeToManager(id, (managerInstance) => {
|
|
208
229
|
setManager(managerInstance);
|
|
209
|
-
unsubscribe = managerInstance?.subscribe((state) =>
|
|
230
|
+
unsubscribe = managerInstance?.subscribe((state) => {
|
|
231
|
+
pendingIndexRef.current = state.currentIndex;
|
|
232
|
+
setCurrentIndex(state.currentIndex);
|
|
233
|
+
});
|
|
210
234
|
});
|
|
211
235
|
|
|
212
236
|
return () => {
|
|
@@ -216,6 +240,12 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
216
240
|
}, [id]);
|
|
217
241
|
|
|
218
242
|
useEffect(() => {
|
|
243
|
+
return registry.subscribeToActiveTrigger(id, setActiveTriggerNode);
|
|
244
|
+
}, [id]);
|
|
245
|
+
|
|
246
|
+
useEffect(() => {
|
|
247
|
+
pendingIndexRef.current = initialIndex;
|
|
248
|
+
|
|
219
249
|
if (!manager) {
|
|
220
250
|
return;
|
|
221
251
|
}
|
|
@@ -289,6 +319,18 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
289
319
|
onAnimationCompleteRef.current = triggerAnimation?.onAnimationComplete;
|
|
290
320
|
}, [triggerAnimation?.onAnimationComplete]);
|
|
291
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
|
+
|
|
292
334
|
useEffect(() => {
|
|
293
335
|
if (shouldStartTriggerAnimation && triggerRectRef.current) {
|
|
294
336
|
const startX = triggerRectRef.current.x + triggerRectRef.current.width / 2 - width / 2;
|
|
@@ -331,32 +373,41 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
331
373
|
]);
|
|
332
374
|
|
|
333
375
|
useEffect(() => {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
if (node && typeof node.measure === 'function') {
|
|
337
|
-
node.measure((_x, _y, width, height, pageX, pageY) => {
|
|
338
|
-
triggerRectRef.current = { height, width, x: pageX, y: pageY };
|
|
339
|
-
triggerOpacity.value = 0;
|
|
340
|
-
setShouldStartTriggerAnimation(true);
|
|
341
|
-
registry.clearTriggerNode(id);
|
|
342
|
-
});
|
|
376
|
+
if (!activeTriggerNode || typeof activeTriggerNode.measure !== 'function') {
|
|
377
|
+
return;
|
|
343
378
|
}
|
|
344
379
|
|
|
380
|
+
activeTriggerNode.measure((_x, _y, measuredWidth, measuredHeight, pageX, pageY) => {
|
|
381
|
+
const nextTriggerRect = {
|
|
382
|
+
height: measuredHeight,
|
|
383
|
+
width: measuredWidth,
|
|
384
|
+
x: pageX,
|
|
385
|
+
y: pageY,
|
|
386
|
+
} satisfies TriggerRect;
|
|
387
|
+
|
|
388
|
+
if (!isValidTriggerRect(nextTriggerRect)) {
|
|
389
|
+
registry.clearActiveTriggerNode(id);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
triggerRectRef.current = nextTriggerRect;
|
|
394
|
+
triggerOpacity.value = 0;
|
|
395
|
+
setShouldStartTriggerAnimation(true);
|
|
396
|
+
registry.clearActiveTriggerNode(id);
|
|
397
|
+
});
|
|
398
|
+
}, [activeTriggerNode, id, isValidTriggerRect, triggerOpacity]);
|
|
399
|
+
|
|
400
|
+
useEffect(() => {
|
|
345
401
|
return () => {
|
|
346
402
|
triggerRectRef.current = null;
|
|
347
403
|
};
|
|
348
|
-
}, [
|
|
349
|
-
|
|
350
|
-
const handleDismiss = useCallback(() => {
|
|
351
|
-
onDismissStart?.();
|
|
404
|
+
}, []);
|
|
352
405
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
const
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
triggerRectRef.current.height / height,
|
|
359
|
-
);
|
|
406
|
+
const animateDismissToRect = useCallback(
|
|
407
|
+
(rect: TriggerRect) => {
|
|
408
|
+
const endX = rect.x + rect.width / 2 - width / 2;
|
|
409
|
+
const endY = rect.y + rect.height / 2 - height / 2;
|
|
410
|
+
const endScale = Math.min(rect.width / width, rect.height / height);
|
|
360
411
|
|
|
361
412
|
triggerScale.value = withTiming(endScale, animationConfig);
|
|
362
413
|
triggerTranslateX.value = withTiming(endX, animationConfig);
|
|
@@ -366,23 +417,62 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
366
417
|
scheduleOnRN(onDismiss);
|
|
367
418
|
}
|
|
368
419
|
});
|
|
369
|
-
|
|
370
|
-
|
|
420
|
+
},
|
|
421
|
+
[
|
|
422
|
+
animationConfig,
|
|
423
|
+
height,
|
|
424
|
+
onDismiss,
|
|
425
|
+
triggerOpacity,
|
|
426
|
+
triggerScale,
|
|
427
|
+
triggerTranslateX,
|
|
428
|
+
triggerTranslateY,
|
|
429
|
+
width,
|
|
430
|
+
],
|
|
431
|
+
);
|
|
371
432
|
|
|
433
|
+
const dismissWithoutTrigger = useCallback(() => {
|
|
372
434
|
if (onDismiss) {
|
|
373
435
|
scheduleOnRN(onDismiss);
|
|
374
436
|
}
|
|
375
|
-
}, [
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
onDismissStart
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
437
|
+
}, [onDismiss]);
|
|
438
|
+
|
|
439
|
+
const handleDismiss = useCallback(() => {
|
|
440
|
+
onDismissStart?.();
|
|
441
|
+
|
|
442
|
+
const dismissTargetIndex = pendingIndexRef.current;
|
|
443
|
+
const indexedTriggerNode = registry.getIndexedTriggerNode(id, dismissTargetIndex);
|
|
444
|
+
|
|
445
|
+
if (indexedTriggerNode && typeof indexedTriggerNode.measure === 'function') {
|
|
446
|
+
indexedTriggerNode.measure((_x, _y, measuredWidth, measuredHeight, pageX, pageY) => {
|
|
447
|
+
const currentTriggerRect = {
|
|
448
|
+
height: measuredHeight,
|
|
449
|
+
width: measuredWidth,
|
|
450
|
+
x: pageX,
|
|
451
|
+
y: pageY,
|
|
452
|
+
} satisfies TriggerRect;
|
|
453
|
+
|
|
454
|
+
if (isValidTriggerRect(currentTriggerRect)) {
|
|
455
|
+
animateDismissToRect(currentTriggerRect);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (isValidTriggerRect(triggerRectRef.current)) {
|
|
460
|
+
animateDismissToRect(triggerRectRef.current);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
dismissWithoutTrigger();
|
|
465
|
+
});
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (isValidTriggerRect(triggerRectRef.current)) {
|
|
470
|
+
animateDismissToRect(triggerRectRef.current);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
dismissWithoutTrigger();
|
|
475
|
+
}, [animateDismissToRect, dismissWithoutTrigger, id, isValidTriggerRect, onDismissStart]);
|
|
386
476
|
|
|
387
477
|
const dismissGesture = useMemo(() => {
|
|
388
478
|
const canDismiss = !isZoomed && dismissOptions.enabled;
|
|
@@ -399,7 +489,14 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
399
489
|
translateY.value = event.translationY / dismissOptions.resistance;
|
|
400
490
|
})
|
|
401
491
|
.onEnd((event) => {
|
|
402
|
-
if (
|
|
492
|
+
if (
|
|
493
|
+
canDismiss &&
|
|
494
|
+
shouldDismissByDirection(
|
|
495
|
+
event.translationY,
|
|
496
|
+
dismissOptions.threshold,
|
|
497
|
+
dismissOptions.direction,
|
|
498
|
+
)
|
|
499
|
+
) {
|
|
403
500
|
scheduleOnRN(handleDismiss);
|
|
404
501
|
return;
|
|
405
502
|
}
|
|
@@ -571,6 +668,40 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
571
668
|
],
|
|
572
669
|
);
|
|
573
670
|
|
|
671
|
+
const emitSingleTap = useCallback((x: number, y: number) => {
|
|
672
|
+
const index = pendingIndexRef.current;
|
|
673
|
+
const currentData = dataRef.current;
|
|
674
|
+
|
|
675
|
+
if (index < 0 || index >= currentData.length) {
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
managerRef.current?.emitTap({ kind: 'single', x, y, index });
|
|
680
|
+
|
|
681
|
+
const item = currentData[index];
|
|
682
|
+
|
|
683
|
+
if (item === undefined) {
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
onSingleTapRef.current?.({ x, y, index, item });
|
|
688
|
+
}, []);
|
|
689
|
+
|
|
690
|
+
const singleTapGesture = useMemo(
|
|
691
|
+
() =>
|
|
692
|
+
Gesture.Tap()
|
|
693
|
+
.enabled(Platform.OS !== 'web')
|
|
694
|
+
.numberOfTaps(1)
|
|
695
|
+
.onEnd((event, success) => {
|
|
696
|
+
if (!success) {
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
scheduleOnRN(emitSingleTap, event.x, event.y);
|
|
701
|
+
}),
|
|
702
|
+
[emitSingleTap],
|
|
703
|
+
);
|
|
704
|
+
|
|
574
705
|
const doubleTapGesture = useMemo(
|
|
575
706
|
() =>
|
|
576
707
|
Gesture.Tap()
|
|
@@ -591,9 +722,14 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
591
722
|
[enableDoubleTapZoom, height, maxZoomScale, scale, translateX, translateY, width],
|
|
592
723
|
);
|
|
593
724
|
|
|
725
|
+
const tapGesture = useMemo(
|
|
726
|
+
() => Gesture.Exclusive(doubleTapGesture, singleTapGesture),
|
|
727
|
+
[doubleTapGesture, singleTapGesture],
|
|
728
|
+
);
|
|
729
|
+
|
|
594
730
|
const zoomGesture = useMemo(
|
|
595
|
-
() => Gesture.Race(zoomPinchGesture, Gesture.Exclusive(zoomPanGesture,
|
|
596
|
-
[zoomPinchGesture, zoomPanGesture,
|
|
731
|
+
() => Gesture.Race(zoomPinchGesture, Gesture.Exclusive(zoomPanGesture, tapGesture)),
|
|
732
|
+
[zoomPinchGesture, zoomPanGesture, tapGesture],
|
|
597
733
|
);
|
|
598
734
|
|
|
599
735
|
const animatedStyle = useAnimatedStyle(() => ({
|
|
@@ -617,38 +753,40 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
617
753
|
return { opacity: baseOpacity };
|
|
618
754
|
}
|
|
619
755
|
|
|
620
|
-
const
|
|
756
|
+
const dismissDistance = getDismissDistance(translateY.value, dismissOptions.direction);
|
|
757
|
+
const dismissOpacity = interpolate(dismissDistance, [0, 200], [1, 0], 'clamp');
|
|
621
758
|
|
|
622
759
|
return { opacity: baseOpacity * dismissOpacity };
|
|
623
|
-
}, [dismissOptions.fadeBackdrop]);
|
|
760
|
+
}, [dismissOptions.direction, dismissOptions.fadeBackdrop]);
|
|
624
761
|
|
|
625
762
|
const nativeScrollGesture = useMemo(() => {
|
|
626
763
|
return Gesture.Native().requireExternalGestureToFail(dismissGestureRef);
|
|
627
764
|
}, []);
|
|
628
765
|
|
|
629
|
-
const { onMomentumScrollEnd, onScroll, onScrollBeginDrag,
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
766
|
+
const { onMomentumScrollEnd, onScroll, onScrollBeginDrag, onWebClick } = useGestureViewerPaging({
|
|
767
|
+
adjustedInitialIndex,
|
|
768
|
+
autoPlay,
|
|
769
|
+
autoPlayInterval,
|
|
770
|
+
currentIndex,
|
|
771
|
+
dataLength,
|
|
772
|
+
enableDoubleTapZoom,
|
|
773
|
+
enableHorizontalSwipe,
|
|
774
|
+
enableLoop,
|
|
775
|
+
height,
|
|
776
|
+
isRotated,
|
|
777
|
+
isZoomed,
|
|
778
|
+
itemSpacing,
|
|
779
|
+
manager,
|
|
780
|
+
maxZoomScale,
|
|
781
|
+
onSingleTap: emitSingleTap,
|
|
782
|
+
scale,
|
|
783
|
+
scrollTo,
|
|
784
|
+
syncCurrentIndex,
|
|
785
|
+
syncPendingIndex,
|
|
786
|
+
translateX,
|
|
787
|
+
translateY,
|
|
788
|
+
width,
|
|
789
|
+
});
|
|
652
790
|
|
|
653
791
|
return {
|
|
654
792
|
animatedStyle,
|
|
@@ -662,7 +800,7 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
662
800
|
isZoomed,
|
|
663
801
|
listRef,
|
|
664
802
|
nativeScrollGesture,
|
|
665
|
-
|
|
803
|
+
onWebClick,
|
|
666
804
|
onMomentumScrollEnd,
|
|
667
805
|
onScroll,
|
|
668
806
|
|
|
@@ -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>(
|
|
@@ -20,6 +20,7 @@ export function useGestureViewerPaging({
|
|
|
20
20
|
manager,
|
|
21
21
|
scrollTo,
|
|
22
22
|
syncCurrentIndex,
|
|
23
|
+
syncPendingIndex,
|
|
23
24
|
width,
|
|
24
25
|
}: UseGestureViewerPagingArgs): UseGestureViewerPagingResult {
|
|
25
26
|
useEffect(() => {
|
|
@@ -99,14 +100,29 @@ export function useGestureViewerPaging({
|
|
|
99
100
|
],
|
|
100
101
|
);
|
|
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
|
+
|
|
102
118
|
const onScrollBeginDrag = useCallback(() => {
|
|
103
119
|
manager?.handleScrollBeginDrag();
|
|
104
120
|
}, [manager]);
|
|
105
121
|
|
|
106
122
|
return {
|
|
107
123
|
onMomentumScrollEnd,
|
|
108
|
-
onScroll
|
|
124
|
+
onScroll,
|
|
109
125
|
onScrollBeginDrag,
|
|
110
|
-
|
|
126
|
+
onWebClick: undefined,
|
|
111
127
|
};
|
|
112
128
|
}
|
|
@@ -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,17 +19,23 @@ 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;
|
|
26
|
+
syncPendingIndex: (nextIndex: number) => void;
|
|
24
27
|
translateX: SharedValue<number>;
|
|
25
28
|
translateY: SharedValue<number>;
|
|
26
29
|
width: number;
|
|
27
30
|
};
|
|
28
31
|
|
|
32
|
+
export type WebClickTarget = {
|
|
33
|
+
getBoundingClientRect: () => { left: number; top: number };
|
|
34
|
+
};
|
|
35
|
+
|
|
29
36
|
export type UseGestureViewerPagingResult = {
|
|
30
37
|
onMomentumScrollEnd?: ScrollViewProps['onMomentumScrollEnd'];
|
|
31
38
|
onScroll?: ScrollViewProps['onScroll'];
|
|
32
39
|
onScrollBeginDrag?: ScrollViewProps['onScrollBeginDrag'];
|
|
33
|
-
|
|
40
|
+
onWebClick?: (event: MouseEvent<WebClickTarget>) => void;
|
|
34
41
|
};
|