react-native-gesture-image-viewer 2.0.0-beta.2 → 2.0.0-beta.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.
- package/README.md +13 -6
- package/lib/module/GestureViewer.js +5 -5
- package/lib/module/GestureViewer.js.map +1 -1
- package/lib/module/GestureViewerManager.js +5 -5
- package/lib/module/GestureViewerManager.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/useGestureViewer.js +40 -63
- package/lib/module/useGestureViewer.js.map +1 -1
- package/lib/module/useGestureViewerController.js +39 -58
- package/lib/module/useGestureViewerController.js.map +1 -1
- package/lib/module/useGestureViewerState.js +78 -0
- package/lib/module/useGestureViewerState.js.map +1 -0
- package/lib/typescript/src/GestureViewer.d.ts +2 -2
- package/lib/typescript/src/GestureViewer.d.ts.map +1 -1
- package/lib/typescript/src/GestureViewerManager.d.ts +5 -8
- package/lib/typescript/src/GestureViewerManager.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +60 -57
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/useGestureViewer.d.ts +1 -1
- package/lib/typescript/src/useGestureViewer.d.ts.map +1 -1
- package/lib/typescript/src/useGestureViewerController.d.ts +10 -21
- package/lib/typescript/src/useGestureViewerController.d.ts.map +1 -1
- package/lib/typescript/src/useGestureViewerState.d.ts +35 -0
- package/lib/typescript/src/useGestureViewerState.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/GestureViewer.tsx +7 -7
- package/src/GestureViewerManager.ts +9 -9
- package/src/index.tsx +2 -1
- package/src/types.ts +60 -59
- package/src/useGestureViewer.ts +41 -69
- package/src/useGestureViewerController.ts +43 -62
- package/src/useGestureViewerState.ts +85 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { GestureViewerState } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Hook to access the current state of the gesture viewer.
|
|
4
|
+
*
|
|
5
|
+
* @param id - Viewer instance identifier (default: 'default')
|
|
6
|
+
* @returns Current state of the viewer
|
|
7
|
+
*
|
|
8
|
+
* **Available state:**
|
|
9
|
+
* - `currentIndex: number` - Current active index (read-only)
|
|
10
|
+
* - `totalCount: number` - Total number of items (read-only)
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* const { currentIndex, totalCount } = useGestureViewerState();
|
|
15
|
+
*
|
|
16
|
+
* // Display current position
|
|
17
|
+
* return (
|
|
18
|
+
* <Text>
|
|
19
|
+
* {currentIndex + 1} / {totalCount}
|
|
20
|
+
* </Text>
|
|
21
|
+
* );
|
|
22
|
+
*
|
|
23
|
+
* // React to index changes
|
|
24
|
+
* useEffect(() => {
|
|
25
|
+
* console.log(`Moved to image ${currentIndex + 1}`);
|
|
26
|
+
* trackPageView(currentIndex);
|
|
27
|
+
* }, [currentIndex]);
|
|
28
|
+
*
|
|
29
|
+
* // Check navigation availability
|
|
30
|
+
* const canGoNext = currentIndex < totalCount - 1;
|
|
31
|
+
* const canGoPrevious = currentIndex > 0;
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare const useGestureViewerState: (id?: string) => GestureViewerState;
|
|
35
|
+
//# sourceMappingURL=useGestureViewerState.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useGestureViewerState.d.ts","sourceRoot":"","sources":["../../../src/useGestureViewerState.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,qBAAqB,GAAI,WAAc,KAAG,kBAgDtD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-gesture-image-viewer",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.4",
|
|
4
4
|
"description": "🖼️ A highly customizable and easy-to-use React Native image viewer with gesture support and external controls",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
package/src/GestureViewer.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
2
|
-
import { type
|
|
2
|
+
import { Platform, type ScrollView, type ScrollViewProps, StyleSheet, useWindowDimensions, View } from 'react-native';
|
|
3
3
|
import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
4
4
|
import Animated from 'react-native-reanimated';
|
|
5
5
|
import { registry } from './GestureViewerRegistry';
|
|
@@ -8,7 +8,7 @@ import { useGestureViewer } from './useGestureViewer';
|
|
|
8
8
|
import { createLoopData, isFlashListLike, isFlatListLike, isScrollViewLike } from './utils';
|
|
9
9
|
import WebPagingFixStyle from './WebPagingFixStyle';
|
|
10
10
|
|
|
11
|
-
export function GestureViewer<T = any, LC = typeof
|
|
11
|
+
export function GestureViewer<T = any, LC = typeof ScrollView>({
|
|
12
12
|
id = 'default',
|
|
13
13
|
data,
|
|
14
14
|
renderItem: renderItemProp,
|
|
@@ -20,7 +20,7 @@ export function GestureViewer<T = any, LC = typeof FlatList>({
|
|
|
20
20
|
containerStyle,
|
|
21
21
|
initialIndex = 0,
|
|
22
22
|
itemSpacing = 0,
|
|
23
|
-
|
|
23
|
+
enableSnapMode = false,
|
|
24
24
|
enableLoop = false,
|
|
25
25
|
...props
|
|
26
26
|
}: GestureViewerProps<T, LC>) {
|
|
@@ -31,7 +31,7 @@ export function GestureViewer<T = any, LC = typeof FlatList>({
|
|
|
31
31
|
|
|
32
32
|
const { width: screenWidth } = useWindowDimensions();
|
|
33
33
|
|
|
34
|
-
const width =
|
|
34
|
+
const width = enableSnapMode ? customWidth || screenWidth : screenWidth;
|
|
35
35
|
|
|
36
36
|
const loopData = useMemo(() => createLoopData(dataRef, enableLoop), [enableLoop]);
|
|
37
37
|
|
|
@@ -53,7 +53,7 @@ export function GestureViewer<T = any, LC = typeof FlatList>({
|
|
|
53
53
|
width,
|
|
54
54
|
initialIndex,
|
|
55
55
|
itemSpacing,
|
|
56
|
-
|
|
56
|
+
enableSnapMode,
|
|
57
57
|
enableLoop,
|
|
58
58
|
...props,
|
|
59
59
|
});
|
|
@@ -118,7 +118,7 @@ export function GestureViewer<T = any, LC = typeof FlatList>({
|
|
|
118
118
|
showsHorizontalScrollIndicator: false,
|
|
119
119
|
onMomentumScrollEnd: onMomentumScrollEnd,
|
|
120
120
|
onScrollBeginDrag,
|
|
121
|
-
...(
|
|
121
|
+
...(enableSnapMode
|
|
122
122
|
? {
|
|
123
123
|
snapToInterval: width + itemSpacing,
|
|
124
124
|
snapToAlignment: 'center',
|
|
@@ -130,7 +130,7 @@ export function GestureViewer<T = any, LC = typeof FlatList>({
|
|
|
130
130
|
scrollEventThrottle: 16,
|
|
131
131
|
removeClippedSubviews: true,
|
|
132
132
|
}) satisfies ScrollViewProps,
|
|
133
|
-
[width, itemSpacing, isZoomed, isRotated, onMomentumScrollEnd, onScrollBeginDrag,
|
|
133
|
+
[width, itemSpacing, isZoomed, isRotated, onMomentumScrollEnd, onScrollBeginDrag, enableSnapMode],
|
|
134
134
|
);
|
|
135
135
|
|
|
136
136
|
const listComponent = (
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { type SharedValue, withTiming } from 'react-native-reanimated';
|
|
2
2
|
import type {
|
|
3
|
-
GestureViewerControllerState,
|
|
4
3
|
GestureViewerEventCallback,
|
|
5
4
|
GestureViewerEventData,
|
|
6
5
|
GestureViewerEventType,
|
|
6
|
+
GestureViewerState,
|
|
7
7
|
} from './types';
|
|
8
8
|
import { createBoundsConstraint, createScrollAction } from './utils';
|
|
9
9
|
|
|
@@ -13,7 +13,7 @@ class GestureViewerManager {
|
|
|
13
13
|
private width = 0;
|
|
14
14
|
private height = 0;
|
|
15
15
|
private maxZoomScale = 2;
|
|
16
|
-
private
|
|
16
|
+
private enableHorizontalSwipe = true;
|
|
17
17
|
private enableLoop = false;
|
|
18
18
|
private listRef: any | null = null;
|
|
19
19
|
|
|
@@ -24,7 +24,7 @@ class GestureViewerManager {
|
|
|
24
24
|
|
|
25
25
|
private loopCallback: (() => void) | null = null;
|
|
26
26
|
|
|
27
|
-
private listeners = new Set<(state:
|
|
27
|
+
private listeners = new Set<(state: GestureViewerState) => void>();
|
|
28
28
|
private eventListeners = new Map<GestureViewerEventType, Set<(data: any) => void>>();
|
|
29
29
|
|
|
30
30
|
private notifyListeners() {
|
|
@@ -33,7 +33,7 @@ class GestureViewerManager {
|
|
|
33
33
|
this.listeners.forEach((listener) => listener(state));
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
subscribe(listener: (state:
|
|
36
|
+
subscribe(listener: (state: GestureViewerState) => void) {
|
|
37
37
|
this.listeners.add(listener);
|
|
38
38
|
|
|
39
39
|
return () => {
|
|
@@ -77,7 +77,7 @@ class GestureViewerManager {
|
|
|
77
77
|
this.emitEvent('rotationChange', { rotation, previousRotation });
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
-
getState() {
|
|
80
|
+
getState(): GestureViewerState {
|
|
81
81
|
return {
|
|
82
82
|
currentIndex: this.currentIndex,
|
|
83
83
|
totalCount: this.dataLength,
|
|
@@ -104,8 +104,8 @@ class GestureViewerManager {
|
|
|
104
104
|
this.dataLength = length;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
this.
|
|
107
|
+
setEnableHorizontalSwipe(enabled: boolean) {
|
|
108
|
+
this.enableHorizontalSwipe = enabled;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
setCurrentIndex(index: number) {
|
|
@@ -224,7 +224,7 @@ class GestureViewerManager {
|
|
|
224
224
|
};
|
|
225
225
|
|
|
226
226
|
goToIndex = (index: number) => {
|
|
227
|
-
if (!this.
|
|
227
|
+
if (!this.enableHorizontalSwipe || !this.listRef) {
|
|
228
228
|
return;
|
|
229
229
|
}
|
|
230
230
|
|
|
@@ -299,7 +299,7 @@ class GestureViewerManager {
|
|
|
299
299
|
this.loopCallback = null;
|
|
300
300
|
this.listeners.clear();
|
|
301
301
|
this.listRef = null;
|
|
302
|
-
this.
|
|
302
|
+
this.enableHorizontalSwipe = true;
|
|
303
303
|
this.currentIndex = 0;
|
|
304
304
|
this.dataLength = 0;
|
|
305
305
|
this.maxZoomScale = 2;
|
package/src/index.tsx
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
export { GestureViewer } from './GestureViewer';
|
|
2
2
|
export type {
|
|
3
3
|
GestureViewerController,
|
|
4
|
-
GestureViewerControllerState,
|
|
5
4
|
GestureViewerEventCallback,
|
|
6
5
|
GestureViewerEventData,
|
|
7
6
|
GestureViewerEventType,
|
|
8
7
|
GestureViewerProps,
|
|
8
|
+
GestureViewerState,
|
|
9
9
|
} from './types';
|
|
10
10
|
export { useGestureViewerController } from './useGestureViewerController';
|
|
11
11
|
export { useGestureViewerEvent } from './useGestureViewerEvent';
|
|
12
|
+
export { useGestureViewerState } from './useGestureViewerState';
|
package/src/types.ts
CHANGED
|
@@ -13,7 +13,7 @@ type ConditionalListProps<LC> = LC extends FlatListComponent
|
|
|
13
13
|
? React.ComponentProps<LC>
|
|
14
14
|
: GetComponentProps<LC>;
|
|
15
15
|
|
|
16
|
-
export interface GestureViewerProps<T = any, LC = typeof
|
|
16
|
+
export interface GestureViewerProps<T = any, LC = typeof RNScrollView> {
|
|
17
17
|
/**
|
|
18
18
|
* When you want to efficiently manage multiple `GestureViewer` instances, you can use the `id` prop to use multiple `GestureViewer` components.
|
|
19
19
|
* @remark `GestureViewer` automatically removes instances from memory when components are unmounted, so no manual memory management is required.
|
|
@@ -29,10 +29,6 @@ export interface GestureViewerProps<T = any, LC = typeof RNFlatList> {
|
|
|
29
29
|
* @defaultValue 0
|
|
30
30
|
*/
|
|
31
31
|
initialIndex?: number;
|
|
32
|
-
/**
|
|
33
|
-
* A callback function that is called when the index of the item changes.
|
|
34
|
-
*/
|
|
35
|
-
onIndexChange?: (index: number) => void;
|
|
36
32
|
/**
|
|
37
33
|
* A callback function that is called when the `GestureViewer` is dismissed.
|
|
38
34
|
*/
|
|
@@ -55,44 +51,6 @@ export interface GestureViewerProps<T = any, LC = typeof RNFlatList> {
|
|
|
55
51
|
* @defaultValue screen width
|
|
56
52
|
*/
|
|
57
53
|
width?: number;
|
|
58
|
-
/**
|
|
59
|
-
* Enables snap scrolling mode.
|
|
60
|
-
*
|
|
61
|
-
* @remark
|
|
62
|
-
* **`false` (default)**: Paging mode (`pagingEnabled: true`)
|
|
63
|
-
* - Scrolls by full screen size increments
|
|
64
|
-
*
|
|
65
|
-
* **`true`**: Snap mode (`snapToInterval` auto-calculated)
|
|
66
|
-
* - `snapToInterval` is automatically calculated based on `width` and `itemSpacing` values
|
|
67
|
-
* - Use this option when you need item spacing
|
|
68
|
-
* @defaultValue false
|
|
69
|
-
*
|
|
70
|
-
*/
|
|
71
|
-
useSnap?: boolean;
|
|
72
|
-
/**
|
|
73
|
-
* `dismissThreshold` controls when `onDismiss` is called by applying a threshold value during vertical gestures.
|
|
74
|
-
* @defaultValue 80
|
|
75
|
-
*/
|
|
76
|
-
dismissThreshold?: number;
|
|
77
|
-
// swipeThreshold?: number;
|
|
78
|
-
// velocityThreshold?: number;
|
|
79
|
-
/**
|
|
80
|
-
* Calls `onDismiss` function when swiping down.
|
|
81
|
-
* @remark Useful for closing modals with downward swipe gestures.
|
|
82
|
-
* @defaultValue true
|
|
83
|
-
*/
|
|
84
|
-
enableDismissGesture?: boolean;
|
|
85
|
-
/**
|
|
86
|
-
* Controls left/right swipe gestures.
|
|
87
|
-
* @remark When `false`, horizontal gestures are disabled.
|
|
88
|
-
* @defaultValue true
|
|
89
|
-
*/
|
|
90
|
-
enableSwipeGesture?: boolean;
|
|
91
|
-
/**
|
|
92
|
-
* `resistance` controls the range of vertical movement by applying resistance during vertical gestures.
|
|
93
|
-
* @defaultValue 2
|
|
94
|
-
*/
|
|
95
|
-
resistance?: number;
|
|
96
54
|
/**
|
|
97
55
|
* The props to pass to the list component.
|
|
98
56
|
* @remark The `listProps` provides **type inference based on the selected list component**, ensuring accurate autocompletion and type safety in your IDE.
|
|
@@ -107,45 +65,86 @@ export interface GestureViewerProps<T = any, LC = typeof RNFlatList> {
|
|
|
107
65
|
*/
|
|
108
66
|
containerStyle?: StyleProp<ViewStyle>;
|
|
109
67
|
/**
|
|
110
|
-
*
|
|
111
|
-
* @remark
|
|
68
|
+
* Dismiss gesture options. Calls `onDismiss` function when swiping down.
|
|
69
|
+
* @remark Useful for closing modals with downward swipe gestures.
|
|
70
|
+
*/
|
|
71
|
+
dismiss?: {
|
|
72
|
+
/**
|
|
73
|
+
* When `false`, dismiss gesture is disabled.
|
|
74
|
+
* @defaultValue true
|
|
75
|
+
*/
|
|
76
|
+
enabled?: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* `threshold` controls when `onDismiss` is called by applying a threshold value during vertical gestures.
|
|
79
|
+
* @defaultValue 80
|
|
80
|
+
*/
|
|
81
|
+
threshold?: number;
|
|
82
|
+
/**
|
|
83
|
+
* `resistance` controls the range of vertical movement by applying resistance during dismiss gestures.
|
|
84
|
+
* @defaultValue 2
|
|
85
|
+
*/
|
|
86
|
+
resistance?: number;
|
|
87
|
+
/**
|
|
88
|
+
* By default, the background `opacity` gradually decreases from 1 to 0 during downward swipe gestures.
|
|
89
|
+
* @remark When `false`, this animation is disabled.
|
|
90
|
+
* @defaultValue true
|
|
91
|
+
*/
|
|
92
|
+
fadeBackdrop?: boolean;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Controls left/right swipe gestures.
|
|
96
|
+
* @remark When `false`, horizontal gestures are disabled.
|
|
112
97
|
* @defaultValue true
|
|
113
98
|
*/
|
|
114
|
-
|
|
99
|
+
enableHorizontalSwipe?: boolean;
|
|
115
100
|
/**
|
|
116
101
|
* Only works when zoom is active, allows moving item position when zoomed.
|
|
117
102
|
* @remark When `false`, gesture movement is disabled during zoom.
|
|
118
103
|
* @defaultValue true
|
|
119
104
|
*/
|
|
120
|
-
|
|
105
|
+
enablePanWhenZoomed?: boolean;
|
|
121
106
|
/**
|
|
122
107
|
* Controls two-finger pinch gestures.
|
|
123
108
|
* @remark When `false`, two-finger zoom gestures are disabled.
|
|
124
109
|
* @defaultValue true
|
|
125
110
|
*/
|
|
126
|
-
|
|
111
|
+
enablePinchZoom?: boolean;
|
|
127
112
|
/**
|
|
128
113
|
* Controls double-tap zoom gestures.
|
|
129
114
|
* @remark When `false`, double-tap zoom gestures are disabled.
|
|
130
115
|
* @defaultValue true
|
|
131
116
|
*/
|
|
132
|
-
|
|
117
|
+
enableDoubleTapZoom?: boolean;
|
|
133
118
|
/**
|
|
134
119
|
* Enables infinite loop navigation.
|
|
135
120
|
* @defaultValue false
|
|
136
121
|
*/
|
|
137
122
|
enableLoop?: boolean;
|
|
138
123
|
/**
|
|
139
|
-
*
|
|
140
|
-
*
|
|
124
|
+
* Enables snap scrolling mode.
|
|
125
|
+
*
|
|
126
|
+
* @remark
|
|
127
|
+
* **`false` (default)**: Paging mode (`pagingEnabled: true`)
|
|
128
|
+
* - Scrolls by full screen size increments
|
|
129
|
+
*
|
|
130
|
+
* **`true`**: Snap mode (`snapToInterval` auto-calculated)
|
|
131
|
+
* - `snapToInterval` is automatically calculated based on `width` and `itemSpacing` values
|
|
132
|
+
* - Use this option when you need item spacing
|
|
133
|
+
* @defaultValue false
|
|
134
|
+
*
|
|
141
135
|
*/
|
|
142
|
-
|
|
136
|
+
enableSnapMode?: boolean;
|
|
143
137
|
/**
|
|
144
138
|
* The spacing between items in pixels.
|
|
145
|
-
* @remark Only applied when `
|
|
139
|
+
* @remark Only applied when `enableSnapMode` is `true`.
|
|
146
140
|
* @defaultValue 0
|
|
147
141
|
*/
|
|
148
142
|
itemSpacing?: number;
|
|
143
|
+
/**
|
|
144
|
+
* The maximum zoom scale.
|
|
145
|
+
* @defaultValue 2
|
|
146
|
+
*/
|
|
147
|
+
maxZoomScale?: number;
|
|
149
148
|
}
|
|
150
149
|
|
|
151
150
|
/**
|
|
@@ -167,8 +166,10 @@ export type GestureViewerController = {
|
|
|
167
166
|
*
|
|
168
167
|
* @example
|
|
169
168
|
* ```typescript
|
|
169
|
+
* const { totalCount } = useGestureViewerState();
|
|
170
|
+
*
|
|
170
171
|
* controller.goToIndex(0); // Go to first item
|
|
171
|
-
* controller.goToIndex(
|
|
172
|
+
* controller.goToIndex(totalCount - 1); // Go to last item
|
|
172
173
|
* ```
|
|
173
174
|
*/
|
|
174
175
|
goToIndex: (index: number) => void;
|
|
@@ -252,13 +253,13 @@ export type GestureViewerController = {
|
|
|
252
253
|
* ```
|
|
253
254
|
*/
|
|
254
255
|
rotate: (angle?: RotationAngle, clockwise?: boolean) => void;
|
|
255
|
-
}
|
|
256
|
+
};
|
|
256
257
|
|
|
257
258
|
/**
|
|
258
259
|
* State information for the gesture viewer controller.
|
|
259
260
|
* Contains read-only properties that reflect the current state.
|
|
260
261
|
*/
|
|
261
|
-
export type
|
|
262
|
+
export type GestureViewerState = {
|
|
262
263
|
/**
|
|
263
264
|
* The current index of the active item in the viewer.
|
|
264
265
|
*
|
|
@@ -267,7 +268,7 @@ export type GestureViewerControllerState = {
|
|
|
267
268
|
*
|
|
268
269
|
* @example
|
|
269
270
|
* ```typescript
|
|
270
|
-
* console.log(`Currently viewing item ${
|
|
271
|
+
* console.log(`Currently viewing item ${currentIndex + 1} of ${totalCount}`);
|
|
271
272
|
* ```
|
|
272
273
|
*/
|
|
273
274
|
readonly currentIndex: number;
|
|
@@ -280,8 +281,8 @@ export type GestureViewerControllerState = {
|
|
|
280
281
|
*
|
|
281
282
|
* @example
|
|
282
283
|
* ```typescript
|
|
283
|
-
* const hasNext =
|
|
284
|
-
* const hasPrevious =
|
|
284
|
+
* const hasNext = currentIndex < totalCount - 1;
|
|
285
|
+
* const hasPrevious = currentIndex > 0;
|
|
285
286
|
* ```
|
|
286
287
|
*/
|
|
287
288
|
readonly totalCount: number;
|
package/src/useGestureViewer.ts
CHANGED
|
@@ -29,36 +29,27 @@ type UseGestureViewerProps<T = any> = Omit<
|
|
|
29
29
|
export const useGestureViewer = <T = any>({
|
|
30
30
|
data,
|
|
31
31
|
initialIndex = 0,
|
|
32
|
-
onIndexChange,
|
|
33
32
|
onDismiss,
|
|
34
33
|
width: customWidth,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
enableDismissGesture = true,
|
|
41
|
-
enableSwipeGesture = true,
|
|
42
|
-
enableZoomGesture = true,
|
|
43
|
-
enableDoubleTapGesture = true,
|
|
44
|
-
enableZoomPanGesture = true,
|
|
34
|
+
dismiss,
|
|
35
|
+
enableDoubleTapZoom = true,
|
|
36
|
+
enablePinchZoom = true,
|
|
37
|
+
enableHorizontalSwipe = true,
|
|
38
|
+
enablePanWhenZoomed = true,
|
|
45
39
|
enableLoop = false,
|
|
46
40
|
maxZoomScale = 2,
|
|
47
41
|
itemSpacing = 0,
|
|
48
|
-
|
|
42
|
+
enableSnapMode = false,
|
|
49
43
|
id = 'default',
|
|
50
44
|
}: UseGestureViewerProps<T>) => {
|
|
51
45
|
const { width: screenWidth, height: screenHeight } = useWindowDimensions();
|
|
52
|
-
const width =
|
|
46
|
+
const width = enableSnapMode ? customWidth || screenWidth : screenWidth;
|
|
53
47
|
|
|
54
48
|
const [isZoomed, setIsZoomed] = useState(false);
|
|
55
49
|
const [isRotated, setIsRotated] = useState(false);
|
|
56
50
|
const [manager, setManager] = useState<GestureViewerManager | null>(null);
|
|
57
51
|
|
|
58
52
|
const listRef = useRef<any>(null);
|
|
59
|
-
const unsubscribeRef = useRef<(() => void) | null>(null);
|
|
60
|
-
const onIndexChangeRef = useRef<((index: number) => void) | null>(null);
|
|
61
|
-
const lastEmittedIndexRef = useRef<number>(null);
|
|
62
53
|
|
|
63
54
|
const initialTranslateY = useSharedValue(0);
|
|
64
55
|
const initialTranslateX = useSharedValue(0);
|
|
@@ -72,6 +63,15 @@ export const useGestureViewer = <T = any>({
|
|
|
72
63
|
|
|
73
64
|
const dataLength = data?.length || 0;
|
|
74
65
|
|
|
66
|
+
const dismissOptions = useMemo(() => {
|
|
67
|
+
return {
|
|
68
|
+
enabled: dismiss?.enabled ?? true,
|
|
69
|
+
threshold: dismiss?.threshold ?? 80,
|
|
70
|
+
resistance: dismiss?.resistance ?? 2,
|
|
71
|
+
fadeBackdrop: dismiss?.fadeBackdrop ?? true,
|
|
72
|
+
};
|
|
73
|
+
}, [dismiss?.enabled, dismiss?.threshold, dismiss?.resistance, dismiss?.fadeBackdrop]);
|
|
74
|
+
|
|
75
75
|
const adjustedInitialIndex = useMemo(() => {
|
|
76
76
|
if (enableLoop && data.length > 1) {
|
|
77
77
|
return initialIndex + 1;
|
|
@@ -96,14 +96,18 @@ export const useGestureViewer = <T = any>({
|
|
|
96
96
|
|
|
97
97
|
const emitZoomChange = useCallback(
|
|
98
98
|
(currentScale: number, prevScale: number | null) => {
|
|
99
|
-
manager
|
|
99
|
+
if (manager) {
|
|
100
|
+
manager.emitZoomChange(currentScale, prevScale);
|
|
101
|
+
}
|
|
100
102
|
},
|
|
101
103
|
[manager],
|
|
102
104
|
);
|
|
103
105
|
|
|
104
106
|
const emitRotationChange = useCallback(
|
|
105
107
|
(currentRotation: number, prevRotation: number | null) => {
|
|
106
|
-
manager
|
|
108
|
+
if (manager) {
|
|
109
|
+
manager.emitRotationChange(currentRotation, prevRotation);
|
|
110
|
+
}
|
|
107
111
|
},
|
|
108
112
|
[manager],
|
|
109
113
|
);
|
|
@@ -111,7 +115,7 @@ export const useGestureViewer = <T = any>({
|
|
|
111
115
|
useAnimatedReaction(
|
|
112
116
|
() => scale.value,
|
|
113
117
|
(currentScale, previousScale) => {
|
|
114
|
-
if (
|
|
118
|
+
if (currentScale !== previousScale) {
|
|
115
119
|
runOnJS(emitZoomChange)(currentScale, previousScale);
|
|
116
120
|
}
|
|
117
121
|
|
|
@@ -122,7 +126,7 @@ export const useGestureViewer = <T = any>({
|
|
|
122
126
|
useAnimatedReaction(
|
|
123
127
|
() => rotation.value,
|
|
124
128
|
(currentRotation, previousRotation) => {
|
|
125
|
-
if (
|
|
129
|
+
if (currentRotation !== previousRotation) {
|
|
126
130
|
runOnJS(emitRotationChange)(currentRotation, previousRotation);
|
|
127
131
|
}
|
|
128
132
|
|
|
@@ -131,41 +135,9 @@ export const useGestureViewer = <T = any>({
|
|
|
131
135
|
);
|
|
132
136
|
|
|
133
137
|
useEffect(() => {
|
|
134
|
-
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
useEffect(() => {
|
|
138
|
-
return () => {
|
|
139
|
-
onIndexChangeRef.current = null;
|
|
140
|
-
};
|
|
141
|
-
}, []);
|
|
138
|
+
const unsubscribe = registry.subscribeToManager(id, setManager);
|
|
142
139
|
|
|
143
|
-
|
|
144
|
-
const handleManagerChange = (manager: GestureViewerManager | null) => {
|
|
145
|
-
unsubscribeRef.current?.();
|
|
146
|
-
unsubscribeRef.current = null;
|
|
147
|
-
|
|
148
|
-
setManager(manager);
|
|
149
|
-
|
|
150
|
-
lastEmittedIndexRef.current = null;
|
|
151
|
-
|
|
152
|
-
if (manager) {
|
|
153
|
-
unsubscribeRef.current = manager.subscribe((state) => {
|
|
154
|
-
if (lastEmittedIndexRef.current !== state.currentIndex) {
|
|
155
|
-
lastEmittedIndexRef.current = state.currentIndex;
|
|
156
|
-
onIndexChangeRef.current?.(state.currentIndex);
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
const unsubscribeFromRegistry = registry.subscribeToManager(id, handleManagerChange);
|
|
164
|
-
|
|
165
|
-
return () => {
|
|
166
|
-
unsubscribeFromRegistry();
|
|
167
|
-
unsubscribeRef.current?.();
|
|
168
|
-
};
|
|
140
|
+
return unsubscribe;
|
|
169
141
|
}, [id]);
|
|
170
142
|
|
|
171
143
|
useEffect(() => {
|
|
@@ -174,7 +146,7 @@ export const useGestureViewer = <T = any>({
|
|
|
174
146
|
}
|
|
175
147
|
|
|
176
148
|
manager.setDataLength(dataLength);
|
|
177
|
-
manager.
|
|
149
|
+
manager.setEnableHorizontalSwipe(enableHorizontalSwipe);
|
|
178
150
|
manager.setCurrentIndex(initialIndex);
|
|
179
151
|
manager.setWidth(width + itemSpacing);
|
|
180
152
|
manager.setHeight(screenHeight);
|
|
@@ -184,7 +156,7 @@ export const useGestureViewer = <T = any>({
|
|
|
184
156
|
manager.notifyStateChange();
|
|
185
157
|
}, [
|
|
186
158
|
dataLength,
|
|
187
|
-
|
|
159
|
+
enableHorizontalSwipe,
|
|
188
160
|
initialIndex,
|
|
189
161
|
manager,
|
|
190
162
|
width,
|
|
@@ -229,7 +201,7 @@ export const useGestureViewer = <T = any>({
|
|
|
229
201
|
|
|
230
202
|
const onMomentumScrollEnd = useCallback(
|
|
231
203
|
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
232
|
-
if (!
|
|
204
|
+
if (!enableHorizontalSwipe) {
|
|
233
205
|
return;
|
|
234
206
|
}
|
|
235
207
|
|
|
@@ -271,7 +243,7 @@ export const useGestureViewer = <T = any>({
|
|
|
271
243
|
dataLength,
|
|
272
244
|
width,
|
|
273
245
|
itemSpacing,
|
|
274
|
-
|
|
246
|
+
enableHorizontalSwipe,
|
|
275
247
|
enableLoop,
|
|
276
248
|
translateX,
|
|
277
249
|
translateY,
|
|
@@ -292,10 +264,10 @@ export const useGestureViewer = <T = any>({
|
|
|
292
264
|
.failOffsetX([-10, 10])
|
|
293
265
|
.enabled(!isZoomed)
|
|
294
266
|
.onUpdate((event) => {
|
|
295
|
-
translateY.value = event.translationY / resistance;
|
|
267
|
+
translateY.value = event.translationY / dismissOptions.resistance;
|
|
296
268
|
})
|
|
297
269
|
.onEnd((event) => {
|
|
298
|
-
if (event.translationY >
|
|
270
|
+
if (event.translationY > dismissOptions.threshold && dismissOptions.enabled && onDismiss) {
|
|
299
271
|
runOnJS(onDismiss)();
|
|
300
272
|
return;
|
|
301
273
|
}
|
|
@@ -308,11 +280,11 @@ export const useGestureViewer = <T = any>({
|
|
|
308
280
|
energyThreshold: 6e-9,
|
|
309
281
|
});
|
|
310
282
|
});
|
|
311
|
-
}, [translateY,
|
|
283
|
+
}, [translateY, dismissOptions.threshold, dismissOptions.enabled, onDismiss, dismissOptions.resistance, isZoomed]);
|
|
312
284
|
|
|
313
285
|
const zoomPinchGesture = useMemo(() => {
|
|
314
286
|
return Gesture.Pinch()
|
|
315
|
-
.enabled(
|
|
287
|
+
.enabled(enablePinchZoom)
|
|
316
288
|
.onBegin(() => {
|
|
317
289
|
startScale.value = scale.value;
|
|
318
290
|
initialTranslateX.value = translateX.value;
|
|
@@ -388,7 +360,7 @@ export const useGestureViewer = <T = any>({
|
|
|
388
360
|
});
|
|
389
361
|
}, [
|
|
390
362
|
scale,
|
|
391
|
-
|
|
363
|
+
enablePinchZoom,
|
|
392
364
|
maxZoomScale,
|
|
393
365
|
translateX,
|
|
394
366
|
translateY,
|
|
@@ -402,7 +374,7 @@ export const useGestureViewer = <T = any>({
|
|
|
402
374
|
|
|
403
375
|
const zoomPanGesture = useMemo(() => {
|
|
404
376
|
return Gesture.Pan()
|
|
405
|
-
.enabled(
|
|
377
|
+
.enabled(enablePanWhenZoomed && isZoomed)
|
|
406
378
|
.activeCursor('grabbing')
|
|
407
379
|
.averageTouches(true)
|
|
408
380
|
.onBegin(() => {
|
|
@@ -427,7 +399,7 @@ export const useGestureViewer = <T = any>({
|
|
|
427
399
|
}, [
|
|
428
400
|
translateX,
|
|
429
401
|
translateY,
|
|
430
|
-
|
|
402
|
+
enablePanWhenZoomed,
|
|
431
403
|
isZoomed,
|
|
432
404
|
scale,
|
|
433
405
|
initialTranslateX,
|
|
@@ -437,7 +409,7 @@ export const useGestureViewer = <T = any>({
|
|
|
437
409
|
|
|
438
410
|
const doubleTapGesture = useMemo(() => {
|
|
439
411
|
return Gesture.Tap()
|
|
440
|
-
.enabled(
|
|
412
|
+
.enabled(enableDoubleTapZoom)
|
|
441
413
|
.numberOfTaps(2)
|
|
442
414
|
.onEnd((event) => {
|
|
443
415
|
const nextScale = scale.value > 1 ? 1 : maxZoomScale;
|
|
@@ -471,7 +443,7 @@ export const useGestureViewer = <T = any>({
|
|
|
471
443
|
easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
|
|
472
444
|
});
|
|
473
445
|
});
|
|
474
|
-
}, [scale,
|
|
446
|
+
}, [scale, enableDoubleTapZoom, maxZoomScale, translateX, translateY, width, screenHeight]);
|
|
475
447
|
|
|
476
448
|
const zoomGesture = useMemo(() => {
|
|
477
449
|
return Gesture.Race(zoomPinchGesture, Gesture.Exclusive(zoomPanGesture, doubleTapGesture));
|
|
@@ -489,14 +461,14 @@ export const useGestureViewer = <T = any>({
|
|
|
489
461
|
});
|
|
490
462
|
|
|
491
463
|
const backdropStyle = useAnimatedStyle(() => {
|
|
492
|
-
if (!
|
|
464
|
+
if (!dismissOptions.fadeBackdrop || scale.value !== 1) {
|
|
493
465
|
return { opacity: 1 };
|
|
494
466
|
}
|
|
495
467
|
|
|
496
468
|
const opacity = interpolate(translateY.value, [0, 200], [1, 0], 'clamp');
|
|
497
469
|
|
|
498
470
|
return { opacity };
|
|
499
|
-
}, [
|
|
471
|
+
}, [dismissOptions.fadeBackdrop]);
|
|
500
472
|
|
|
501
473
|
const onScrollBeginDrag = useCallback(() => {
|
|
502
474
|
manager?.handleScrollBeginDrag();
|