react-native-gesture-image-viewer 2.4.0 → 3.0.0-beta.1

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.
Files changed (138) hide show
  1. package/README.md +5 -3
  2. package/lib/module/GestureViewer.js +96 -147
  3. package/lib/module/GestureViewer.js.map +1 -1
  4. package/lib/module/GestureViewerManager.js +49 -105
  5. package/lib/module/GestureViewerManager.js.map +1 -1
  6. package/lib/module/gestureViewerAnimation.js +18 -0
  7. package/lib/module/gestureViewerAnimation.js.map +1 -0
  8. package/lib/module/gestureViewerGestures.js +12 -0
  9. package/lib/module/gestureViewerGestures.js.map +1 -0
  10. package/lib/module/gestureViewerPaging.js +66 -0
  11. package/lib/module/gestureViewerPaging.js.map +1 -0
  12. package/lib/module/gestureViewerRenderWindow.js +29 -0
  13. package/lib/module/gestureViewerRenderWindow.js.map +1 -0
  14. package/lib/module/getWebContentProps.js +6 -0
  15. package/lib/module/getWebContentProps.js.map +1 -0
  16. package/lib/module/getWebContentProps.web.js +8 -0
  17. package/lib/module/getWebContentProps.web.js.map +1 -0
  18. package/lib/module/platformTapGestures.js +4 -0
  19. package/lib/module/platformTapGestures.js.map +1 -0
  20. package/lib/module/platformTapGestures.web.js +4 -0
  21. package/lib/module/platformTapGestures.web.js.map +1 -0
  22. package/lib/module/renderWindow.js +160 -0
  23. package/lib/module/renderWindow.js.map +1 -0
  24. package/lib/module/useGestureViewer.js +459 -211
  25. package/lib/module/useGestureViewer.js.map +1 -1
  26. package/lib/module/useGestureViewerController.js +3 -3
  27. package/lib/module/useGestureViewerController.js.map +1 -1
  28. package/lib/module/useGestureViewerManagerBridge.js +85 -0
  29. package/lib/module/useGestureViewerManagerBridge.js.map +1 -0
  30. package/lib/module/useGestureViewerPaging.js +168 -79
  31. package/lib/module/useGestureViewerPaging.js.map +1 -1
  32. package/lib/module/useWebClickHandler.js +7 -0
  33. package/lib/module/useWebClickHandler.js.map +1 -0
  34. package/lib/module/useWebClickHandler.web.js +85 -0
  35. package/lib/module/useWebClickHandler.web.js.map +1 -0
  36. package/lib/module/useWebSingleTapTimer.js +12 -0
  37. package/lib/module/useWebSingleTapTimer.js.map +1 -0
  38. package/lib/module/useWebSingleTapTimer.web.js +25 -0
  39. package/lib/module/useWebSingleTapTimer.web.js.map +1 -0
  40. package/lib/module/utils/index.js +0 -77
  41. package/lib/module/utils/index.js.map +1 -1
  42. package/lib/module/utils/tapZoom.js +4 -3
  43. package/lib/module/utils/tapZoom.js.map +1 -1
  44. package/lib/typescript/src/GestureViewer.d.ts +1 -1
  45. package/lib/typescript/src/GestureViewer.d.ts.map +1 -1
  46. package/lib/typescript/src/GestureViewerManager.d.ts +20 -20
  47. package/lib/typescript/src/GestureViewerManager.d.ts.map +1 -1
  48. package/lib/typescript/src/gestureViewerAnimation.d.ts +15 -0
  49. package/lib/typescript/src/gestureViewerAnimation.d.ts.map +1 -0
  50. package/lib/typescript/src/gestureViewerGestures.d.ts +10 -0
  51. package/lib/typescript/src/gestureViewerGestures.d.ts.map +1 -0
  52. package/lib/typescript/src/gestureViewerPaging.d.ts +13 -0
  53. package/lib/typescript/src/gestureViewerPaging.d.ts.map +1 -0
  54. package/lib/typescript/src/gestureViewerRenderWindow.d.ts +15 -0
  55. package/lib/typescript/src/gestureViewerRenderWindow.d.ts.map +1 -0
  56. package/lib/typescript/src/getWebContentProps.d.ts +3 -0
  57. package/lib/typescript/src/getWebContentProps.d.ts.map +1 -0
  58. package/lib/typescript/src/getWebContentProps.web.d.ts +5 -0
  59. package/lib/typescript/src/getWebContentProps.web.d.ts.map +1 -0
  60. package/lib/typescript/src/platformTapGestures.d.ts +2 -0
  61. package/lib/typescript/src/platformTapGestures.d.ts.map +1 -0
  62. package/lib/typescript/src/platformTapGestures.web.d.ts +2 -0
  63. package/lib/typescript/src/platformTapGestures.web.d.ts.map +1 -0
  64. package/lib/typescript/src/renderWindow.d.ts +44 -0
  65. package/lib/typescript/src/renderWindow.d.ts.map +1 -0
  66. package/lib/typescript/src/types.d.ts +36 -53
  67. package/lib/typescript/src/types.d.ts.map +1 -1
  68. package/lib/typescript/src/useGestureViewer.d.ts +11 -10
  69. package/lib/typescript/src/useGestureViewer.d.ts.map +1 -1
  70. package/lib/typescript/src/useGestureViewerController.d.ts +1 -1
  71. package/lib/typescript/src/useGestureViewerManagerBridge.d.ts +28 -0
  72. package/lib/typescript/src/useGestureViewerManagerBridge.d.ts.map +1 -0
  73. package/lib/typescript/src/useGestureViewerPaging.d.ts +33 -2
  74. package/lib/typescript/src/useGestureViewerPaging.d.ts.map +1 -1
  75. package/lib/typescript/src/useWebClickHandler.d.ts +35 -0
  76. package/lib/typescript/src/useWebClickHandler.d.ts.map +1 -0
  77. package/lib/typescript/src/useWebClickHandler.web.d.ts +36 -0
  78. package/lib/typescript/src/useWebClickHandler.web.d.ts.map +1 -0
  79. package/lib/typescript/src/useWebSingleTapTimer.d.ts +6 -0
  80. package/lib/typescript/src/useWebSingleTapTimer.d.ts.map +1 -0
  81. package/lib/typescript/src/useWebSingleTapTimer.web.d.ts +6 -0
  82. package/lib/typescript/src/useWebSingleTapTimer.web.d.ts.map +1 -0
  83. package/lib/typescript/src/utils/index.d.ts +0 -20
  84. package/lib/typescript/src/utils/index.d.ts.map +1 -1
  85. package/lib/typescript/src/utils/tapZoom.d.ts.map +1 -1
  86. package/package.json +2 -2
  87. package/src/GestureViewer.tsx +113 -208
  88. package/src/GestureViewerManager.ts +77 -122
  89. package/src/gestureViewerAnimation.ts +18 -0
  90. package/src/gestureViewerGestures.ts +20 -0
  91. package/src/gestureViewerPaging.ts +116 -0
  92. package/src/gestureViewerRenderWindow.ts +56 -0
  93. package/src/getWebContentProps.ts +5 -0
  94. package/src/getWebContentProps.web.ts +5 -0
  95. package/src/platformTapGestures.ts +1 -0
  96. package/src/platformTapGestures.web.ts +1 -0
  97. package/src/renderWindow.ts +253 -0
  98. package/src/types.ts +34 -100
  99. package/src/useGestureViewer.ts +665 -275
  100. package/src/useGestureViewerController.ts +3 -3
  101. package/src/useGestureViewerManagerBridge.ts +118 -0
  102. package/src/useGestureViewerPaging.ts +306 -112
  103. package/src/useWebClickHandler.ts +44 -0
  104. package/src/useWebClickHandler.web.ts +146 -0
  105. package/src/useWebSingleTapTimer.ts +10 -0
  106. package/src/useWebSingleTapTimer.web.ts +30 -0
  107. package/src/utils/index.ts +0 -102
  108. package/src/utils/tapZoom.ts +4 -3
  109. package/lib/module/WebPagingFixStyle.js +0 -25
  110. package/lib/module/WebPagingFixStyle.js.map +0 -1
  111. package/lib/module/scheduleInitialScroll.js +0 -21
  112. package/lib/module/scheduleInitialScroll.js.map +0 -1
  113. package/lib/module/useGestureViewerPaging.types.js +0 -4
  114. package/lib/module/useGestureViewerPaging.types.js.map +0 -1
  115. package/lib/module/useGestureViewerPaging.web.js +0 -291
  116. package/lib/module/useGestureViewerPaging.web.js.map +0 -1
  117. package/lib/module/utils/FlashList.js +0 -9
  118. package/lib/module/utils/FlashList.js.map +0 -1
  119. package/lib/module/utils/webScroll.js +0 -70
  120. package/lib/module/utils/webScroll.js.map +0 -1
  121. package/lib/typescript/src/WebPagingFixStyle.d.ts +0 -6
  122. package/lib/typescript/src/WebPagingFixStyle.d.ts.map +0 -1
  123. package/lib/typescript/src/scheduleInitialScroll.d.ts +0 -3
  124. package/lib/typescript/src/scheduleInitialScroll.d.ts.map +0 -1
  125. package/lib/typescript/src/useGestureViewerPaging.types.d.ts +0 -42
  126. package/lib/typescript/src/useGestureViewerPaging.types.d.ts.map +0 -1
  127. package/lib/typescript/src/useGestureViewerPaging.web.d.ts +0 -3
  128. package/lib/typescript/src/useGestureViewerPaging.web.d.ts.map +0 -1
  129. package/lib/typescript/src/utils/FlashList.d.ts +0 -2
  130. package/lib/typescript/src/utils/FlashList.d.ts.map +0 -1
  131. package/lib/typescript/src/utils/webScroll.d.ts +0 -18
  132. package/lib/typescript/src/utils/webScroll.d.ts.map +0 -1
  133. package/src/WebPagingFixStyle.tsx +0 -25
  134. package/src/scheduleInitialScroll.ts +0 -36
  135. package/src/useGestureViewerPaging.types.ts +0 -42
  136. package/src/useGestureViewerPaging.web.ts +0 -453
  137. package/src/utils/FlashList.ts +0 -7
  138. package/src/utils/webScroll.ts +0 -96
@@ -1,145 +1,117 @@
1
- import { useCallback, useEffect, useMemo, useRef } from 'react';
2
- import {
3
- Platform,
4
- type ScrollViewProps,
5
- StyleSheet,
6
- useWindowDimensions,
7
- View,
8
- } from 'react-native';
9
- import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler';
10
- import Animated from 'react-native-reanimated';
11
-
1
+ import { useEffect, useMemo } from 'react';
2
+ import { StyleSheet, useWindowDimensions, View, type ViewStyle } from 'react-native';
3
+ import { GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler';
4
+ import Animated, {
5
+ type AnimatedStyle,
6
+ type SharedValue,
7
+ useAnimatedStyle,
8
+ } from 'react-native-reanimated';
9
+
10
+ import { composeGestureViewerGestures } from './gestureViewerGestures';
12
11
  import { registry } from './GestureViewerRegistry';
12
+ import { getWebContentProps } from './getWebContentProps';
13
+ import type { RenderWindowSlot } from './renderWindow';
13
14
  import type { GestureViewerProps } from './types';
14
15
  import { useGestureViewer } from './useGestureViewer';
15
- import {
16
- createLoopData,
17
- isFlashListLike,
18
- isFlatListLike,
19
- isScrollViewLike,
20
- shouldUseNativeScrollGesture,
21
- } from './utils';
22
- import WebPagingFixStyle from './WebPagingFixStyle';
23
16
 
24
- export function GestureViewer<ItemT, LC>({
17
+ type RenderWindowSlotViewProps<ItemT> = {
18
+ animatedStyle: AnimatedStyle<ViewStyle>;
19
+ height: number;
20
+ isActive: boolean;
21
+ pageStride: number;
22
+ renderItem: GestureViewerProps<ItemT>['renderItem'];
23
+ slot: RenderWindowSlot<ItemT>;
24
+ visualPage: SharedValue<number>;
25
+ width: number;
26
+ };
27
+
28
+ function RenderWindowSlotView<ItemT>({
29
+ animatedStyle,
30
+ height,
31
+ isActive,
32
+ pageStride,
33
+ renderItem,
34
+ slot,
35
+ visualPage,
36
+ width,
37
+ }: RenderWindowSlotViewProps<ItemT>) {
38
+ const slotAnimatedStyle = useAnimatedStyle(() => ({
39
+ transform: [{ translateX: (slot.virtualIndex - visualPage.get()) * pageStride }],
40
+ }));
41
+
42
+ return (
43
+ <Animated.View
44
+ style={[
45
+ styles.slot,
46
+ {
47
+ height,
48
+ width,
49
+ },
50
+ slotAnimatedStyle,
51
+ ]}
52
+ >
53
+ <Animated.View style={[styles.page, isActive && animatedStyle]}>
54
+ <View style={[styles.item, { height, width }]}>
55
+ {renderItem(slot.item, slot.logicalIndex, { isActive })}
56
+ </View>
57
+ </Animated.View>
58
+ </Animated.View>
59
+ );
60
+ }
61
+
62
+ export function GestureViewer<ItemT>({
25
63
  id = 'default',
26
64
  data,
27
- renderItem: renderItemProp,
65
+ renderItem,
28
66
  renderContainer,
29
- ListComponent,
30
67
  width: customWidth,
31
68
  height: customHeight,
32
- listProps,
33
69
  backdropStyle: backdropStyleProps,
34
70
  containerStyle,
35
71
  initialIndex = 0,
36
- itemSpacing = 0,
37
- enableSnapMode = false,
72
+ pageSpacing = 0,
73
+ windowSize = 3,
38
74
  enableLoop = false,
39
75
  ...props
40
- }: GestureViewerProps<ItemT, LC>) {
41
- const Component = ListComponent as React.ComponentType<any>;
42
-
43
- const dataRef = useRef(data);
44
-
76
+ }: GestureViewerProps<ItemT>) {
45
77
  const { width: screenWidth, height: screenHeight } = useWindowDimensions();
46
78
 
47
79
  const width = customWidth || screenWidth;
48
80
  const height = customHeight || screenHeight;
49
81
 
50
- const loopData = useMemo(() => createLoopData(dataRef, enableLoop), [enableLoop]);
51
-
52
- const isScrollView = isScrollViewLike(Component);
53
- const isFlashList = isFlashListLike(Component);
54
-
55
82
  const {
56
- activeListIndex,
57
- listRef,
58
- isZoomed,
59
- isRotated,
60
- isPinching,
61
- dismissGesture,
62
- zoomGesture,
63
- nativeScrollGesture,
64
- onWebClick,
65
- onMomentumScrollEnd,
66
- onScroll,
67
- onScrollBeginDrag,
68
83
  animatedStyle,
69
84
  backdropStyle,
85
+ centerVirtualIndex,
86
+ dismissGesture,
70
87
  handleDismiss,
88
+ horizontalPagingGesture,
89
+ onWebClick,
90
+ pageStride,
91
+ renderWindowSlots,
92
+ visualPage,
93
+ zoomGesture,
94
+ zoomPinchGesture,
71
95
  } = useGestureViewer({
72
96
  id,
73
97
  data,
74
98
  width,
75
99
  height,
76
100
  initialIndex,
77
- itemSpacing,
101
+ pageSpacing,
102
+ windowSize,
78
103
  enableLoop,
79
104
  ...props,
80
105
  });
81
106
 
82
- const keyExtractor = useCallback(
83
- (item: ItemT, index: number) => {
84
- if (enableLoop) {
85
- return typeof item === 'string' ? `${item}-${index}` : `item-${index}`;
86
- }
87
-
88
- return typeof item === 'string' ? item : `image-${index}`;
89
- },
90
- [enableLoop],
91
- );
92
-
93
- const renderItem = useCallback(
94
- ({ item, index, target }: { item: ItemT; index: number; target?: string }) => {
95
- const isFlashListCell = !isFlashList || target === undefined || target === 'Cell';
96
-
97
- return (
98
- <View
99
- key={isScrollView ? keyExtractor(item, index) : undefined}
100
- style={[
101
- {
102
- width,
103
- height,
104
- marginHorizontal: itemSpacing / 2,
105
- },
106
- styles.item,
107
- ]}
108
- >
109
- {renderItemProp(item, index, {
110
- isActive: index === activeListIndex && isFlashListCell,
111
- })}
112
- </View>
113
- );
114
- },
115
- [
116
- activeListIndex,
117
- width,
118
- itemSpacing,
119
- renderItemProp,
120
- keyExtractor,
121
- isScrollView,
122
- isFlashList,
123
- height,
124
- ],
125
- );
126
-
127
- const getItemLayout = useCallback(
128
- (_: ArrayLike<ItemT> | null | undefined, index: number) => ({
129
- length: width + itemSpacing,
130
- offset: (width + itemSpacing) * index,
131
- index,
132
- }),
133
- [width, itemSpacing],
134
- );
135
-
136
107
  const gesture = useMemo(() => {
137
- return Gesture.Race(dismissGesture, zoomGesture);
138
- }, [zoomGesture, dismissGesture]);
139
-
140
- useEffect(() => {
141
- dataRef.current = data;
142
- }, [data]);
108
+ return composeGestureViewerGestures({
109
+ dismissGesture,
110
+ horizontalPagingGesture,
111
+ zoomGesture,
112
+ zoomPinchGesture,
113
+ });
114
+ }, [dismissGesture, horizontalPagingGesture, zoomGesture, zoomPinchGesture]);
143
115
 
144
116
  useEffect(() => {
145
117
  registry.createManager(id);
@@ -147,126 +119,59 @@ export function GestureViewer<ItemT, LC>({
147
119
  return () => registry.deleteManager(id);
148
120
  }, [id]);
149
121
 
150
- const commonProps = useMemo(
151
- () =>
152
- ({
153
- horizontal: true,
154
- scrollEnabled: !isZoomed && !isRotated && !isPinching,
155
- showsHorizontalScrollIndicator: false,
156
- onMomentumScrollEnd,
157
- onScroll,
158
- onScrollBeginDrag,
159
- ...(enableSnapMode
160
- ? {
161
- snapToInterval: width + itemSpacing,
162
- snapToAlignment: 'center',
163
- decelerationRate: 'fast',
164
- }
165
- : {
166
- pagingEnabled: true,
167
- }),
168
- scrollEventThrottle: 16,
169
- removeClippedSubviews: true,
170
- }) satisfies ScrollViewProps,
171
- [
172
- width,
173
- itemSpacing,
174
- isZoomed,
175
- isRotated,
176
- isPinching,
177
- onMomentumScrollEnd,
178
- onScroll,
179
- onScrollBeginDrag,
180
- enableSnapMode,
181
- ],
182
- );
183
-
184
122
  const control = useMemo(() => ({ dismiss: handleDismiss }), [handleDismiss]);
123
+ const webContentProps = getWebContentProps(onWebClick);
185
124
 
186
- const shouldWrapScrollableWithNativeGesture = shouldUseNativeScrollGesture(
187
- Platform.OS,
188
- Component,
189
- );
190
-
191
- const maybeWrapWithNativeScrollGesture = useCallback(
192
- (children: React.ReactNode) => {
193
- if (!shouldWrapScrollableWithNativeGesture) {
194
- return children;
195
- }
196
-
197
- return <GestureDetector gesture={nativeScrollGesture}>{children}</GestureDetector>;
198
- },
199
- [nativeScrollGesture, shouldWrapScrollableWithNativeGesture],
200
- );
201
-
202
- const listComponent = (
125
+ const viewer = (
203
126
  <GestureHandlerRootView>
204
127
  <GestureDetector gesture={gesture}>
205
- <View style={[{ width, height }, containerStyle]}>
128
+ <View style={[styles.container, { height, width }, containerStyle]}>
206
129
  <Animated.View style={[styles.background, backdropStyleProps, backdropStyle]} />
207
- <Animated.View
208
- style={[styles.content, animatedStyle]}
209
- {...(Platform.OS === 'web' && {
210
- onClick: onWebClick,
211
- })}
212
- {...(Platform.OS === 'web' &&
213
- isFlashList && { dataSet: { 'flash-list-paging-enabled-fix': true } })}
214
- >
215
- {isScrollView
216
- ? maybeWrapWithNativeScrollGesture(
217
- <Component ref={listRef} {...commonProps} {...listProps}>
218
- {loopData.map((item, index) => renderItem({ item, index }))}
219
- </Component>,
220
- )
221
- : isFlatListLike(Component) &&
222
- maybeWrapWithNativeScrollGesture(
223
- <Component
224
- ref={listRef}
225
- {...commonProps}
226
- data={loopData}
227
- renderItem={renderItem}
228
- initialScrollIndex={
229
- enableLoop && data.length > 1 ? initialIndex + 1 : initialIndex
230
- }
231
- keyExtractor={keyExtractor}
232
- {...(isFlashList
233
- ? // NOTE - Deprecated estimatedItemSize for FlashList V2 (https://shopify.github.io/flash-list/docs/v2-changes#deprecated)
234
- { estimatedItemSize: width + itemSpacing }
235
- : { windowSize: 3, maxToRenderPerBatch: 3, getItemLayout })}
236
- // NOTE - https://github.com/necolas/react-native-web/issues/1299
237
- {...(Platform.OS === 'web' &&
238
- isFlatListLike(Component) && {
239
- dataSet: { 'flat-list-paging-enabled-fix': true },
240
- })}
241
- {...listProps}
242
- />,
243
- )}
130
+ <Animated.View style={styles.content} {...webContentProps}>
131
+ {renderWindowSlots.map((slot) => (
132
+ <RenderWindowSlotView
133
+ animatedStyle={animatedStyle}
134
+ height={height}
135
+ isActive={slot.virtualIndex === centerVirtualIndex}
136
+ key={slot.slotKey}
137
+ pageStride={pageStride}
138
+ renderItem={renderItem}
139
+ slot={slot}
140
+ visualPage={visualPage}
141
+ width={width}
142
+ />
143
+ ))}
244
144
  </Animated.View>
245
- <WebPagingFixStyle Component={Component} />
246
145
  </View>
247
146
  </GestureDetector>
248
147
  </GestureHandlerRootView>
249
148
  );
250
149
 
251
- return renderContainer ? renderContainer(listComponent, control) : listComponent;
150
+ return renderContainer ? renderContainer(viewer, control) : viewer;
252
151
  }
253
152
 
254
153
  const styles = StyleSheet.create({
154
+ background: {
155
+ ...StyleSheet.absoluteFill,
156
+ backgroundColor: '#000',
157
+ },
158
+ container: {
159
+ overflow: 'hidden',
160
+ },
255
161
  content: {
256
- flex: 1,
257
- width: '100%',
258
- height: '100%',
162
+ ...StyleSheet.absoluteFill,
163
+ overflow: 'hidden',
259
164
  },
260
165
  item: {
261
- justifyContent: 'center',
262
166
  alignItems: 'center',
167
+ justifyContent: 'center',
263
168
  },
264
- background: {
169
+ page: {
170
+ flex: 1,
171
+ },
172
+ slot: {
173
+ left: 0,
265
174
  position: 'absolute',
266
175
  top: 0,
267
- left: 0,
268
- right: 0,
269
- bottom: 0,
270
- backgroundColor: 'black',
271
176
  },
272
177
  });
@@ -6,29 +6,45 @@ import type {
6
6
  GestureViewerEventType,
7
7
  GestureViewerState,
8
8
  } from './types';
9
- import { createBoundsConstraint, createScrollAction } from './utils';
9
+ import { createBoundsConstraint } from './utils';
10
+
11
+ export type GestureViewerNavigationOptions = {
12
+ animated?: boolean;
13
+ };
14
+
15
+ export type GestureViewerNavigationAdapter = {
16
+ goToIndex: (index: number, options?: GestureViewerNavigationOptions) => void;
17
+ goToNext: () => void;
18
+ goToPrevious: () => void;
19
+ };
20
+
21
+ export type GestureViewerStateReader = () => GestureViewerState;
22
+
23
+ type GestureViewerEventListenerPresenceCallback = (
24
+ eventType: GestureViewerEventType,
25
+ hasListeners: boolean,
26
+ ) => void;
27
+
28
+ const DEFAULT_STATE_READER: GestureViewerStateReader = () => ({
29
+ currentIndex: 0,
30
+ totalCount: 0,
31
+ });
10
32
 
11
33
  class GestureViewerManager {
12
- private currentIndex = 0;
13
- private dataLength = 0;
14
34
  private width = 0;
15
35
  private height = 0;
16
36
  private maxZoomScale = 2;
17
- private enableHorizontalSwipe = true;
18
- private enableLoop = false;
19
- private listRef: any | null = null;
37
+ private navigationAdapter: GestureViewerNavigationAdapter | null = null;
38
+ private stateReader = DEFAULT_STATE_READER;
20
39
 
21
40
  private scale: SharedValue<number> | null = null;
22
41
  private rotation: SharedValue<number> | null = null;
23
42
  private translateX: SharedValue<number> | null = null;
24
43
  private translateY: SharedValue<number> | null = null;
25
- private resetTransformCallback: (() => void) | null = null;
26
-
27
- private loopCallback: (() => void) | null = null;
28
- private programmaticScrollVersion = 0;
29
44
 
30
45
  private listeners = new Set<(state: GestureViewerState) => void>();
31
46
  private eventListeners = new Map<GestureViewerEventType, Set<(data: any) => void>>();
47
+ private eventListenerPresenceSubscribers = new Set<GestureViewerEventListenerPresenceCallback>();
32
48
 
33
49
  private notifyListeners() {
34
50
  const state = this.getState();
@@ -53,6 +69,7 @@ class GestureViewerManager {
53
69
  }
54
70
 
55
71
  this.eventListeners.get(eventType)?.add(callback);
72
+ this.notifyEventListenerPresence(eventType);
56
73
 
57
74
  return () => {
58
75
  const listeners = this.eventListeners.get(eventType);
@@ -64,9 +81,40 @@ class GestureViewerManager {
64
81
  this.eventListeners.delete(eventType);
65
82
  }
66
83
  }
84
+
85
+ this.notifyEventListenerPresence(eventType);
86
+ };
87
+ }
88
+
89
+ subscribeToEventListenerPresence(callback: GestureViewerEventListenerPresenceCallback) {
90
+ this.eventListenerPresenceSubscribers.add(callback);
91
+
92
+ this.notifyEventListenerPresenceSubscriber(callback, 'zoomChange');
93
+ this.notifyEventListenerPresenceSubscriber(callback, 'rotationChange');
94
+ this.notifyEventListenerPresenceSubscriber(callback, 'tap');
95
+
96
+ return () => {
97
+ this.eventListenerPresenceSubscribers.delete(callback);
67
98
  };
68
99
  }
69
100
 
101
+ hasEventListeners(eventType: GestureViewerEventType) {
102
+ return (this.eventListeners.get(eventType)?.size ?? 0) > 0;
103
+ }
104
+
105
+ private notifyEventListenerPresence(eventType: GestureViewerEventType) {
106
+ this.eventListenerPresenceSubscribers.forEach((callback) => {
107
+ this.notifyEventListenerPresenceSubscriber(callback, eventType);
108
+ });
109
+ }
110
+
111
+ private notifyEventListenerPresenceSubscriber(
112
+ callback: GestureViewerEventListenerPresenceCallback,
113
+ eventType: GestureViewerEventType,
114
+ ) {
115
+ callback(eventType, this.hasEventListeners(eventType));
116
+ }
117
+
70
118
  private emitEvent<T extends GestureViewerEventType>(
71
119
  eventType: T,
72
120
  data: GestureViewerEventData[T],
@@ -91,18 +139,7 @@ class GestureViewerManager {
91
139
  };
92
140
 
93
141
  getState(): GestureViewerState {
94
- return {
95
- currentIndex: this.currentIndex,
96
- totalCount: this.dataLength,
97
- };
98
- }
99
-
100
- getProgrammaticScrollVersion() {
101
- return this.programmaticScrollVersion;
102
- }
103
-
104
- setEnableLoop(enabled: boolean) {
105
- this.enableLoop = enabled;
142
+ return this.stateReader();
106
143
  }
107
144
 
108
145
  setWidth(width: number) {
@@ -113,22 +150,12 @@ class GestureViewerManager {
113
150
  this.height = height;
114
151
  }
115
152
 
116
- setListRef(ref: any) {
117
- this.listRef = ref;
118
- }
119
-
120
- setDataLength(length: number) {
121
- this.dataLength = length;
153
+ setNavigationAdapter(adapter: GestureViewerNavigationAdapter | null) {
154
+ this.navigationAdapter = adapter;
122
155
  }
123
156
 
124
- setEnableHorizontalSwipe(enabled: boolean) {
125
- this.enableHorizontalSwipe = enabled;
126
- }
127
-
128
- setCurrentIndex(index: number) {
129
- if (index !== this.currentIndex) {
130
- this.currentIndex = index;
131
- }
157
+ setStateReader(reader: GestureViewerStateReader | null) {
158
+ this.stateReader = reader ?? DEFAULT_STATE_READER;
132
159
  }
133
160
 
134
161
  setZoomSharedValues(
@@ -143,10 +170,6 @@ class GestureViewerManager {
143
170
  this.maxZoomScale = maxZoomScale;
144
171
  }
145
172
 
146
- setResetTransformCallback(callback: (() => void) | null) {
147
- this.resetTransformCallback = callback;
148
- }
149
-
150
173
  notifyStateChange() {
151
174
  this.notifyListeners();
152
175
  }
@@ -266,110 +289,42 @@ class GestureViewerManager {
266
289
  this.translateY.set(withTiming(0));
267
290
  };
268
291
 
269
- goToIndex = (index: number) => {
270
- if (!this.enableHorizontalSwipe || !this.listRef) {
292
+ goToIndex = (index: number, options?: GestureViewerNavigationOptions) => {
293
+ if (!this.navigationAdapter) {
271
294
  return;
272
295
  }
273
296
 
274
- this.cancelPendingLoopTransition();
275
-
276
- const { scrollTo } = createScrollAction(this.listRef, this.width);
277
-
278
- if (this.enableLoop && this.dataLength > 1) {
279
- if (index < 0) {
280
- this.loopCallback = () => {
281
- scrollTo(this.dataLength, false);
282
- this.updateCurrentIndex(this.dataLength - 1);
283
- this.cancelPendingLoopTransition();
284
- };
285
-
286
- this.resetTransformCallback?.();
287
- this.programmaticScrollVersion += 1;
288
- scrollTo(0, true);
289
- return;
290
- }
291
-
292
- if (index >= this.dataLength) {
293
- this.loopCallback = () => {
294
- scrollTo(1, false);
295
- this.updateCurrentIndex(0);
296
- this.cancelPendingLoopTransition();
297
- };
298
-
299
- this.resetTransformCallback?.();
300
- this.programmaticScrollVersion += 1;
301
- scrollTo(this.dataLength + 1, true);
302
- return;
303
- }
304
-
305
- this.resetTransformCallback?.();
306
- this.programmaticScrollVersion += 1;
307
- scrollTo(index + 1, true);
308
- this.updateCurrentIndex(index);
309
-
310
- return;
311
- }
297
+ this.navigationAdapter.goToIndex(index, options);
298
+ };
312
299
 
313
- if (index < 0 || index >= this.dataLength) {
300
+ goToPrevious = () => {
301
+ if (!this.navigationAdapter) {
314
302
  return;
315
303
  }
316
304
 
317
- this.resetTransformCallback?.();
318
- this.programmaticScrollVersion += 1;
319
- scrollTo(index, true);
320
- this.updateCurrentIndex(index);
305
+ this.navigationAdapter.goToPrevious();
321
306
  };
322
307
 
323
- handleMomentumScrollEnd = (scrollIndex: number) => {
324
- if (!this.loopCallback) {
325
- return false;
326
- }
327
-
328
- if (scrollIndex === 0 || scrollIndex === this.dataLength + 1) {
329
- this.loopCallback();
330
- return true;
308
+ goToNext = () => {
309
+ if (!this.navigationAdapter) {
310
+ return;
331
311
  }
332
312
 
333
- this.cancelPendingLoopTransition();
334
- return true;
335
- };
336
-
337
- cancelPendingLoopTransition = () => {
338
- this.loopCallback = null;
339
- };
340
-
341
- handleScrollBeginDrag = () => {
342
- this.cancelPendingLoopTransition();
343
- };
344
-
345
- goToPrevious = () => {
346
- this.goToIndex(this.currentIndex - 1);
347
- };
348
-
349
- goToNext = () => {
350
- this.goToIndex(this.currentIndex + 1);
313
+ this.navigationAdapter.goToNext();
351
314
  };
352
315
 
353
316
  cleanUp() {
354
- this.cancelPendingLoopTransition();
355
317
  this.listeners.clear();
356
- this.listRef = null;
357
- this.enableHorizontalSwipe = true;
358
- this.currentIndex = 0;
359
- this.dataLength = 0;
318
+ this.navigationAdapter = null;
319
+ this.stateReader = DEFAULT_STATE_READER;
360
320
  this.maxZoomScale = 2;
361
321
  this.scale = null;
362
322
  this.translateX = null;
363
323
  this.translateY = null;
364
324
  this.rotation = null;
365
- this.resetTransformCallback = null;
366
325
  this.eventListeners.clear();
326
+ this.eventListenerPresenceSubscribers.clear();
367
327
  }
368
-
369
- private updateCurrentIndex = (targetIndex: number) => {
370
- this.currentIndex = targetIndex;
371
- this.notifyListeners();
372
- };
373
328
  }
374
329
 
375
330
  export default GestureViewerManager;
@@ -0,0 +1,18 @@
1
+ import { Easing } from 'react-native-reanimated';
2
+
3
+ export const PAGE_TRANSITION_CONFIG = {
4
+ duration: 240,
5
+ easing: Easing.bezier(0.25, 0.1, 0.25, 1),
6
+ };
7
+
8
+ export const PAGE_SPRING_CONFIG = {
9
+ damping: 35,
10
+ energyThreshold: 6e-9,
11
+ mass: 1,
12
+ overshootClamping: false,
13
+ stiffness: 360,
14
+ };
15
+
16
+ export const SWIPE_THRESHOLD_RATIO = 0.25;
17
+ export const SWIPE_VELOCITY_THRESHOLD = 800;
18
+ export const EDGE_RESISTANCE = 0.35;