react-native-gesture-image-viewer 1.2.3 → 1.4.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.
@@ -19,6 +19,7 @@ import {
19
19
  import type GestureViewerManager from './GestureViewerManager';
20
20
  import { registry } from './GestureViewerRegistry';
21
21
  import type { GestureViewerProps } from './types';
22
+ import { createBoundsConstraint } from './utils';
22
23
 
23
24
  type UseGestureViewerProps<T = any> = Omit<
24
25
  GestureViewerProps<T>,
@@ -50,7 +51,7 @@ export const useGestureViewer = <T = any>({
50
51
  const width = useSnap ? customWidth || screenWidth : screenWidth;
51
52
 
52
53
  const [isZoomed, setIsZoomed] = useState(false);
53
-
54
+ const [isRotated, setIsRotated] = useState(false);
54
55
  const [currentIndex, setCurrentIndex] = useState(initialIndex);
55
56
  const [manager, setManager] = useState<GestureViewerManager | null>(null);
56
57
 
@@ -64,29 +65,14 @@ export const useGestureViewer = <T = any>({
64
65
  const translateX = useSharedValue(0);
65
66
  const scale = useSharedValue(1);
66
67
  const backdropOpacity = useSharedValue(1);
68
+ const rotation = useSharedValue(0);
67
69
 
68
70
  const listRef = useRef<any>(null);
69
71
 
70
72
  const dataLength = data?.length || 0;
71
73
 
72
- const constrainToBounds = useCallback(
73
- (translateX: number, translateY: number, scale: number) => {
74
- 'worklet';
75
- if (scale <= 1) {
76
- return {
77
- x: translateX,
78
- y: translateY,
79
- };
80
- }
81
-
82
- const maxTranslateX = (width * scale - width) / 2;
83
- const maxTranslateY = (screenHeight * scale - screenHeight) / 2;
84
-
85
- return {
86
- x: Math.max(-maxTranslateX, Math.min(maxTranslateX, translateX)),
87
- y: Math.max(-maxTranslateY, Math.min(maxTranslateY, translateY)),
88
- };
89
- },
74
+ const constrainTranslation = useMemo(
75
+ () => createBoundsConstraint({ width, height: screenHeight }),
90
76
  [width, screenHeight],
91
77
  );
92
78
 
@@ -97,6 +83,13 @@ export const useGestureViewer = <T = any>({
97
83
  },
98
84
  );
99
85
 
86
+ useAnimatedReaction(
87
+ () => rotation.value,
88
+ (currentRotation) => {
89
+ runOnJS(setIsRotated)(currentRotation % 360 !== 0);
90
+ },
91
+ );
92
+
100
93
  useEffect(() => {
101
94
  const handleManagerChange = (manager: GestureViewerManager | null) => {
102
95
  unsubscribeRef.current?.();
@@ -132,8 +125,24 @@ export const useGestureViewer = <T = any>({
132
125
  manager.setEnableSwipeGesture(enableSwipeGesture);
133
126
  manager.setCurrentIndex(initialIndex);
134
127
  manager.setWidth(width + itemSpacing);
128
+ manager.setHeight(screenHeight);
129
+ manager.setZoomSharedValues(scale, translateX, translateY, maxZoomScale);
130
+ manager.setRotation(rotation);
135
131
  manager.notifyStateChange();
136
- }, [dataLength, enableSwipeGesture, initialIndex, manager, width, itemSpacing]);
132
+ }, [
133
+ dataLength,
134
+ enableSwipeGesture,
135
+ initialIndex,
136
+ manager,
137
+ width,
138
+ itemSpacing,
139
+ maxZoomScale,
140
+ scale,
141
+ screenHeight,
142
+ translateX,
143
+ translateY,
144
+ rotation,
145
+ ]);
137
146
 
138
147
  useEffect(() => {
139
148
  if (!manager || !listRef.current) {
@@ -153,6 +162,7 @@ export const useGestureViewer = <T = any>({
153
162
  scale.value = 1;
154
163
  backdropOpacity.value = 1;
155
164
  startScale.value = 1;
165
+ rotation.value = 0;
156
166
 
157
167
  if (initialIndex <= 0 || !listRef.current) {
158
168
  return;
@@ -175,7 +185,7 @@ export const useGestureViewer = <T = any>({
175
185
  return () => {
176
186
  runAfterInteractions?.cancel();
177
187
  };
178
- }, [initialIndex, translateY, backdropOpacity, translateX, scale, startScale, width, itemSpacing]);
188
+ }, [initialIndex, translateY, backdropOpacity, translateX, scale, startScale, width, itemSpacing, rotation]);
179
189
 
180
190
  const onMomentumScrollEnd = useCallback(
181
191
  (event: NativeSyntheticEvent<NativeScrollEvent>) => {
@@ -199,6 +209,7 @@ export const useGestureViewer = <T = any>({
199
209
  initialTranslateY.value = withTiming(0);
200
210
  startScale.value = withTiming(1);
201
211
  scale.value = withTiming(1);
212
+ rotation.value = 0;
202
213
  }
203
214
  },
204
215
  [
@@ -214,6 +225,7 @@ export const useGestureViewer = <T = any>({
214
225
  initialTranslateX,
215
226
  initialTranslateY,
216
227
  startScale,
228
+ rotation,
217
229
  ],
218
230
  );
219
231
 
@@ -252,25 +264,30 @@ export const useGestureViewer = <T = any>({
252
264
  .onUpdate((event) => {
253
265
  const newScale = startScale.value * event.scale;
254
266
 
267
+ scale.value = newScale;
268
+
269
+ if (newScale <= 1) {
270
+ translateX.value = withTiming(0);
271
+ translateY.value = withTiming(0);
272
+ return;
273
+ }
274
+
255
275
  const deltaScale = newScale - startScale.value;
256
276
  const centerX = event.focalX - width / 2;
257
277
  const centerY = event.focalY - screenHeight / 2;
258
278
 
259
- scale.value = newScale;
260
279
  // NOTE 새로운 이동값 = 기존 이동값 - (중심점 거리 × 스케일 변화량) / 원래 스케일 (중심점이 화면 중심에서 멀수록, 확대 배율이 클수록 더 많이 이동)
261
280
  const newTranslateX = initialTranslateX.value - (centerX * deltaScale) / startScale.value;
262
281
  const newTranslateY = initialTranslateY.value - (centerY * deltaScale) / startScale.value;
263
282
 
264
- const constrained = constrainToBounds(newTranslateX, newTranslateY, newScale);
265
-
266
- if (newScale <= 1) {
267
- translateX.value = withTiming(0);
268
- translateY.value = withTiming(0);
269
- return;
270
- }
283
+ const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } = constrainTranslation({
284
+ translateX: newTranslateX,
285
+ translateY: newTranslateY,
286
+ scale: newScale,
287
+ });
271
288
 
272
- translateX.value = constrained.x;
273
- translateY.value = constrained.y;
289
+ translateX.value = constrainedTranslateX;
290
+ translateY.value = constrainedTranslateY;
274
291
  })
275
292
  .onEnd(() => {
276
293
  if (scale.value > maxZoomScale) {
@@ -279,10 +296,14 @@ export const useGestureViewer = <T = any>({
279
296
  easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
280
297
  });
281
298
 
282
- const constrained = constrainToBounds(translateX.value, translateY.value, maxZoomScale);
299
+ const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } = constrainTranslation({
300
+ translateX: translateX.value,
301
+ translateY: translateY.value,
302
+ scale: maxZoomScale,
303
+ });
283
304
 
284
- translateX.value = withTiming(constrained.x);
285
- translateY.value = withTiming(constrained.y);
305
+ translateX.value = withTiming(constrainedTranslateX);
306
+ translateY.value = withTiming(constrainedTranslateY);
286
307
 
287
308
  return;
288
309
  }
@@ -299,9 +320,14 @@ export const useGestureViewer = <T = any>({
299
320
  return;
300
321
  }
301
322
 
302
- const finalConstrained = constrainToBounds(translateX.value, translateY.value, scale.value);
303
- translateX.value = withTiming(finalConstrained.x);
304
- translateY.value = withTiming(finalConstrained.y);
323
+ const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } = constrainTranslation({
324
+ translateX: translateX.value,
325
+ translateY: translateY.value,
326
+ scale: scale.value,
327
+ });
328
+
329
+ translateX.value = withTiming(constrainedTranslateX);
330
+ translateY.value = withTiming(constrainedTranslateY);
305
331
  });
306
332
  }, [
307
333
  scale,
@@ -314,7 +340,7 @@ export const useGestureViewer = <T = any>({
314
340
  initialTranslateY,
315
341
  width,
316
342
  screenHeight,
317
- constrainToBounds,
343
+ constrainTranslation,
318
344
  ]);
319
345
 
320
346
  const zoomPanGesture = useMemo(() => {
@@ -331,10 +357,14 @@ export const useGestureViewer = <T = any>({
331
357
  const newTranslateX = initialTranslateX.value + event.translationX;
332
358
  const newTranslateY = initialTranslateY.value + event.translationY;
333
359
 
334
- const constrained = constrainToBounds(newTranslateX, newTranslateY, scale.value);
360
+ const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } = constrainTranslation({
361
+ translateX: newTranslateX,
362
+ translateY: newTranslateY,
363
+ scale: scale.value,
364
+ });
335
365
 
336
- translateX.value = constrained.x;
337
- translateY.value = constrained.y;
366
+ translateX.value = constrainedTranslateX;
367
+ translateY.value = constrainedTranslateY;
338
368
  }
339
369
  });
340
370
  }, [
@@ -345,7 +375,7 @@ export const useGestureViewer = <T = any>({
345
375
  scale,
346
376
  initialTranslateX,
347
377
  initialTranslateY,
348
- constrainToBounds,
378
+ constrainTranslation,
349
379
  ]);
350
380
 
351
381
  const doubleTapGesture = useMemo(() => {
@@ -392,7 +422,12 @@ export const useGestureViewer = <T = any>({
392
422
 
393
423
  const animatedStyle = useAnimatedStyle(() => {
394
424
  return {
395
- transform: [{ translateY: translateY.value }, { translateX: translateX.value }, { scale: scale.value }],
425
+ transform: [
426
+ { translateY: translateY.value },
427
+ { translateX: translateX.value },
428
+ { scale: scale.value },
429
+ { rotate: `${rotation.value}deg` },
430
+ ],
396
431
  };
397
432
  });
398
433
 
@@ -412,7 +447,7 @@ export const useGestureViewer = <T = any>({
412
447
  translateY,
413
448
  listRef,
414
449
  isZoomed,
415
-
450
+ isRotated,
416
451
  dismissGesture,
417
452
  zoomGesture,
418
453
 
@@ -42,6 +42,10 @@ export const useGestureViewerController = (id = 'default') => {
42
42
  goToIndex: manager?.goToIndex || noopFunction,
43
43
  goToPrevious: manager?.goToPrevious || noopFunction,
44
44
  goToNext: manager?.goToNext || noopFunction,
45
+ zoomIn: manager?.zoomIn || noopFunction,
46
+ zoomOut: manager?.zoomOut || noopFunction,
47
+ resetZoom: manager?.resetZoom || noopFunction,
48
+ rotate: manager?.rotate || noopFunction,
45
49
  currentIndex: state.currentIndex,
46
50
  totalCount: state.dataLength,
47
51
  };
package/src/utils.ts CHANGED
@@ -27,3 +27,24 @@ export const isFlashListLike = (component: any): component is any => {
27
27
 
28
28
  return component?.name === 'FlashList';
29
29
  };
30
+
31
+ export const createBoundsConstraint =
32
+ ({ width, height }: { width: number; height: number }) =>
33
+ ({ scale, translateX, translateY }: { translateX: number; translateY: number; scale: number }) => {
34
+ 'worklet';
35
+
36
+ if (scale <= 1) {
37
+ return {
38
+ translateX,
39
+ translateY,
40
+ };
41
+ }
42
+
43
+ const maxTranslateX = (width * scale - width) / 2;
44
+ const maxTranslateY = (height * scale - height) / 2;
45
+
46
+ return {
47
+ translateX: Math.max(-maxTranslateX, Math.min(maxTranslateX, translateX)),
48
+ translateY: Math.max(-maxTranslateY, Math.min(maxTranslateY, translateY)),
49
+ };
50
+ };