react-native-gesture-image-viewer 2.4.0 → 2.5.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 +19 -5
- package/lib/module/GestureViewer.js +31 -13
- package/lib/module/GestureViewer.js.map +1 -1
- package/lib/module/GestureViewerManager.js +43 -14
- package/lib/module/GestureViewerManager.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/itemDimensions.js +86 -0
- package/lib/module/itemDimensions.js.map +1 -0
- package/lib/module/useGestureViewer.js +244 -32
- package/lib/module/useGestureViewer.js.map +1 -1
- package/lib/module/useGestureViewerPaging.js +1 -3
- package/lib/module/useGestureViewerPaging.js.map +1 -1
- package/lib/module/useGestureViewerPaging.web.js +26 -18
- package/lib/module/useGestureViewerPaging.web.js.map +1 -1
- package/lib/module/utils/index.js +43 -10
- package/lib/module/utils/index.js.map +1 -1
- package/lib/module/utils/tapZoom.js +51 -13
- 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 +16 -3
- 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/itemDimensions.d.ts +26 -0
- package/lib/typescript/src/itemDimensions.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +29 -0
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/useGestureViewer.d.ts +3 -2
- package/lib/typescript/src/useGestureViewer.d.ts.map +1 -1
- package/lib/typescript/src/useGestureViewerPaging.d.ts.map +1 -1
- package/lib/typescript/src/useGestureViewerPaging.types.d.ts +2 -0
- 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/index.d.ts +7 -3
- package/lib/typescript/src/utils/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/tapZoom.d.ts +17 -1
- package/lib/typescript/src/utils/tapZoom.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/GestureViewer.tsx +47 -16
- package/src/GestureViewerManager.ts +58 -19
- package/src/index.tsx +4 -0
- package/src/itemDimensions.ts +162 -0
- package/src/types.ts +39 -0
- package/src/useGestureViewer.ts +352 -36
- package/src/useGestureViewerPaging.ts +1 -3
- package/src/useGestureViewerPaging.types.ts +2 -0
- package/src/useGestureViewerPaging.web.ts +41 -21
- package/src/utils/index.ts +86 -27
- package/src/utils/tapZoom.ts +66 -13
|
@@ -30,6 +30,8 @@ export function useGestureViewerPaging({
|
|
|
30
30
|
adjustedInitialIndex,
|
|
31
31
|
autoPlay,
|
|
32
32
|
autoPlayInterval,
|
|
33
|
+
contentHeight,
|
|
34
|
+
contentWidth,
|
|
33
35
|
currentIndex,
|
|
34
36
|
dataLength,
|
|
35
37
|
enableDoubleTapZoom,
|
|
@@ -51,20 +53,25 @@ export function useGestureViewerPaging({
|
|
|
51
53
|
width,
|
|
52
54
|
}: UseGestureViewerPagingArgs): UseGestureViewerPagingResult {
|
|
53
55
|
const [activeListIndex, setActiveListIndex] = useState(adjustedInitialIndex);
|
|
54
|
-
const
|
|
55
|
-
const activeResetWidth = adjustedInitialIndex > 0 ? width : 0;
|
|
56
|
+
const pageStride = width + itemSpacing;
|
|
56
57
|
const webSingleTapTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
57
58
|
|
|
58
59
|
const webScrollRuntimeRef = useRef<WebScrollRuntime>({
|
|
59
60
|
actor: 'idle',
|
|
60
61
|
isAutoplayPausedByUser: false,
|
|
61
62
|
lastSettledPhysicalIndex: adjustedInitialIndex,
|
|
62
|
-
latestOffsetX: adjustedInitialIndex *
|
|
63
|
+
latestOffsetX: adjustedInitialIndex * pageStride,
|
|
63
64
|
latestRawPhysicalIndex: adjustedInitialIndex,
|
|
64
65
|
lastProgrammaticScrollVersion: 0,
|
|
65
66
|
settleTimer: null,
|
|
66
67
|
resumeAutoplayTimer: null,
|
|
67
68
|
});
|
|
69
|
+
const previousPagingLayoutRef = useRef<{
|
|
70
|
+
adjustedInitialIndex: number;
|
|
71
|
+
dataLength: number;
|
|
72
|
+
manager: typeof manager;
|
|
73
|
+
pageStride: number;
|
|
74
|
+
} | null>(null);
|
|
68
75
|
|
|
69
76
|
const clearWebSingleTapTimer = useCallback(() => {
|
|
70
77
|
if (webSingleTapTimerRef.current) {
|
|
@@ -227,38 +234,47 @@ export function useGestureViewerPaging({
|
|
|
227
234
|
|
|
228
235
|
useEffect(() => {
|
|
229
236
|
const runtime = webScrollRuntimeRef.current;
|
|
237
|
+
const previousLayout = previousPagingLayoutRef.current;
|
|
238
|
+
const shouldResetToInitialIndex =
|
|
239
|
+
previousLayout === null ||
|
|
240
|
+
previousLayout.adjustedInitialIndex !== adjustedInitialIndex ||
|
|
241
|
+
previousLayout.dataLength !== dataLength ||
|
|
242
|
+
previousLayout.manager !== manager;
|
|
243
|
+
const didPageStrideChange = previousLayout !== null && previousLayout.pageStride !== pageStride;
|
|
244
|
+
|
|
245
|
+
previousPagingLayoutRef.current = {
|
|
246
|
+
adjustedInitialIndex,
|
|
247
|
+
dataLength,
|
|
248
|
+
manager,
|
|
249
|
+
pageStride,
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
if (!shouldResetToInitialIndex && !didPageStrideChange) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
230
255
|
|
|
231
256
|
runtime.actor = 'idle';
|
|
232
257
|
runtime.isAutoplayPausedByUser = false;
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
258
|
+
|
|
259
|
+
if (shouldResetToInitialIndex) {
|
|
260
|
+
runtime.lastSettledPhysicalIndex = adjustedInitialIndex;
|
|
261
|
+
setActiveListIndex(adjustedInitialIndex);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
runtime.latestOffsetX = runtime.lastSettledPhysicalIndex * pageStride;
|
|
265
|
+
runtime.latestRawPhysicalIndex = runtime.lastSettledPhysicalIndex;
|
|
236
266
|
runtime.lastProgrammaticScrollVersion = manager?.getProgrammaticScrollVersion() ?? 0;
|
|
237
|
-
setActiveListIndex(adjustedInitialIndex);
|
|
238
267
|
clearWebSettleTimer();
|
|
239
268
|
clearWebAutoplayResumeTimer();
|
|
240
269
|
}, [
|
|
241
|
-
activeResetItemSpacing,
|
|
242
|
-
activeResetWidth,
|
|
243
270
|
adjustedInitialIndex,
|
|
244
271
|
clearWebAutoplayResumeTimer,
|
|
245
272
|
clearWebSettleTimer,
|
|
246
273
|
dataLength,
|
|
247
274
|
manager,
|
|
275
|
+
pageStride,
|
|
248
276
|
]);
|
|
249
277
|
|
|
250
|
-
useEffect(() => {
|
|
251
|
-
const runtime = webScrollRuntimeRef.current;
|
|
252
|
-
|
|
253
|
-
runtime.actor = 'idle';
|
|
254
|
-
runtime.isAutoplayPausedByUser = false;
|
|
255
|
-
runtime.latestOffsetX = runtime.lastSettledPhysicalIndex * (width + itemSpacing);
|
|
256
|
-
runtime.latestRawPhysicalIndex = runtime.lastSettledPhysicalIndex;
|
|
257
|
-
runtime.lastProgrammaticScrollVersion = manager?.getProgrammaticScrollVersion() ?? 0;
|
|
258
|
-
clearWebSettleTimer();
|
|
259
|
-
clearWebAutoplayResumeTimer();
|
|
260
|
-
}, [clearWebAutoplayResumeTimer, clearWebSettleTimer, itemSpacing, manager, width]);
|
|
261
|
-
|
|
262
278
|
useEffect(() => {
|
|
263
279
|
return () => {
|
|
264
280
|
clearWebSingleTapTimer();
|
|
@@ -405,6 +421,8 @@ export function useGestureViewerPaging({
|
|
|
405
421
|
scheduleWebAutoplayResume();
|
|
406
422
|
|
|
407
423
|
applyTapZoomAtPoint({
|
|
424
|
+
contentHeight: contentHeight.get(),
|
|
425
|
+
contentWidth: contentWidth.get(),
|
|
408
426
|
x: resolvedX,
|
|
409
427
|
y: resolvedY,
|
|
410
428
|
width,
|
|
@@ -430,6 +448,8 @@ export function useGestureViewerPaging({
|
|
|
430
448
|
},
|
|
431
449
|
[
|
|
432
450
|
clearWebSingleTapTimer,
|
|
451
|
+
contentHeight,
|
|
452
|
+
contentWidth,
|
|
433
453
|
enableDoubleTapZoom,
|
|
434
454
|
height,
|
|
435
455
|
maxZoomScale,
|
package/src/utils/index.ts
CHANGED
|
@@ -47,39 +47,92 @@ export const shouldUseNativeScrollGesture = (
|
|
|
47
47
|
return platformOS === 'ios' && component !== GestureScrollView && component !== GestureFlatList;
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
-
export const
|
|
51
|
-
(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
50
|
+
export const clampIndex = (index: number | undefined, dataLength: number): number => {
|
|
51
|
+
if (dataLength <= 0) {
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const candidateIndex = index ?? 0;
|
|
56
|
+
|
|
57
|
+
if (!Number.isFinite(candidateIndex)) {
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return Math.min(Math.max(Math.trunc(candidateIndex), 0), dataLength - 1);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const resolveGeometrySyncTranslationMode = (
|
|
65
|
+
previousIndex: number | null,
|
|
66
|
+
nextIndex: number,
|
|
67
|
+
scale: number,
|
|
68
|
+
): 'constrain' | 'none' | 'reset' => {
|
|
69
|
+
if (previousIndex !== null && previousIndex !== nextIndex) {
|
|
70
|
+
return 'reset';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return scale > 1 ? 'constrain' : 'none';
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const isValidDimension = (value: number): boolean => {
|
|
77
|
+
'worklet';
|
|
78
|
+
|
|
79
|
+
return Number.isFinite(value) && value > 0;
|
|
80
|
+
};
|
|
69
81
|
|
|
70
|
-
|
|
71
|
-
|
|
82
|
+
const clampTranslation = (value: number, max: number): number => {
|
|
83
|
+
'worklet';
|
|
72
84
|
|
|
85
|
+
if (max <= 0) {
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const clamped = Math.max(-max, Math.min(max, value));
|
|
90
|
+
|
|
91
|
+
return clamped === 0 ? 0 : clamped;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const clampTranslationToBounds = ({
|
|
95
|
+
width,
|
|
96
|
+
height,
|
|
97
|
+
contentWidth,
|
|
98
|
+
contentHeight,
|
|
99
|
+
scale,
|
|
100
|
+
translateX,
|
|
101
|
+
translateY,
|
|
102
|
+
}: {
|
|
103
|
+
width: number;
|
|
104
|
+
height: number;
|
|
105
|
+
contentWidth?: number;
|
|
106
|
+
contentHeight?: number;
|
|
107
|
+
translateX: number;
|
|
108
|
+
translateY: number;
|
|
109
|
+
scale: number;
|
|
110
|
+
}) => {
|
|
111
|
+
'worklet';
|
|
112
|
+
|
|
113
|
+
if (scale <= 1) {
|
|
73
114
|
return {
|
|
74
|
-
translateX
|
|
75
|
-
translateY
|
|
115
|
+
translateX,
|
|
116
|
+
translateY,
|
|
76
117
|
};
|
|
77
|
-
}
|
|
118
|
+
}
|
|
78
119
|
|
|
79
|
-
|
|
80
|
-
|
|
120
|
+
const baseContentWidth =
|
|
121
|
+
contentWidth !== undefined && isValidDimension(contentWidth) ? contentWidth : width;
|
|
122
|
+
const baseContentHeight =
|
|
123
|
+
contentHeight !== undefined && isValidDimension(contentHeight) ? contentHeight : height;
|
|
81
124
|
|
|
82
|
-
|
|
125
|
+
const maxTranslateX = Math.max(0, (baseContentWidth * scale - width) / 2);
|
|
126
|
+
const maxTranslateY = Math.max(0, (baseContentHeight * scale - height) / 2);
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
translateX: clampTranslation(translateX, maxTranslateX),
|
|
130
|
+
translateY: clampTranslation(translateY, maxTranslateY),
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export const createLoopData = <T>(data: T[], enableLoop: boolean): T[] => {
|
|
135
|
+
if (!enableLoop || data.length <= 1) {
|
|
83
136
|
return data;
|
|
84
137
|
}
|
|
85
138
|
|
|
@@ -93,6 +146,12 @@ export const createLoopData = <T>(dataRef: React.RefObject<T[]>, enableLoop: boo
|
|
|
93
146
|
return [lastItem, ...data, firstItem];
|
|
94
147
|
};
|
|
95
148
|
|
|
149
|
+
export const getLoopPhysicalIndex = (
|
|
150
|
+
logicalIndex: number,
|
|
151
|
+
dataLength: number,
|
|
152
|
+
enableLoop: boolean,
|
|
153
|
+
): number => (enableLoop && dataLength > 1 ? logicalIndex + 1 : logicalIndex);
|
|
154
|
+
|
|
96
155
|
export const getLoopAdjustedIndex = (
|
|
97
156
|
scrollIndex: number,
|
|
98
157
|
originalDataLength: number,
|
package/src/utils/tapZoom.ts
CHANGED
|
@@ -1,11 +1,62 @@
|
|
|
1
1
|
import type { SharedValue } from 'react-native-reanimated';
|
|
2
2
|
import { Easing, withTiming } from 'react-native-reanimated';
|
|
3
3
|
|
|
4
|
+
import { clampTranslationToBounds } from '.';
|
|
5
|
+
|
|
6
|
+
export const getTapZoomTarget = ({
|
|
7
|
+
x,
|
|
8
|
+
y,
|
|
9
|
+
width,
|
|
10
|
+
height,
|
|
11
|
+
contentWidth,
|
|
12
|
+
contentHeight,
|
|
13
|
+
maxZoomScale,
|
|
14
|
+
scale,
|
|
15
|
+
}: {
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
contentWidth?: number;
|
|
21
|
+
contentHeight?: number;
|
|
22
|
+
maxZoomScale: number;
|
|
23
|
+
scale: number;
|
|
24
|
+
}) => {
|
|
25
|
+
'worklet';
|
|
26
|
+
|
|
27
|
+
const nextScale = scale > 1 ? 1 : maxZoomScale;
|
|
28
|
+
|
|
29
|
+
if (nextScale <= 1) {
|
|
30
|
+
return {
|
|
31
|
+
scale: nextScale,
|
|
32
|
+
translateX: 0,
|
|
33
|
+
translateY: 0,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const centerX = x - width / 2;
|
|
38
|
+
const centerY = y - height / 2;
|
|
39
|
+
return {
|
|
40
|
+
scale: nextScale,
|
|
41
|
+
...clampTranslationToBounds({
|
|
42
|
+
contentHeight,
|
|
43
|
+
contentWidth,
|
|
44
|
+
height,
|
|
45
|
+
scale: nextScale,
|
|
46
|
+
translateX: -centerX * (nextScale - 1),
|
|
47
|
+
translateY: -centerY * (nextScale - 1),
|
|
48
|
+
width,
|
|
49
|
+
}),
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
|
|
4
53
|
export const applyTapZoomAtPoint = ({
|
|
5
54
|
x,
|
|
6
55
|
y,
|
|
7
56
|
width,
|
|
8
57
|
height,
|
|
58
|
+
contentWidth,
|
|
59
|
+
contentHeight,
|
|
9
60
|
maxZoomScale,
|
|
10
61
|
scale,
|
|
11
62
|
translateX,
|
|
@@ -15,6 +66,8 @@ export const applyTapZoomAtPoint = ({
|
|
|
15
66
|
y: number;
|
|
16
67
|
width: number;
|
|
17
68
|
height: number;
|
|
69
|
+
contentWidth?: number;
|
|
70
|
+
contentHeight?: number;
|
|
18
71
|
maxZoomScale: number;
|
|
19
72
|
scale: SharedValue<number>;
|
|
20
73
|
translateX: SharedValue<number>;
|
|
@@ -22,23 +75,23 @@ export const applyTapZoomAtPoint = ({
|
|
|
22
75
|
}) => {
|
|
23
76
|
'worklet';
|
|
24
77
|
|
|
25
|
-
const
|
|
78
|
+
const target = getTapZoomTarget({
|
|
79
|
+
contentHeight,
|
|
80
|
+
contentWidth,
|
|
81
|
+
height,
|
|
82
|
+
maxZoomScale,
|
|
83
|
+
scale: scale.get(),
|
|
84
|
+
width,
|
|
85
|
+
x,
|
|
86
|
+
y,
|
|
87
|
+
});
|
|
26
88
|
const timingConfig = {
|
|
27
89
|
duration: 300,
|
|
28
90
|
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
|
|
29
91
|
};
|
|
30
92
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const centerY = y - height / 2;
|
|
34
|
-
|
|
35
|
-
// NOTE 확대로 밀려난 거리만큼 반대로 이동해서 탭 지점을 제자리에 유지
|
|
36
|
-
translateX.set(withTiming(-centerX * (nextScale - 1), timingConfig));
|
|
37
|
-
translateY.set(withTiming(-centerY * (nextScale - 1), timingConfig));
|
|
38
|
-
} else {
|
|
39
|
-
translateX.set(withTiming(0, timingConfig));
|
|
40
|
-
translateY.set(withTiming(0, timingConfig));
|
|
41
|
-
}
|
|
93
|
+
translateX.set(withTiming(target.translateX, timingConfig));
|
|
94
|
+
translateY.set(withTiming(target.translateY, timingConfig));
|
|
42
95
|
|
|
43
|
-
scale.set(withTiming(
|
|
96
|
+
scale.set(withTiming(target.scale, timingConfig));
|
|
44
97
|
};
|