react-native-gesture-image-viewer 0.5.1 → 0.5.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-gesture-image-viewer",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
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",
@@ -13,7 +13,6 @@
13
13
  "./package.json": "./package.json"
14
14
  },
15
15
  "files": [
16
- "src",
17
16
  "lib",
18
17
  "android",
19
18
  "ios",
@@ -1,165 +0,0 @@
1
- import { useCallback, useEffect, useMemo } from 'react';
2
- import { type FlatList, Platform, type ScrollViewProps, StyleSheet, useWindowDimensions, View } from 'react-native';
3
- import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler';
4
- import Animated from 'react-native-reanimated';
5
- import { registry } from './ImageViewerRegistry';
6
- import type { GestureImageViewerProps } from './types';
7
- import { useGestureImageViewer } from './useGestureImageViewer';
8
- import { isFlashListLike, isFlatListLike, isScrollViewLike } from './utils';
9
-
10
- const WebPagingFix = () => {
11
- if (Platform.OS !== 'web') {
12
- return null;
13
- }
14
-
15
- return <style>{`[data-paging-enabled-fix] > div > div > div {height: 100%;}`}</style>;
16
- };
17
-
18
- export function GestureImageViewer<T = any, LC = typeof FlatList>({
19
- id = 'default',
20
- data,
21
- renderImage,
22
- renderContainer,
23
- ListComponent,
24
- width: customWidth,
25
- listProps,
26
- backdropStyle: backdropStyleProps,
27
- containerStyle,
28
- initialIndex = 0,
29
- itemSpacing = 0,
30
- ...props
31
- }: GestureImageViewerProps<T, LC>) {
32
- const Component = ListComponent as React.ComponentType<any>;
33
-
34
- const { width: screenWidth } = useWindowDimensions();
35
-
36
- const width = customWidth || screenWidth;
37
-
38
- const { listRef, isZoomed, dismissGesture, zoomGesture, onMomentumScrollEnd, animatedStyle, backdropStyle } =
39
- useGestureImageViewer({
40
- id,
41
- data,
42
- width,
43
- initialIndex,
44
- itemSpacing,
45
- ...props,
46
- });
47
-
48
- const renderItem = useCallback(
49
- ({ item, index }: { item: T; index: number }) => {
50
- return (
51
- <View
52
- key={typeof item === 'string' ? item : index}
53
- style={[
54
- {
55
- width: width,
56
- height: '100%',
57
- justifyContent: 'center',
58
- alignItems: 'center',
59
- marginHorizontal: itemSpacing / 2,
60
- },
61
- ]}
62
- >
63
- {renderImage(item, index)}
64
- </View>
65
- );
66
- },
67
- [width, itemSpacing, renderImage],
68
- );
69
-
70
- const getItemLayout = useCallback(
71
- (_: ArrayLike<T> | null | undefined, index: number) => ({
72
- length: width + itemSpacing,
73
- offset: (width + itemSpacing) * index,
74
- index,
75
- }),
76
- [width, itemSpacing],
77
- );
78
-
79
- const keyExtractor = useCallback(
80
- (item: T, index: number) => (typeof item === 'string' ? item : `image-${index}`),
81
- [],
82
- );
83
-
84
- const gesture = useMemo(() => {
85
- return Gesture.Race(dismissGesture, zoomGesture);
86
- }, [zoomGesture, dismissGesture]);
87
-
88
- useEffect(() => {
89
- registry.createManager(id);
90
-
91
- return () => registry.deleteManager(id);
92
- }, [id]);
93
-
94
- const commonProps: ScrollViewProps = useMemo(
95
- () => ({
96
- horizontal: true,
97
- scrollEnabled: !isZoomed,
98
- showsHorizontalScrollIndicator: false,
99
- onMomentumScrollEnd: onMomentumScrollEnd,
100
- snapToInterval: width + itemSpacing,
101
- snapToAlignment: 'center',
102
- decelerationRate: 'fast',
103
- scrollEventThrottle: 16,
104
- removeClippedSubviews: true,
105
- }),
106
- [width, itemSpacing, isZoomed, onMomentumScrollEnd],
107
- );
108
-
109
- const listComponent = (
110
- <GestureHandlerRootView>
111
- <GestureDetector gesture={gesture}>
112
- <View style={[styles.container, containerStyle]}>
113
- <Animated.View style={[styles.background, backdropStyleProps, backdropStyle]} />
114
- <Animated.View style={[styles.content, animatedStyle]}>
115
- {isScrollViewLike(Component) ? (
116
- <Component ref={listRef} {...commonProps} {...listProps}>
117
- {data.map((item, index) => renderItem({ item, index }))}
118
- </Component>
119
- ) : (
120
- isFlatListLike(Component) && (
121
- <Component
122
- ref={listRef}
123
- {...commonProps}
124
- data={data}
125
- renderItem={renderItem}
126
- initialScrollIndex={initialIndex}
127
- keyExtractor={keyExtractor}
128
- windowSize={3}
129
- maxToRenderPerBatch={3}
130
- getItemLayout={getItemLayout}
131
- {...(isFlashListLike(Component) && { estimatedItemSize: width + itemSpacing })}
132
- // NOTE - https://github.com/necolas/react-native-web/issues/1299
133
- {...(Platform.OS === 'web' && { dataSet: { 'paging-enabled-fix': true } })}
134
- {...listProps}
135
- />
136
- )
137
- )}
138
- </Animated.View>
139
- <WebPagingFix />
140
- </View>
141
- </GestureDetector>
142
- </GestureHandlerRootView>
143
- );
144
-
145
- return renderContainer ? renderContainer(listComponent) : listComponent;
146
- }
147
-
148
- const styles = StyleSheet.create({
149
- container: {
150
- flex: 1,
151
- },
152
- content: {
153
- flex: 1,
154
- width: '100%',
155
- height: '100%',
156
- },
157
- background: {
158
- position: 'absolute',
159
- top: 0,
160
- left: 0,
161
- right: 0,
162
- bottom: 0,
163
- backgroundColor: 'black',
164
- },
165
- });
@@ -1,103 +0,0 @@
1
- export type ImageViewerManagerState = {
2
- currentIndex: number;
3
- dataLength: number;
4
- };
5
-
6
- class ImageViewerManager {
7
- private currentIndex = 0;
8
- private dataLength = 0;
9
- private width = 0;
10
- private listRef: any | null = null;
11
- private enableSwipeGesture = true;
12
- private listeners = new Set<(state: ImageViewerManagerState) => void>();
13
-
14
- // private updateState(newState: Partial<any>) {
15
- // Object.assign(this, newState);
16
- // this.notifyListeners();
17
- // }
18
-
19
- private notifyListeners() {
20
- const state = this.getState();
21
-
22
- this.listeners.forEach((listener) => listener(state));
23
- }
24
-
25
- subscribe(listener: (state: ImageViewerManagerState) => void) {
26
- this.listeners.add(listener);
27
-
28
- return () => {
29
- this.listeners.delete(listener);
30
- };
31
- }
32
-
33
- getState() {
34
- return {
35
- currentIndex: this.currentIndex,
36
- dataLength: this.dataLength,
37
- };
38
- }
39
-
40
- setWidth(width: number) {
41
- this.width = width;
42
- }
43
-
44
- setListRef(ref: any) {
45
- this.listRef = ref;
46
- }
47
-
48
- setDataLength(length: number) {
49
- this.dataLength = length;
50
- }
51
-
52
- setEnableSwipeGesture(enabled: boolean) {
53
- this.enableSwipeGesture = enabled;
54
- }
55
-
56
- setCurrentIndex(index: number) {
57
- if (index !== this.currentIndex) {
58
- this.currentIndex = index;
59
- }
60
- }
61
-
62
- notifyStateChange() {
63
- this.notifyListeners();
64
- }
65
-
66
- goToIndex = (index: number) => {
67
- if (index < 0 || index >= this.dataLength || !this.enableSwipeGesture || !this.listRef) {
68
- return;
69
- }
70
-
71
- this.currentIndex = index;
72
-
73
- if (this.listRef.scrollToIndex) {
74
- this.listRef.scrollToIndex({ index, animated: true });
75
- } else if (this.listRef.scrollTo) {
76
- this.listRef.scrollTo({ x: index * this.width, animated: true });
77
- }
78
-
79
- this.notifyListeners();
80
- };
81
-
82
- goToPrevious = () => {
83
- if (this.currentIndex > 0) {
84
- this.goToIndex(this.currentIndex - 1);
85
- }
86
- };
87
-
88
- goToNext = () => {
89
- if (this.currentIndex < this.dataLength - 1) {
90
- this.goToIndex(this.currentIndex + 1);
91
- }
92
- };
93
-
94
- cleanUp() {
95
- this.listeners.clear();
96
- this.listRef = null;
97
- this.enableSwipeGesture = true;
98
- this.currentIndex = 0;
99
- this.dataLength = 0;
100
- }
101
- }
102
-
103
- export default ImageViewerManager;
@@ -1,66 +0,0 @@
1
- import ImageViewerManager from './ImageViewerManager';
2
-
3
- class ImageViewerRegistry {
4
- private managers = new Map<string, ImageViewerManager>();
5
- private subscribers = new Map<string, Set<(manager: ImageViewerManager | null) => void>>();
6
-
7
- subscribeToManager(id: string, callback: (manager: ImageViewerManager | null) => void) {
8
- if (!this.subscribers.has(id)) {
9
- this.subscribers.set(id, new Set());
10
- }
11
-
12
- this.subscribers.get(id)?.add(callback);
13
-
14
- const manager = this.managers.get(id) || null;
15
-
16
- callback(manager);
17
-
18
- return () => {
19
- const subscribers = this.subscribers.get(id);
20
-
21
- subscribers?.delete(callback);
22
-
23
- if (subscribers && subscribers.size === 0) {
24
- this.subscribers.delete(id);
25
- }
26
- };
27
- }
28
-
29
- createManager(id: string): ImageViewerManager | null {
30
- if (this.managers.has(id)) {
31
- return this.managers.get(id) || null;
32
- }
33
-
34
- const manager = new ImageViewerManager();
35
- this.managers.set(id, manager);
36
-
37
- this.notifySubscribers(id, manager);
38
-
39
- return manager;
40
- }
41
-
42
- getManager(id: string): ImageViewerManager | null {
43
- return this.managers.get(id) || null;
44
- }
45
-
46
- deleteManager(id: string) {
47
- const manager = this.managers.get(id);
48
-
49
- if (manager) {
50
- manager.cleanUp();
51
- this.managers.delete(id);
52
-
53
- this.notifySubscribers(id, null);
54
- }
55
- }
56
-
57
- notifySubscribers(id: string, manager: ImageViewerManager | null) {
58
- const listeners = this.subscribers.get(id);
59
-
60
- if (listeners) {
61
- [...listeners].forEach((callback) => callback(manager));
62
- }
63
- }
64
- }
65
-
66
- export const registry = new ImageViewerRegistry();
package/src/index.tsx DELETED
@@ -1,3 +0,0 @@
1
- export { GestureImageViewer } from './GestureImageViewer';
2
- export type { GestureImageViewerProps } from './types';
3
- export { useImageViewerController } from './useImageViewerController';
package/src/types.ts DELETED
@@ -1,41 +0,0 @@
1
- import type React from 'react';
2
- import type { FlatList as RNFlatList, ScrollView as RNScrollView, StyleProp, ViewStyle } from 'react-native';
3
- import type { FlatList as GHFlatList, ScrollView as GHScrollView } from 'react-native-gesture-handler';
4
-
5
- export type FlatListComponent = typeof RNFlatList | typeof GHFlatList;
6
- export type ScrollViewComponent = typeof RNScrollView | typeof GHScrollView;
7
-
8
- type GetComponentProps<T> = T extends React.ComponentType<infer P> ? P : never;
9
-
10
- type ConditionalListProps<LC> = LC extends FlatListComponent
11
- ? React.ComponentProps<LC>
12
- : LC extends ScrollViewComponent
13
- ? React.ComponentProps<LC>
14
- : GetComponentProps<LC>;
15
-
16
- export interface GestureImageViewerProps<T = any, LC = typeof RNFlatList> {
17
- id?: string;
18
- data: T[];
19
- initialIndex?: number;
20
- onIndexChange?: (index: number) => void;
21
- onDismiss?: () => void;
22
- renderImage: (item: T, index: number) => React.ReactElement;
23
- renderContainer?: (children: React.ReactElement) => React.ReactElement;
24
- ListComponent: LC;
25
- width?: number;
26
- dismissThreshold?: number;
27
- // swipeThreshold?: number;
28
- // velocityThreshold?: number;
29
- enableDismissGesture?: boolean;
30
- enableSwipeGesture?: boolean;
31
- resistance?: number;
32
- listProps?: Partial<ConditionalListProps<LC>>;
33
- backdropStyle?: StyleProp<ViewStyle>;
34
- containerStyle?: StyleProp<ViewStyle>;
35
- animateBackdrop?: boolean;
36
- enableZoomPanGesture?: boolean;
37
- enableZoomGesture?: boolean;
38
- enableDoubleTapGesture?: boolean;
39
- maxZoomScale?: number;
40
- itemSpacing?: number;
41
- }
@@ -1,346 +0,0 @@
1
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
- import {
3
- InteractionManager,
4
- type NativeScrollEvent,
5
- type NativeSyntheticEvent,
6
- useWindowDimensions,
7
- } from 'react-native';
8
- import { Gesture } from 'react-native-gesture-handler';
9
- import {
10
- Easing,
11
- interpolate,
12
- runOnJS,
13
- useAnimatedReaction,
14
- useAnimatedStyle,
15
- useSharedValue,
16
- withSpring,
17
- withTiming,
18
- } from 'react-native-reanimated';
19
- import type ImageViewerManager from './ImageViewerManager';
20
- import { registry } from './ImageViewerRegistry';
21
- import type { GestureImageViewerProps } from './types';
22
-
23
- type UseGestureImageViewerProps<T = any> = Omit<
24
- GestureImageViewerProps<T>,
25
- 'renderImage' | 'renderContainer' | 'ListComponent' | 'listProps' | 'containerStyle' | 'backdropStyle'
26
- >;
27
-
28
- export const useGestureImageViewer = <T = any>({
29
- data,
30
- initialIndex = 0,
31
- onIndexChange,
32
- onDismiss,
33
- width: customWidth,
34
- dismissThreshold = 80,
35
- resistance = 2,
36
- // swipeThreshold = 0.5,
37
- // velocityThreshold = 200,
38
- animateBackdrop = true,
39
- enableDismissGesture = true,
40
- enableSwipeGesture = true,
41
- enableZoomGesture = true,
42
- enableDoubleTapGesture = true,
43
- enableZoomPanGesture = true,
44
- maxZoomScale = 2,
45
- itemSpacing = 0,
46
- id = 'default',
47
- }: UseGestureImageViewerProps<T>) => {
48
- const { width: screenWidth, height: screenHeight } = useWindowDimensions();
49
- const width = customWidth || screenWidth;
50
-
51
- const [isZoomed, setIsZoomed] = useState(false);
52
-
53
- const [currentIndex, setCurrentIndex] = useState(initialIndex);
54
- const [manager, setManager] = useState<ImageViewerManager | null>(null);
55
-
56
- const unsubscribeRef = useRef<(() => void) | null>(null);
57
-
58
- const initialTranslateY = useSharedValue(0);
59
- const initialTranslateX = useSharedValue(0);
60
- const startScale = useSharedValue(1);
61
-
62
- const translateY = useSharedValue(0);
63
- const translateX = useSharedValue(0);
64
- const scale = useSharedValue(1);
65
- const backdropOpacity = useSharedValue(1);
66
-
67
- const listRef = useRef<any>(null);
68
-
69
- const dataLength = data?.length || 0;
70
-
71
- useAnimatedReaction(
72
- () => scale.value,
73
- (currentScale) => {
74
- runOnJS(setIsZoomed)(currentScale > 1);
75
- },
76
- );
77
-
78
- useEffect(() => {
79
- const handleManagerChange = (manager: ImageViewerManager | null) => {
80
- unsubscribeRef.current?.();
81
- unsubscribeRef.current = null;
82
-
83
- setManager(manager);
84
-
85
- if (manager) {
86
- setCurrentIndex(manager.getState().currentIndex);
87
- unsubscribeRef.current = manager.subscribe((state) => {
88
- setCurrentIndex(state.currentIndex);
89
- });
90
- return;
91
- }
92
-
93
- setCurrentIndex(0);
94
- };
95
-
96
- const unsubscribeFromRegistry = registry.subscribeToManager(id, handleManagerChange);
97
-
98
- return () => {
99
- unsubscribeFromRegistry();
100
- unsubscribeRef.current?.();
101
- };
102
- }, [id]);
103
-
104
- useEffect(() => {
105
- if (!manager) {
106
- return;
107
- }
108
-
109
- manager.setDataLength(dataLength);
110
- manager.setEnableSwipeGesture(enableSwipeGesture);
111
- manager.setCurrentIndex(initialIndex);
112
- manager.setWidth(width + itemSpacing);
113
- manager.notifyStateChange();
114
- }, [dataLength, enableSwipeGesture, initialIndex, manager, width, itemSpacing]);
115
-
116
- useEffect(() => {
117
- if (!manager || !listRef.current) {
118
- return;
119
- }
120
-
121
- manager.setListRef(listRef.current);
122
- }, [manager]);
123
-
124
- useEffect(() => {
125
- onIndexChange?.(currentIndex);
126
- }, [currentIndex, onIndexChange]);
127
-
128
- useEffect(() => {
129
- translateY.value = 0;
130
- translateX.value = 0;
131
- scale.value = 1;
132
- backdropOpacity.value = 1;
133
- startScale.value = 1;
134
-
135
- if (initialIndex <= 0 || !listRef.current) {
136
- return;
137
- }
138
-
139
- const runAfterInteractions = InteractionManager.runAfterInteractions(() => {
140
- if (listRef.current.scrollToIndex) {
141
- listRef.current.scrollToIndex({
142
- index: initialIndex,
143
- animated: false,
144
- });
145
- } else if (listRef.current.scrollTo) {
146
- listRef.current.scrollTo({
147
- x: initialIndex * (width + itemSpacing),
148
- animated: false,
149
- });
150
- }
151
- });
152
-
153
- return () => {
154
- runAfterInteractions?.cancel();
155
- };
156
- }, [initialIndex, translateY, backdropOpacity, translateX, scale, startScale, width, itemSpacing]);
157
-
158
- const onMomentumScrollEnd = useCallback(
159
- (event: NativeSyntheticEvent<NativeScrollEvent>) => {
160
- if (!enableSwipeGesture) {
161
- return;
162
- }
163
-
164
- const contentOffset = event.nativeEvent.contentOffset;
165
- const newIndex = Math.round(contentOffset.x / (width + itemSpacing));
166
-
167
- if (newIndex !== currentIndex && newIndex >= 0 && newIndex < dataLength) {
168
- if (manager) {
169
- manager.setCurrentIndex(newIndex);
170
- setCurrentIndex(newIndex);
171
- manager.notifyStateChange();
172
- }
173
-
174
- translateX.value = withTiming(0);
175
- translateY.value = withTiming(0);
176
- initialTranslateX.value = withTiming(0);
177
- initialTranslateY.value = withTiming(0);
178
- startScale.value = withTiming(1);
179
- scale.value = withTiming(1);
180
- }
181
- },
182
- [
183
- manager,
184
- currentIndex,
185
- dataLength,
186
- width,
187
- itemSpacing,
188
- enableSwipeGesture,
189
- translateX,
190
- translateY,
191
- scale,
192
- initialTranslateX,
193
- initialTranslateY,
194
- startScale,
195
- ],
196
- );
197
-
198
- const dismissGesture = useMemo(() => {
199
- return Gesture.Pan()
200
- .minDistance(10)
201
- .averageTouches(true)
202
- .activeOffsetY([-10, 10])
203
- .failOffsetX([-10, 10])
204
- .enabled(!isZoomed)
205
- .onUpdate((event) => {
206
- translateY.value = event.translationY / resistance;
207
- })
208
- .onEnd((event) => {
209
- 'worklet';
210
-
211
- if (event.translationY > dismissThreshold && enableDismissGesture && onDismiss) {
212
- runOnJS(onDismiss)();
213
- return;
214
- }
215
-
216
- translateY.value = withSpring(0, {
217
- damping: 15,
218
- stiffness: 150,
219
- });
220
- });
221
- }, [translateY, dismissThreshold, enableDismissGesture, onDismiss, resistance, isZoomed]);
222
-
223
- const zoomPinchGesture = useMemo(() => {
224
- return Gesture.Pinch()
225
- .enabled(enableZoomGesture)
226
- .onBegin(() => {
227
- startScale.value = scale.value;
228
- })
229
- .onUpdate((event) => {
230
- scale.value = startScale.value * event.scale;
231
- })
232
- .onEnd(() => {
233
- if (scale.value > maxZoomScale) {
234
- scale.value = withTiming(maxZoomScale, {
235
- duration: 300,
236
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
237
- });
238
- return;
239
- }
240
-
241
- if (scale.value < 1) {
242
- scale.value = withTiming(1, {
243
- duration: 300,
244
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
245
- });
246
- translateX.value = withTiming(0);
247
- translateY.value = withTiming(0);
248
- }
249
- });
250
- }, [scale, enableZoomGesture, maxZoomScale, translateX, translateY, startScale]);
251
-
252
- const zoomPanGesture = useMemo(() => {
253
- return Gesture.Pan()
254
- .enabled(enableZoomPanGesture && isZoomed)
255
- .onBegin(() => {
256
- initialTranslateX.value = translateX.value;
257
- initialTranslateY.value = translateY.value;
258
- })
259
- .onUpdate((event) => {
260
- 'worklet';
261
-
262
- if (scale.value > 1) {
263
- const maxTranslateX = (width * scale.value - width) / 2;
264
- const maxTranslateY = (screenHeight * scale.value - screenHeight) / 2;
265
-
266
- translateX.value = Math.max(
267
- -maxTranslateX,
268
- Math.min(maxTranslateX, initialTranslateX.value + event.translationX),
269
- );
270
- translateY.value = Math.max(
271
- -maxTranslateY,
272
- Math.min(maxTranslateY, initialTranslateY.value + event.translationY),
273
- );
274
- }
275
- });
276
- }, [
277
- translateX,
278
- translateY,
279
- enableZoomPanGesture,
280
- isZoomed,
281
- scale,
282
- initialTranslateX,
283
- initialTranslateY,
284
- width,
285
- screenHeight,
286
- ]);
287
-
288
- const doubleTapGesture = useMemo(() => {
289
- return Gesture.Tap()
290
- .enabled(enableDoubleTapGesture)
291
- .numberOfTaps(2)
292
- .onEnd(() => {
293
- const nextScale = scale.value > 1 ? 1 : maxZoomScale;
294
-
295
- translateX.value = withTiming(0, {
296
- duration: 300,
297
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
298
- });
299
- translateY.value = withTiming(0, {
300
- duration: 300,
301
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
302
- });
303
-
304
- scale.value = withTiming(nextScale, {
305
- duration: 300,
306
- easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
307
- });
308
- });
309
- }, [scale, enableDoubleTapGesture, maxZoomScale, translateX, translateY]);
310
-
311
- const zoomGesture = useMemo(() => {
312
- return Gesture.Simultaneous(zoomPinchGesture, zoomPanGesture, doubleTapGesture);
313
- }, [zoomPinchGesture, zoomPanGesture, doubleTapGesture]);
314
-
315
- const animatedStyle = useAnimatedStyle(() => {
316
- return {
317
- transform: [{ translateY: translateY.value }, { translateX: translateX.value }, { scale: scale.value }],
318
- };
319
- });
320
-
321
- const backdropStyle = useAnimatedStyle(() => {
322
- if (!animateBackdrop || scale.value > 1) {
323
- return { opacity: 1 };
324
- }
325
-
326
- const opacity = interpolate(translateY.value, [0, 200], [1, 0], 'clamp');
327
-
328
- return { opacity };
329
- }, [animateBackdrop]);
330
-
331
- return {
332
- currentIndex,
333
- dataLength,
334
- translateY,
335
- listRef,
336
- isZoomed,
337
-
338
- dismissGesture,
339
- zoomGesture,
340
-
341
- onMomentumScrollEnd,
342
-
343
- animatedStyle,
344
- backdropStyle,
345
- };
346
- };
@@ -1,48 +0,0 @@
1
- import { useEffect, useMemo, useRef, useState } from 'react';
2
- import type ImageViewerManager from './ImageViewerManager';
3
- import type { ImageViewerManagerState } from './ImageViewerManager';
4
- import { registry } from './ImageViewerRegistry';
5
-
6
- export const useImageViewerController = (id = 'default') => {
7
- const [state, setState] = useState<ImageViewerManagerState>({
8
- currentIndex: 0,
9
- dataLength: 0,
10
- });
11
-
12
- const [manager, setManager] = useState<ImageViewerManager | null>(null);
13
- const unsubscribeRef = useRef<(() => void) | null>(null);
14
-
15
- useEffect(() => {
16
- const handleManagerChange = (newManager: ImageViewerManager | null) => {
17
- unsubscribeRef.current?.();
18
- unsubscribeRef.current = null;
19
-
20
- setManager(newManager);
21
-
22
- if (newManager) {
23
- setState(newManager.getState());
24
- unsubscribeRef.current = newManager.subscribe(setState);
25
- return;
26
- }
27
-
28
- setState({ currentIndex: 0, dataLength: 0 });
29
- };
30
-
31
- const unsubscribeFromRegistry = registry.subscribeToManager(id, handleManagerChange);
32
-
33
- return () => {
34
- unsubscribeFromRegistry();
35
- unsubscribeRef.current?.();
36
- };
37
- }, [id]);
38
-
39
- const noopFunction = useMemo(() => () => {}, []);
40
-
41
- return {
42
- goToIndex: manager?.goToIndex || noopFunction,
43
- goToPrevious: manager?.goToPrevious || noopFunction,
44
- goToNext: manager?.goToNext || noopFunction,
45
- currentIndex: state.currentIndex,
46
- totalCount: state.dataLength,
47
- };
48
- };
package/src/utils.ts DELETED
@@ -1,29 +0,0 @@
1
- import { FlatList as RNFlatList, ScrollView as RNScrollView } from 'react-native';
2
- import { FlatList as GestureFlatList, ScrollView as GestureScrollView } from 'react-native-gesture-handler';
3
- import type { FlatListComponent, ScrollViewComponent } from './types';
4
-
5
- export const isScrollViewLike = (component: any): component is ScrollViewComponent => {
6
- return component === RNScrollView || component === GestureScrollView;
7
- };
8
-
9
- export const isFlatListLike = (component: any): component is FlatListComponent => {
10
- if (component === RNFlatList || component === GestureFlatList || isFlashListLike(component)) {
11
- return true;
12
- }
13
-
14
- return false;
15
- };
16
-
17
- export const isFlashListLike = (component: any): component is any => {
18
- try {
19
- const FlashList = require('@shopify/flash-list')?.FlashList;
20
-
21
- if (FlashList && component === FlashList) {
22
- return true;
23
- }
24
- } catch {
25
- // do nothing
26
- }
27
-
28
- return component?.name === 'FlashList';
29
- };