react-native-gesture-image-viewer 2.3.3 → 3.0.0-beta.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.
Files changed (137) hide show
  1. package/README.md +5 -3
  2. package/lib/module/GestureViewer.js +88 -140
  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/gestureViewerPaging.js +66 -0
  9. package/lib/module/gestureViewerPaging.js.map +1 -0
  10. package/lib/module/gestureViewerRenderWindow.js +29 -0
  11. package/lib/module/gestureViewerRenderWindow.js.map +1 -0
  12. package/lib/module/getWebContentProps.js +6 -0
  13. package/lib/module/getWebContentProps.js.map +1 -0
  14. package/lib/module/getWebContentProps.web.js +8 -0
  15. package/lib/module/getWebContentProps.web.js.map +1 -0
  16. package/lib/module/index.js.map +1 -1
  17. package/lib/module/platformTapGestures.js +4 -0
  18. package/lib/module/platformTapGestures.js.map +1 -0
  19. package/lib/module/platformTapGestures.web.js +4 -0
  20. package/lib/module/platformTapGestures.web.js.map +1 -0
  21. package/lib/module/renderWindow.js +160 -0
  22. package/lib/module/renderWindow.js.map +1 -0
  23. package/lib/module/useGestureViewer.js +386 -200
  24. package/lib/module/useGestureViewer.js.map +1 -1
  25. package/lib/module/useGestureViewerController.js +3 -3
  26. package/lib/module/useGestureViewerController.js.map +1 -1
  27. package/lib/module/useGestureViewerManagerBridge.js +85 -0
  28. package/lib/module/useGestureViewerManagerBridge.js.map +1 -0
  29. package/lib/module/useGestureViewerPaging.js +147 -65
  30. package/lib/module/useGestureViewerPaging.js.map +1 -1
  31. package/lib/module/useWebClickHandler.js +7 -0
  32. package/lib/module/useWebClickHandler.js.map +1 -0
  33. package/lib/module/useWebClickHandler.web.js +85 -0
  34. package/lib/module/useWebClickHandler.web.js.map +1 -0
  35. package/lib/module/useWebSingleTapTimer.js +12 -0
  36. package/lib/module/useWebSingleTapTimer.js.map +1 -0
  37. package/lib/module/useWebSingleTapTimer.web.js +25 -0
  38. package/lib/module/useWebSingleTapTimer.web.js.map +1 -0
  39. package/lib/module/utils/index.js +0 -77
  40. package/lib/module/utils/index.js.map +1 -1
  41. package/lib/module/utils/tapZoom.js +4 -3
  42. package/lib/module/utils/tapZoom.js.map +1 -1
  43. package/lib/typescript/src/GestureViewer.d.ts +1 -1
  44. package/lib/typescript/src/GestureViewer.d.ts.map +1 -1
  45. package/lib/typescript/src/GestureViewerManager.d.ts +20 -20
  46. package/lib/typescript/src/GestureViewerManager.d.ts.map +1 -1
  47. package/lib/typescript/src/gestureViewerAnimation.d.ts +15 -0
  48. package/lib/typescript/src/gestureViewerAnimation.d.ts.map +1 -0
  49. package/lib/typescript/src/gestureViewerPaging.d.ts +13 -0
  50. package/lib/typescript/src/gestureViewerPaging.d.ts.map +1 -0
  51. package/lib/typescript/src/gestureViewerRenderWindow.d.ts +15 -0
  52. package/lib/typescript/src/gestureViewerRenderWindow.d.ts.map +1 -0
  53. package/lib/typescript/src/getWebContentProps.d.ts +3 -0
  54. package/lib/typescript/src/getWebContentProps.d.ts.map +1 -0
  55. package/lib/typescript/src/getWebContentProps.web.d.ts +5 -0
  56. package/lib/typescript/src/getWebContentProps.web.d.ts.map +1 -0
  57. package/lib/typescript/src/index.d.ts +1 -1
  58. package/lib/typescript/src/index.d.ts.map +1 -1
  59. package/lib/typescript/src/platformTapGestures.d.ts +2 -0
  60. package/lib/typescript/src/platformTapGestures.d.ts.map +1 -0
  61. package/lib/typescript/src/platformTapGestures.web.d.ts +2 -0
  62. package/lib/typescript/src/platformTapGestures.web.d.ts.map +1 -0
  63. package/lib/typescript/src/renderWindow.d.ts +44 -0
  64. package/lib/typescript/src/renderWindow.d.ts.map +1 -0
  65. package/lib/typescript/src/types.d.ts +45 -52
  66. package/lib/typescript/src/types.d.ts.map +1 -1
  67. package/lib/typescript/src/useGestureViewer.d.ts +9 -8
  68. package/lib/typescript/src/useGestureViewer.d.ts.map +1 -1
  69. package/lib/typescript/src/useGestureViewerController.d.ts +1 -1
  70. package/lib/typescript/src/useGestureViewerManagerBridge.d.ts +28 -0
  71. package/lib/typescript/src/useGestureViewerManagerBridge.d.ts.map +1 -0
  72. package/lib/typescript/src/useGestureViewerPaging.d.ts +31 -2
  73. package/lib/typescript/src/useGestureViewerPaging.d.ts.map +1 -1
  74. package/lib/typescript/src/useWebClickHandler.d.ts +35 -0
  75. package/lib/typescript/src/useWebClickHandler.d.ts.map +1 -0
  76. package/lib/typescript/src/useWebClickHandler.web.d.ts +36 -0
  77. package/lib/typescript/src/useWebClickHandler.web.d.ts.map +1 -0
  78. package/lib/typescript/src/useWebSingleTapTimer.d.ts +6 -0
  79. package/lib/typescript/src/useWebSingleTapTimer.d.ts.map +1 -0
  80. package/lib/typescript/src/useWebSingleTapTimer.web.d.ts +6 -0
  81. package/lib/typescript/src/useWebSingleTapTimer.web.d.ts.map +1 -0
  82. package/lib/typescript/src/utils/index.d.ts +0 -20
  83. package/lib/typescript/src/utils/index.d.ts.map +1 -1
  84. package/lib/typescript/src/utils/tapZoom.d.ts.map +1 -1
  85. package/package.json +1 -1
  86. package/src/GestureViewer.tsx +104 -191
  87. package/src/GestureViewerManager.ts +77 -122
  88. package/src/gestureViewerAnimation.ts +18 -0
  89. package/src/gestureViewerPaging.ts +116 -0
  90. package/src/gestureViewerRenderWindow.ts +56 -0
  91. package/src/getWebContentProps.ts +5 -0
  92. package/src/getWebContentProps.web.ts +5 -0
  93. package/src/index.tsx +1 -0
  94. package/src/platformTapGestures.ts +1 -0
  95. package/src/platformTapGestures.web.ts +1 -0
  96. package/src/renderWindow.ts +253 -0
  97. package/src/types.ts +44 -99
  98. package/src/useGestureViewer.ts +557 -258
  99. package/src/useGestureViewerController.ts +3 -3
  100. package/src/useGestureViewerManagerBridge.ts +118 -0
  101. package/src/useGestureViewerPaging.ts +277 -98
  102. package/src/useWebClickHandler.ts +44 -0
  103. package/src/useWebClickHandler.web.ts +146 -0
  104. package/src/useWebSingleTapTimer.ts +10 -0
  105. package/src/useWebSingleTapTimer.web.ts +30 -0
  106. package/src/utils/index.ts +0 -102
  107. package/src/utils/tapZoom.ts +4 -3
  108. package/lib/module/WebPagingFixStyle.js +0 -25
  109. package/lib/module/WebPagingFixStyle.js.map +0 -1
  110. package/lib/module/scheduleInitialScroll.js +0 -21
  111. package/lib/module/scheduleInitialScroll.js.map +0 -1
  112. package/lib/module/useGestureViewerPaging.types.js +0 -4
  113. package/lib/module/useGestureViewerPaging.types.js.map +0 -1
  114. package/lib/module/useGestureViewerPaging.web.js +0 -275
  115. package/lib/module/useGestureViewerPaging.web.js.map +0 -1
  116. package/lib/module/utils/FlashList.js +0 -9
  117. package/lib/module/utils/FlashList.js.map +0 -1
  118. package/lib/module/utils/webScroll.js +0 -70
  119. package/lib/module/utils/webScroll.js.map +0 -1
  120. package/lib/typescript/src/WebPagingFixStyle.d.ts +0 -6
  121. package/lib/typescript/src/WebPagingFixStyle.d.ts.map +0 -1
  122. package/lib/typescript/src/scheduleInitialScroll.d.ts +0 -3
  123. package/lib/typescript/src/scheduleInitialScroll.d.ts.map +0 -1
  124. package/lib/typescript/src/useGestureViewerPaging.types.d.ts +0 -41
  125. package/lib/typescript/src/useGestureViewerPaging.types.d.ts.map +0 -1
  126. package/lib/typescript/src/useGestureViewerPaging.web.d.ts +0 -3
  127. package/lib/typescript/src/useGestureViewerPaging.web.d.ts.map +0 -1
  128. package/lib/typescript/src/utils/FlashList.d.ts +0 -2
  129. package/lib/typescript/src/utils/FlashList.d.ts.map +0 -1
  130. package/lib/typescript/src/utils/webScroll.d.ts +0 -18
  131. package/lib/typescript/src/utils/webScroll.d.ts.map +0 -1
  132. package/src/WebPagingFixStyle.tsx +0 -25
  133. package/src/scheduleInitialScroll.ts +0 -36
  134. package/src/useGestureViewerPaging.types.ts +0 -41
  135. package/src/useGestureViewerPaging.web.ts +0 -434
  136. package/src/utils/FlashList.ts +0 -7
  137. package/src/utils/webScroll.ts +0 -96
@@ -1,130 +1,110 @@
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';
1
+ import { useEffect, useMemo } from 'react';
2
+ import { StyleSheet, useWindowDimensions, View, type ViewStyle } from 'react-native';
9
3
  import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler';
10
- import Animated from 'react-native-reanimated';
4
+ import Animated, {
5
+ type AnimatedStyle,
6
+ type SharedValue,
7
+ useAnimatedStyle,
8
+ } from 'react-native-reanimated';
11
9
 
12
10
  import { registry } from './GestureViewerRegistry';
11
+ import { getWebContentProps } from './getWebContentProps';
12
+ import type { RenderWindowSlot } from './renderWindow';
13
13
  import type { GestureViewerProps } from './types';
14
14
  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
15
 
24
- export function GestureViewer<ItemT, LC>({
16
+ type RenderWindowSlotViewProps<ItemT> = {
17
+ animatedStyle: AnimatedStyle<ViewStyle>;
18
+ height: number;
19
+ isActive: boolean;
20
+ pageStride: number;
21
+ renderItem: GestureViewerProps<ItemT>['renderItem'];
22
+ slot: RenderWindowSlot<ItemT>;
23
+ visualPage: SharedValue<number>;
24
+ width: number;
25
+ };
26
+
27
+ function RenderWindowSlotView<ItemT>({
28
+ animatedStyle,
29
+ height,
30
+ isActive,
31
+ pageStride,
32
+ renderItem,
33
+ slot,
34
+ visualPage,
35
+ width,
36
+ }: RenderWindowSlotViewProps<ItemT>) {
37
+ const slotAnimatedStyle = useAnimatedStyle(() => ({
38
+ transform: [{ translateX: (slot.virtualIndex - visualPage.get()) * pageStride }],
39
+ }));
40
+
41
+ return (
42
+ <Animated.View
43
+ style={[
44
+ styles.slot,
45
+ {
46
+ height,
47
+ width,
48
+ },
49
+ slotAnimatedStyle,
50
+ ]}
51
+ >
52
+ <Animated.View style={[styles.page, isActive && animatedStyle]}>
53
+ <View style={[styles.item, { height, width }]}>
54
+ {renderItem(slot.item, slot.logicalIndex, { isActive })}
55
+ </View>
56
+ </Animated.View>
57
+ </Animated.View>
58
+ );
59
+ }
60
+
61
+ export function GestureViewer<ItemT>({
25
62
  id = 'default',
26
63
  data,
27
- renderItem: renderItemProp,
64
+ renderItem,
28
65
  renderContainer,
29
- ListComponent,
30
66
  width: customWidth,
31
67
  height: customHeight,
32
- listProps,
33
68
  backdropStyle: backdropStyleProps,
34
69
  containerStyle,
35
70
  initialIndex = 0,
36
- itemSpacing = 0,
37
- enableSnapMode = false,
71
+ pageSpacing = 0,
72
+ windowSize = 3,
38
73
  enableLoop = false,
39
74
  ...props
40
- }: GestureViewerProps<ItemT, LC>) {
41
- const Component = ListComponent as React.ComponentType<any>;
42
-
43
- const dataRef = useRef(data);
44
-
75
+ }: GestureViewerProps<ItemT>) {
45
76
  const { width: screenWidth, height: screenHeight } = useWindowDimensions();
46
77
 
47
78
  const width = customWidth || screenWidth;
48
79
  const height = customHeight || screenHeight;
49
80
 
50
- const loopData = useMemo(() => createLoopData(dataRef, enableLoop), [enableLoop]);
51
-
52
- const isScrollView = isScrollViewLike(Component);
53
-
54
81
  const {
55
- listRef,
56
- isZoomed,
57
- isRotated,
58
- isPinching,
59
- dismissGesture,
60
- zoomGesture,
61
- nativeScrollGesture,
62
- onWebClick,
63
- onMomentumScrollEnd,
64
- onScroll,
65
- onScrollBeginDrag,
66
82
  animatedStyle,
67
83
  backdropStyle,
84
+ centerVirtualIndex,
85
+ dismissGesture,
68
86
  handleDismiss,
87
+ horizontalPagingGesture,
88
+ onWebClick,
89
+ pageStride,
90
+ renderWindowSlots,
91
+ visualPage,
92
+ zoomGesture,
69
93
  } = useGestureViewer({
70
94
  id,
71
95
  data,
72
96
  width,
73
97
  height,
74
98
  initialIndex,
75
- itemSpacing,
99
+ pageSpacing,
100
+ windowSize,
76
101
  enableLoop,
77
102
  ...props,
78
103
  });
79
104
 
80
- const keyExtractor = useCallback(
81
- (item: ItemT, index: number) => {
82
- if (enableLoop) {
83
- return typeof item === 'string' ? `${item}-${index}` : `item-${index}`;
84
- }
85
-
86
- return typeof item === 'string' ? item : `image-${index}`;
87
- },
88
- [enableLoop],
89
- );
90
-
91
- const renderItem = useCallback(
92
- ({ item, index }: { item: ItemT; index: number }) => {
93
- return (
94
- <View
95
- key={isScrollView ? keyExtractor(item, index) : undefined}
96
- style={[
97
- {
98
- width,
99
- height,
100
- marginHorizontal: itemSpacing / 2,
101
- },
102
- styles.item,
103
- ]}
104
- >
105
- {renderItemProp(item, index)}
106
- </View>
107
- );
108
- },
109
- [width, itemSpacing, renderItemProp, keyExtractor, isScrollView, height],
110
- );
111
-
112
- const getItemLayout = useCallback(
113
- (_: ArrayLike<ItemT> | null | undefined, index: number) => ({
114
- length: width + itemSpacing,
115
- offset: (width + itemSpacing) * index,
116
- index,
117
- }),
118
- [width, itemSpacing],
119
- );
120
-
121
105
  const gesture = useMemo(() => {
122
- return Gesture.Race(dismissGesture, zoomGesture);
123
- }, [zoomGesture, dismissGesture]);
124
-
125
- useEffect(() => {
126
- dataRef.current = data;
127
- }, [data]);
106
+ return Gesture.Race(dismissGesture, horizontalPagingGesture, zoomGesture);
107
+ }, [dismissGesture, horizontalPagingGesture, zoomGesture]);
128
108
 
129
109
  useEffect(() => {
130
110
  registry.createManager(id);
@@ -132,126 +112,59 @@ export function GestureViewer<ItemT, LC>({
132
112
  return () => registry.deleteManager(id);
133
113
  }, [id]);
134
114
 
135
- const commonProps = useMemo(
136
- () =>
137
- ({
138
- horizontal: true,
139
- scrollEnabled: !isZoomed && !isRotated && !isPinching,
140
- showsHorizontalScrollIndicator: false,
141
- onMomentumScrollEnd,
142
- onScroll,
143
- onScrollBeginDrag,
144
- ...(enableSnapMode
145
- ? {
146
- snapToInterval: width + itemSpacing,
147
- snapToAlignment: 'center',
148
- decelerationRate: 'fast',
149
- }
150
- : {
151
- pagingEnabled: true,
152
- }),
153
- scrollEventThrottle: 16,
154
- removeClippedSubviews: true,
155
- }) satisfies ScrollViewProps,
156
- [
157
- width,
158
- itemSpacing,
159
- isZoomed,
160
- isRotated,
161
- isPinching,
162
- onMomentumScrollEnd,
163
- onScroll,
164
- onScrollBeginDrag,
165
- enableSnapMode,
166
- ],
167
- );
168
-
169
115
  const control = useMemo(() => ({ dismiss: handleDismiss }), [handleDismiss]);
116
+ const webContentProps = getWebContentProps(onWebClick);
170
117
 
171
- const shouldWrapScrollableWithNativeGesture = shouldUseNativeScrollGesture(
172
- Platform.OS,
173
- Component,
174
- );
175
-
176
- const maybeWrapWithNativeScrollGesture = useCallback(
177
- (children: React.ReactNode) => {
178
- if (!shouldWrapScrollableWithNativeGesture) {
179
- return children;
180
- }
181
-
182
- return <GestureDetector gesture={nativeScrollGesture}>{children}</GestureDetector>;
183
- },
184
- [nativeScrollGesture, shouldWrapScrollableWithNativeGesture],
185
- );
186
-
187
- const listComponent = (
118
+ const viewer = (
188
119
  <GestureHandlerRootView>
189
120
  <GestureDetector gesture={gesture}>
190
- <View style={[{ width, height }, containerStyle]}>
121
+ <View style={[styles.container, { height, width }, containerStyle]}>
191
122
  <Animated.View style={[styles.background, backdropStyleProps, backdropStyle]} />
192
- <Animated.View
193
- style={[styles.content, animatedStyle]}
194
- {...(Platform.OS === 'web' && {
195
- onClick: onWebClick,
196
- })}
197
- {...(Platform.OS === 'web' &&
198
- isFlashListLike(Component) && { dataSet: { 'flash-list-paging-enabled-fix': true } })}
199
- >
200
- {isScrollView
201
- ? maybeWrapWithNativeScrollGesture(
202
- <Component ref={listRef} {...commonProps} {...listProps}>
203
- {loopData.map((item, index) => renderItem({ item, index }))}
204
- </Component>,
205
- )
206
- : isFlatListLike(Component) &&
207
- maybeWrapWithNativeScrollGesture(
208
- <Component
209
- ref={listRef}
210
- {...commonProps}
211
- data={loopData}
212
- renderItem={renderItem}
213
- initialScrollIndex={
214
- enableLoop && data.length > 1 ? initialIndex + 1 : initialIndex
215
- }
216
- keyExtractor={keyExtractor}
217
- {...(isFlashListLike(Component)
218
- ? // NOTE - Deprecated estimatedItemSize for FlashList V2 (https://shopify.github.io/flash-list/docs/v2-changes#deprecated)
219
- { estimatedItemSize: width + itemSpacing }
220
- : { windowSize: 3, maxToRenderPerBatch: 3, getItemLayout })}
221
- // NOTE - https://github.com/necolas/react-native-web/issues/1299
222
- {...(Platform.OS === 'web' &&
223
- isFlatListLike(Component) && {
224
- dataSet: { 'flat-list-paging-enabled-fix': true },
225
- })}
226
- {...listProps}
227
- />,
228
- )}
123
+ <Animated.View style={styles.content} {...webContentProps}>
124
+ {renderWindowSlots.map((slot) => (
125
+ <RenderWindowSlotView
126
+ animatedStyle={animatedStyle}
127
+ height={height}
128
+ isActive={slot.virtualIndex === centerVirtualIndex}
129
+ key={slot.slotKey}
130
+ pageStride={pageStride}
131
+ renderItem={renderItem}
132
+ slot={slot}
133
+ visualPage={visualPage}
134
+ width={width}
135
+ />
136
+ ))}
229
137
  </Animated.View>
230
- <WebPagingFixStyle Component={Component} />
231
138
  </View>
232
139
  </GestureDetector>
233
140
  </GestureHandlerRootView>
234
141
  );
235
142
 
236
- return renderContainer ? renderContainer(listComponent, control) : listComponent;
143
+ return renderContainer ? renderContainer(viewer, control) : viewer;
237
144
  }
238
145
 
239
146
  const styles = StyleSheet.create({
147
+ background: {
148
+ ...StyleSheet.absoluteFill,
149
+ backgroundColor: '#000',
150
+ },
151
+ container: {
152
+ overflow: 'hidden',
153
+ },
240
154
  content: {
241
- flex: 1,
242
- width: '100%',
243
- height: '100%',
155
+ ...StyleSheet.absoluteFill,
156
+ overflow: 'hidden',
244
157
  },
245
158
  item: {
246
- justifyContent: 'center',
247
159
  alignItems: 'center',
160
+ justifyContent: 'center',
248
161
  },
249
- background: {
162
+ page: {
163
+ flex: 1,
164
+ },
165
+ slot: {
166
+ left: 0,
250
167
  position: 'absolute',
251
168
  top: 0,
252
- left: 0,
253
- right: 0,
254
- bottom: 0,
255
- backgroundColor: 'black',
256
169
  },
257
170
  });
@@ -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;