react-native-gesture-image-viewer 0.5.0 → 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/README.md +7 -5
- package/lib/module/GestureImageViewer.js +10 -9
- package/lib/module/GestureImageViewer.js.map +1 -1
- package/lib/module/utils.js +6 -3
- package/lib/module/utils.js.map +1 -1
- package/lib/typescript/src/GestureImageViewer.d.ts +2 -1
- package/lib/typescript/src/GestureImageViewer.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +11 -13
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/utils.d.ts +4 -3
- package/lib/typescript/src/utils.d.ts.map +1 -1
- package/package.json +1 -2
- package/src/GestureImageViewer.tsx +0 -163
- package/src/ImageViewerManager.ts +0 -103
- package/src/ImageViewerRegistry.ts +0 -66
- package/src/index.tsx +0 -3
- package/src/types.ts +0 -36
- package/src/useGestureImageViewer.ts +0 -346
- package/src/useImageViewerController.ts +0 -48
- package/src/utils.ts +0 -24
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Existing libraries often have limited customization options or performance issue
|
|
|
16
16
|
- ✅ **External Control API** - Programmatic control from buttons or other components
|
|
17
17
|
- ✅ **Multi-Instance Management** - ID-based independent management of multiple viewers
|
|
18
18
|
- ✅ **Flexible Integration** - Use with FlatList, FlashList, Expo Image, FastImage, and more
|
|
19
|
-
- ✅ **Full TypeScript Support** -
|
|
19
|
+
- ✅ **Full TypeScript Support** - Enhanced developer experience with smart type inference
|
|
20
20
|
- ✅ **Cross-Platform** - iOS, Android, and Web support
|
|
21
21
|
- ✅ **Easy-to-Use API** - Intuitive and simple implementation without complex setup
|
|
22
22
|
- ✅ **Various Environment Support** - Expo Go and New Architecture support
|
|
@@ -168,17 +168,19 @@ function App() {
|
|
|
168
168
|
|
|
169
169
|
#### List Components
|
|
170
170
|
|
|
171
|
-
Support for `ScrollView`, `FlatList`, `FlashList
|
|
172
|
-
|
|
171
|
+
Support for any list component like `ScrollView`, `FlatList`, `FlashList` through the `ListComponent` prop.
|
|
172
|
+
The `listProps` provides **type inference based on the selected list component**, ensuring accurate autocompletion and type safety in your IDE.
|
|
173
173
|
|
|
174
174
|
```tsx
|
|
175
|
+
import { FlashList } from '@shopify/flash-list';
|
|
176
|
+
|
|
175
177
|
function App() {
|
|
176
178
|
return (
|
|
177
179
|
<GestureImageViewer
|
|
178
180
|
data={images}
|
|
179
|
-
ListComponent={
|
|
181
|
+
ListComponent={FlashList}
|
|
180
182
|
listProps={{
|
|
181
|
-
//
|
|
183
|
+
// ✅ FlashList props autocompletion
|
|
182
184
|
}}
|
|
183
185
|
/>
|
|
184
186
|
);
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import { useCallback, useEffect, useMemo } from 'react';
|
|
4
4
|
import { Platform, StyleSheet, useWindowDimensions, View } from 'react-native';
|
|
5
|
-
import {
|
|
5
|
+
import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
6
6
|
import Animated from 'react-native-reanimated';
|
|
7
7
|
import { registry } from "./ImageViewerRegistry.js";
|
|
8
8
|
import { useGestureImageViewer } from "./useGestureImageViewer.js";
|
|
9
|
-
import { isFlatListLike, isScrollViewLike } from "./utils.js";
|
|
9
|
+
import { isFlashListLike, isFlatListLike, isScrollViewLike } from "./utils.js";
|
|
10
10
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
11
|
const WebPagingFix = () => {
|
|
12
12
|
if (Platform.OS !== 'web') {
|
|
@@ -21,7 +21,7 @@ export function GestureImageViewer({
|
|
|
21
21
|
data,
|
|
22
22
|
renderImage,
|
|
23
23
|
renderContainer,
|
|
24
|
-
ListComponent
|
|
24
|
+
ListComponent,
|
|
25
25
|
width: customWidth,
|
|
26
26
|
listProps,
|
|
27
27
|
backdropStyle: backdropStyleProps,
|
|
@@ -30,6 +30,7 @@ export function GestureImageViewer({
|
|
|
30
30
|
itemSpacing = 0,
|
|
31
31
|
...props
|
|
32
32
|
}) {
|
|
33
|
+
const Component = ListComponent;
|
|
33
34
|
const {
|
|
34
35
|
width: screenWidth
|
|
35
36
|
} = useWindowDimensions();
|
|
@@ -98,7 +99,7 @@ export function GestureImageViewer({
|
|
|
98
99
|
style: [styles.background, backdropStyleProps, backdropStyle]
|
|
99
100
|
}), /*#__PURE__*/_jsx(Animated.View, {
|
|
100
101
|
style: [styles.content, animatedStyle],
|
|
101
|
-
children: isScrollViewLike(
|
|
102
|
+
children: isScrollViewLike(Component) ? /*#__PURE__*/_jsx(Component, {
|
|
102
103
|
ref: listRef,
|
|
103
104
|
...commonProps,
|
|
104
105
|
...listProps,
|
|
@@ -106,19 +107,19 @@ export function GestureImageViewer({
|
|
|
106
107
|
item,
|
|
107
108
|
index
|
|
108
109
|
}))
|
|
109
|
-
}) : isFlatListLike(
|
|
110
|
+
}) : isFlatListLike(Component) && /*#__PURE__*/_jsx(Component, {
|
|
110
111
|
ref: listRef,
|
|
111
112
|
...commonProps,
|
|
112
113
|
data: data,
|
|
113
114
|
renderItem: renderItem,
|
|
114
115
|
initialScrollIndex: initialIndex,
|
|
115
116
|
keyExtractor: keyExtractor,
|
|
116
|
-
estimatedItemSize: width + itemSpacing,
|
|
117
117
|
windowSize: 3,
|
|
118
118
|
maxToRenderPerBatch: 3,
|
|
119
|
-
getItemLayout: getItemLayout
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
getItemLayout: getItemLayout,
|
|
120
|
+
...(isFlashListLike(Component) && {
|
|
121
|
+
estimatedItemSize: width + itemSpacing
|
|
122
|
+
}),
|
|
122
123
|
...(Platform.OS === 'web' && {
|
|
123
124
|
dataSet: {
|
|
124
125
|
'paging-enabled-fix': true
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["useCallback","useEffect","useMemo","Platform","StyleSheet","useWindowDimensions","View","
|
|
1
|
+
{"version":3,"names":["useCallback","useEffect","useMemo","Platform","StyleSheet","useWindowDimensions","View","Gesture","GestureDetector","GestureHandlerRootView","Animated","registry","useGestureImageViewer","isFlashListLike","isFlatListLike","isScrollViewLike","jsx","_jsx","jsxs","_jsxs","WebPagingFix","OS","children","GestureImageViewer","id","data","renderImage","renderContainer","ListComponent","width","customWidth","listProps","backdropStyle","backdropStyleProps","containerStyle","initialIndex","itemSpacing","props","Component","screenWidth","listRef","isZoomed","dismissGesture","zoomGesture","onMomentumScrollEnd","animatedStyle","renderItem","item","index","style","height","justifyContent","alignItems","marginHorizontal","getItemLayout","_","length","offset","keyExtractor","gesture","Race","createManager","deleteManager","commonProps","horizontal","scrollEnabled","showsHorizontalScrollIndicator","snapToInterval","snapToAlignment","decelerationRate","scrollEventThrottle","removeClippedSubviews","listComponent","styles","container","background","content","ref","map","initialScrollIndex","windowSize","maxToRenderPerBatch","estimatedItemSize","dataSet","create","flex","position","top","left","right","bottom","backgroundColor"],"sourceRoot":"../../src","sources":["GestureImageViewer.tsx"],"mappings":";;AAAA,SAASA,WAAW,EAAEC,SAAS,EAAEC,OAAO,QAAQ,OAAO;AACvD,SAAwBC,QAAQ,EAAwBC,UAAU,EAAEC,mBAAmB,EAAEC,IAAI,QAAQ,cAAc;AACnH,SAASC,OAAO,EAAEC,eAAe,EAAEC,sBAAsB,QAAQ,8BAA8B;AAC/F,OAAOC,QAAQ,MAAM,yBAAyB;AAC9C,SAASC,QAAQ,QAAQ,0BAAuB;AAEhD,SAASC,qBAAqB,QAAQ,4BAAyB;AAC/D,SAASC,eAAe,EAAEC,cAAc,EAAEC,gBAAgB,QAAQ,YAAS;AAAC,SAAAC,GAAA,IAAAC,IAAA,EAAAC,IAAA,IAAAC,KAAA;AAE5E,MAAMC,YAAY,GAAGA,CAAA,KAAM;EACzB,IAAIjB,QAAQ,CAACkB,EAAE,KAAK,KAAK,EAAE;IACzB,OAAO,IAAI;EACb;EAEA,oBAAOJ,IAAA;IAAAK,QAAA,EAAQ;EAA6D,CAAQ,CAAC;AACvF,CAAC;AAED,OAAO,SAASC,kBAAkBA,CAAgC;EAChEC,EAAE,GAAG,SAAS;EACdC,IAAI;EACJC,WAAW;EACXC,eAAe;EACfC,aAAa;EACbC,KAAK,EAAEC,WAAW;EAClBC,SAAS;EACTC,aAAa,EAAEC,kBAAkB;EACjCC,cAAc;EACdC,YAAY,GAAG,CAAC;EAChBC,WAAW,GAAG,CAAC;EACf,GAAGC;AAC2B,CAAC,EAAE;EACjC,MAAMC,SAAS,GAAGV,aAAyC;EAE3D,MAAM;IAAEC,KAAK,EAAEU;EAAY,CAAC,GAAGlC,mBAAmB,CAAC,CAAC;EAEpD,MAAMwB,KAAK,GAAGC,WAAW,IAAIS,WAAW;EAExC,MAAM;IAAEC,OAAO;IAAEC,QAAQ;IAAEC,cAAc;IAAEC,WAAW;IAAEC,mBAAmB;IAAEC,aAAa;IAAEb;EAAc,CAAC,GACzGpB,qBAAqB,CAAC;IACpBY,EAAE;IACFC,IAAI;IACJI,KAAK;IACLM,YAAY;IACZC,WAAW;IACX,GAAGC;EACL,CAAC,CAAC;EAEJ,MAAMS,UAAU,GAAG9C,WAAW,CAC5B,CAAC;IAAE+C,IAAI;IAAEC;EAAkC,CAAC,KAAK;IAC/C,oBACE/B,IAAA,CAACX,IAAI;MAEH2C,KAAK,EAAE,CACL;QACEpB,KAAK,EAAEA,KAAK;QACZqB,MAAM,EAAE,MAAM;QACdC,cAAc,EAAE,QAAQ;QACxBC,UAAU,EAAE,QAAQ;QACpBC,gBAAgB,EAAEjB,WAAW,GAAG;MAClC,CAAC,CACD;MAAAd,QAAA,EAEDI,WAAW,CAACqB,IAAI,EAAEC,KAAK;IAAC,GAXpB,OAAOD,IAAI,KAAK,QAAQ,GAAGA,IAAI,GAAGC,KAYnC,CAAC;EAEX,CAAC,EACD,CAACnB,KAAK,EAAEO,WAAW,EAAEV,WAAW,CAClC,CAAC;EAED,MAAM4B,aAAa,GAAGtD,WAAW,CAC/B,CAACuD,CAAkC,EAAEP,KAAa,MAAM;IACtDQ,MAAM,EAAE3B,KAAK,GAAGO,WAAW;IAC3BqB,MAAM,EAAE,CAAC5B,KAAK,GAAGO,WAAW,IAAIY,KAAK;IACrCA;EACF,CAAC,CAAC,EACF,CAACnB,KAAK,EAAEO,WAAW,CACrB,CAAC;EAED,MAAMsB,YAAY,GAAG1D,WAAW,CAC9B,CAAC+C,IAAO,EAAEC,KAAa,KAAM,OAAOD,IAAI,KAAK,QAAQ,GAAGA,IAAI,GAAG,SAASC,KAAK,EAAG,EAChF,EACF,CAAC;EAED,MAAMW,OAAO,GAAGzD,OAAO,CAAC,MAAM;IAC5B,OAAOK,OAAO,CAACqD,IAAI,CAAClB,cAAc,EAAEC,WAAW,CAAC;EAClD,CAAC,EAAE,CAACA,WAAW,EAAED,cAAc,CAAC,CAAC;EAEjCzC,SAAS,CAAC,MAAM;IACdU,QAAQ,CAACkD,aAAa,CAACrC,EAAE,CAAC;IAE1B,OAAO,MAAMb,QAAQ,CAACmD,aAAa,CAACtC,EAAE,CAAC;EACzC,CAAC,EAAE,CAACA,EAAE,CAAC,CAAC;EAER,MAAMuC,WAA4B,GAAG7D,OAAO,CAC1C,OAAO;IACL8D,UAAU,EAAE,IAAI;IAChBC,aAAa,EAAE,CAACxB,QAAQ;IACxByB,8BAA8B,EAAE,KAAK;IACrCtB,mBAAmB,EAAEA,mBAAmB;IACxCuB,cAAc,EAAEtC,KAAK,GAAGO,WAAW;IACnCgC,eAAe,EAAE,QAAQ;IACzBC,gBAAgB,EAAE,MAAM;IACxBC,mBAAmB,EAAE,EAAE;IACvBC,qBAAqB,EAAE;EACzB,CAAC,CAAC,EACF,CAAC1C,KAAK,EAAEO,WAAW,EAAEK,QAAQ,EAAEG,mBAAmB,CACpD,CAAC;EAED,MAAM4B,aAAa,gBACjBvD,IAAA,CAACR,sBAAsB;IAAAa,QAAA,eACrBL,IAAA,CAACT,eAAe;MAACmD,OAAO,EAAEA,OAAQ;MAAArC,QAAA,eAChCH,KAAA,CAACb,IAAI;QAAC2C,KAAK,EAAE,CAACwB,MAAM,CAACC,SAAS,EAAExC,cAAc,CAAE;QAAAZ,QAAA,gBAC9CL,IAAA,CAACP,QAAQ,CAACJ,IAAI;UAAC2C,KAAK,EAAE,CAACwB,MAAM,CAACE,UAAU,EAAE1C,kBAAkB,EAAED,aAAa;QAAE,CAAE,CAAC,eAChFf,IAAA,CAACP,QAAQ,CAACJ,IAAI;UAAC2C,KAAK,EAAE,CAACwB,MAAM,CAACG,OAAO,EAAE/B,aAAa,CAAE;UAAAvB,QAAA,EACnDP,gBAAgB,CAACuB,SAAS,CAAC,gBAC1BrB,IAAA,CAACqB,SAAS;YAACuC,GAAG,EAAErC,OAAQ;YAAA,GAAKuB,WAAW;YAAA,GAAMhC,SAAS;YAAAT,QAAA,EACpDG,IAAI,CAACqD,GAAG,CAAC,CAAC/B,IAAI,EAAEC,KAAK,KAAKF,UAAU,CAAC;cAAEC,IAAI;cAAEC;YAAM,CAAC,CAAC;UAAC,CAC9C,CAAC,GAEZlC,cAAc,CAACwB,SAAS,CAAC,iBACvBrB,IAAA,CAACqB,SAAS;YACRuC,GAAG,EAAErC,OAAQ;YAAA,GACTuB,WAAW;YACftC,IAAI,EAAEA,IAAK;YACXqB,UAAU,EAAEA,UAAW;YACvBiC,kBAAkB,EAAE5C,YAAa;YACjCuB,YAAY,EAAEA,YAAa;YAC3BsB,UAAU,EAAE,CAAE;YACdC,mBAAmB,EAAE,CAAE;YACvB3B,aAAa,EAAEA,aAAc;YAAA,IACxBzC,eAAe,CAACyB,SAAS,CAAC,IAAI;cAAE4C,iBAAiB,EAAErD,KAAK,GAAGO;YAAY,CAAC;YAAA,IAExEjC,QAAQ,CAACkB,EAAE,KAAK,KAAK,IAAI;cAAE8D,OAAO,EAAE;gBAAE,oBAAoB,EAAE;cAAK;YAAE,CAAC;YAAA,GACrEpD;UAAS,CACd;QAEJ,CACY,CAAC,eAChBd,IAAA,CAACG,YAAY,IAAE,CAAC;MAAA,CACZ;IAAC,CACQ;EAAC,CACI,CACzB;EAED,OAAOO,eAAe,GAAGA,eAAe,CAAC6C,aAAa,CAAC,GAAGA,aAAa;AACzE;AAEA,MAAMC,MAAM,GAAGrE,UAAU,CAACgF,MAAM,CAAC;EAC/BV,SAAS,EAAE;IACTW,IAAI,EAAE;EACR,CAAC;EACDT,OAAO,EAAE;IACPS,IAAI,EAAE,CAAC;IACPxD,KAAK,EAAE,MAAM;IACbqB,MAAM,EAAE;EACV,CAAC;EACDyB,UAAU,EAAE;IACVW,QAAQ,EAAE,UAAU;IACpBC,GAAG,EAAE,CAAC;IACNC,IAAI,EAAE,CAAC;IACPC,KAAK,EAAE,CAAC;IACRC,MAAM,EAAE,CAAC;IACTC,eAAe,EAAE;EACnB;AACF,CAAC,CAAC","ignoreList":[]}
|
package/lib/module/utils.js
CHANGED
|
@@ -6,6 +6,12 @@ export const isScrollViewLike = component => {
|
|
|
6
6
|
return component === RNScrollView || component === GestureScrollView;
|
|
7
7
|
};
|
|
8
8
|
export const isFlatListLike = component => {
|
|
9
|
+
if (component === RNFlatList || component === GestureFlatList || isFlashListLike(component)) {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
return false;
|
|
13
|
+
};
|
|
14
|
+
export const isFlashListLike = component => {
|
|
9
15
|
try {
|
|
10
16
|
const FlashList = require('@shopify/flash-list')?.FlashList;
|
|
11
17
|
if (FlashList && component === FlashList) {
|
|
@@ -14,9 +20,6 @@ export const isFlatListLike = component => {
|
|
|
14
20
|
} catch {
|
|
15
21
|
// do nothing
|
|
16
22
|
}
|
|
17
|
-
if (component === RNFlatList || component === GestureFlatList) {
|
|
18
|
-
return true;
|
|
19
|
-
}
|
|
20
23
|
return component?.name === 'FlashList';
|
|
21
24
|
};
|
|
22
25
|
//# sourceMappingURL=utils.js.map
|
package/lib/module/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["FlatList","RNFlatList","ScrollView","RNScrollView","GestureFlatList","GestureScrollView","isScrollViewLike","component","isFlatListLike","FlashList","require","name"],"sourceRoot":"../../src","sources":["utils.ts"],"mappings":";;AAAA,SAASA,QAAQ,IAAIC,UAAU,EAAEC,UAAU,IAAIC,YAAY,QAAQ,cAAc;AACjF,SAASH,QAAQ,IAAII,eAAe,EAAEF,UAAU,IAAIG,iBAAiB,QAAQ,8BAA8B;
|
|
1
|
+
{"version":3,"names":["FlatList","RNFlatList","ScrollView","RNScrollView","GestureFlatList","GestureScrollView","isScrollViewLike","component","isFlatListLike","isFlashListLike","FlashList","require","name"],"sourceRoot":"../../src","sources":["utils.ts"],"mappings":";;AAAA,SAASA,QAAQ,IAAIC,UAAU,EAAEC,UAAU,IAAIC,YAAY,QAAQ,cAAc;AACjF,SAASH,QAAQ,IAAII,eAAe,EAAEF,UAAU,IAAIG,iBAAiB,QAAQ,8BAA8B;AAG3G,OAAO,MAAMC,gBAAgB,GAAIC,SAAc,IAAuC;EACpF,OAAOA,SAAS,KAAKJ,YAAY,IAAII,SAAS,KAAKF,iBAAiB;AACtE,CAAC;AAED,OAAO,MAAMG,cAAc,GAAID,SAAc,IAAqC;EAChF,IAAIA,SAAS,KAAKN,UAAU,IAAIM,SAAS,KAAKH,eAAe,IAAIK,eAAe,CAACF,SAAS,CAAC,EAAE;IAC3F,OAAO,IAAI;EACb;EAEA,OAAO,KAAK;AACd,CAAC;AAED,OAAO,MAAME,eAAe,GAAIF,SAAc,IAAuB;EACnE,IAAI;IACF,MAAMG,SAAS,GAAGC,OAAO,CAAC,qBAAqB,CAAC,EAAED,SAAS;IAE3D,IAAIA,SAAS,IAAIH,SAAS,KAAKG,SAAS,EAAE;MACxC,OAAO,IAAI;IACb;EACF,CAAC,CAAC,MAAM;IACN;EAAA;EAGF,OAAOH,SAAS,EAAEK,IAAI,KAAK,WAAW;AACxC,CAAC","ignoreList":[]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type FlatList } from 'react-native';
|
|
1
2
|
import type { GestureImageViewerProps } from './types';
|
|
2
|
-
export declare function GestureImageViewer<T = any>({ id, data, renderImage, renderContainer, ListComponent, width: customWidth, listProps, backdropStyle: backdropStyleProps, containerStyle, initialIndex, itemSpacing, ...props }: GestureImageViewerProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
3
|
+
export declare function GestureImageViewer<T = any, LC = typeof FlatList>({ id, data, renderImage, renderContainer, ListComponent, width: customWidth, listProps, backdropStyle: backdropStyleProps, containerStyle, initialIndex, itemSpacing, ...props }: GestureImageViewerProps<T, LC>): import("react/jsx-runtime").JSX.Element;
|
|
3
4
|
//# sourceMappingURL=GestureImageViewer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GestureImageViewer.d.ts","sourceRoot":"","sources":["../../../src/GestureImageViewer.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"GestureImageViewer.d.ts","sourceRoot":"","sources":["../../../src/GestureImageViewer.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,QAAQ,EAAyE,MAAM,cAAc,CAAC;AAIpH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAYvD,wBAAgB,kBAAkB,CAAC,CAAC,GAAG,GAAG,EAAE,EAAE,GAAG,OAAO,QAAQ,EAAE,EAChE,EAAc,EACd,IAAI,EACJ,WAAW,EACX,eAAe,EACf,aAAa,EACb,KAAK,EAAE,WAAW,EAClB,SAAS,EACT,aAAa,EAAE,kBAAkB,EACjC,cAAc,EACd,YAAgB,EAChB,WAAe,EACf,GAAG,KAAK,EACT,EAAE,uBAAuB,CAAC,CAAC,EAAE,EAAE,CAAC,2CAmHhC"}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type {
|
|
3
|
-
|
|
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
|
+
export type FlatListComponent = typeof RNFlatList | typeof GHFlatList;
|
|
5
|
+
export type ScrollViewComponent = typeof RNScrollView | typeof GHScrollView;
|
|
6
|
+
type GetComponentProps<T> = T extends React.ComponentType<infer P> ? P : never;
|
|
7
|
+
type ConditionalListProps<LC> = LC extends FlatListComponent ? React.ComponentProps<LC> : LC extends ScrollViewComponent ? React.ComponentProps<LC> : GetComponentProps<LC>;
|
|
8
|
+
export interface GestureImageViewerProps<T = any, LC = typeof RNFlatList> {
|
|
4
9
|
id?: string;
|
|
5
10
|
data: T[];
|
|
6
11
|
initialIndex?: number;
|
|
@@ -8,15 +13,13 @@ export interface GestureImageViewerProps<T = any> {
|
|
|
8
13
|
onDismiss?: () => void;
|
|
9
14
|
renderImage: (item: T, index: number) => React.ReactElement;
|
|
10
15
|
renderContainer?: (children: React.ReactElement) => React.ReactElement;
|
|
11
|
-
ListComponent
|
|
16
|
+
ListComponent: LC;
|
|
12
17
|
width?: number;
|
|
13
18
|
dismissThreshold?: number;
|
|
14
|
-
swipeThreshold?: number;
|
|
15
|
-
velocityThreshold?: number;
|
|
16
19
|
enableDismissGesture?: boolean;
|
|
17
20
|
enableSwipeGesture?: boolean;
|
|
18
21
|
resistance?: number;
|
|
19
|
-
listProps?:
|
|
22
|
+
listProps?: Partial<ConditionalListProps<LC>>;
|
|
20
23
|
backdropStyle?: StyleProp<ViewStyle>;
|
|
21
24
|
containerStyle?: StyleProp<ViewStyle>;
|
|
22
25
|
animateBackdrop?: boolean;
|
|
@@ -26,10 +29,5 @@ export interface GestureImageViewerProps<T = any> {
|
|
|
26
29
|
maxZoomScale?: number;
|
|
27
30
|
itemSpacing?: number;
|
|
28
31
|
}
|
|
29
|
-
export
|
|
30
|
-
currentIndex: number;
|
|
31
|
-
translateY: SharedValue<number>;
|
|
32
|
-
isGestureActive: boolean;
|
|
33
|
-
lockDirection: 'horizontal' | 'vertical' | null;
|
|
34
|
-
}
|
|
32
|
+
export {};
|
|
35
33
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,QAAQ,IAAI,UAAU,EAAE,UAAU,IAAI,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC7G,OAAO,KAAK,EAAE,QAAQ,IAAI,UAAU,EAAE,UAAU,IAAI,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAEvG,MAAM,MAAM,iBAAiB,GAAG,OAAO,UAAU,GAAG,OAAO,UAAU,CAAC;AACtE,MAAM,MAAM,mBAAmB,GAAG,OAAO,YAAY,GAAG,OAAO,YAAY,CAAC;AAE5E,KAAK,iBAAiB,CAAC,CAAC,IAAI,CAAC,SAAS,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAE/E,KAAK,oBAAoB,CAAC,EAAE,IAAI,EAAE,SAAS,iBAAiB,GACxD,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC,GACxB,EAAE,SAAS,mBAAmB,GAC5B,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC,GACxB,iBAAiB,CAAC,EAAE,CAAC,CAAC;AAE5B,MAAM,WAAW,uBAAuB,CAAC,CAAC,GAAG,GAAG,EAAE,EAAE,GAAG,OAAO,UAAU;IACtE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,YAAY,CAAC;IAC5D,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC,YAAY,KAAK,KAAK,CAAC,YAAY,CAAC;IACvE,aAAa,EAAE,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAG1B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9C,aAAa,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACrC,cAAc,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACtC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare const isScrollViewLike: (component: any) => component is
|
|
3
|
-
export declare const isFlatListLike: (component: any) => component is
|
|
1
|
+
import type { FlatListComponent, ScrollViewComponent } from './types';
|
|
2
|
+
export declare const isScrollViewLike: (component: any) => component is ScrollViewComponent;
|
|
3
|
+
export declare const isFlatListLike: (component: any) => component is FlatListComponent;
|
|
4
|
+
export declare const isFlashListLike: (component: any) => component is any;
|
|
4
5
|
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAEtE,eAAO,MAAM,gBAAgB,GAAI,WAAW,GAAG,KAAG,SAAS,IAAI,mBAE9D,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,WAAW,GAAG,KAAG,SAAS,IAAI,iBAM5D,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,WAAW,GAAG,KAAG,SAAS,IAAI,GAY7D,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-gesture-image-viewer",
|
|
3
|
-
"version": "0.5.
|
|
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,163 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo } from 'react';
|
|
2
|
-
import { Platform, type ScrollViewProps, StyleSheet, useWindowDimensions, View } from 'react-native';
|
|
3
|
-
import { FlatList, 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 { 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>({
|
|
19
|
-
id = 'default',
|
|
20
|
-
data,
|
|
21
|
-
renderImage,
|
|
22
|
-
renderContainer,
|
|
23
|
-
ListComponent = FlatList,
|
|
24
|
-
width: customWidth,
|
|
25
|
-
listProps,
|
|
26
|
-
backdropStyle: backdropStyleProps,
|
|
27
|
-
containerStyle,
|
|
28
|
-
initialIndex = 0,
|
|
29
|
-
itemSpacing = 0,
|
|
30
|
-
...props
|
|
31
|
-
}: GestureImageViewerProps<T>) {
|
|
32
|
-
const { width: screenWidth } = useWindowDimensions();
|
|
33
|
-
|
|
34
|
-
const width = customWidth || screenWidth;
|
|
35
|
-
|
|
36
|
-
const { listRef, isZoomed, dismissGesture, zoomGesture, onMomentumScrollEnd, animatedStyle, backdropStyle } =
|
|
37
|
-
useGestureImageViewer({
|
|
38
|
-
id,
|
|
39
|
-
data,
|
|
40
|
-
width,
|
|
41
|
-
initialIndex,
|
|
42
|
-
itemSpacing,
|
|
43
|
-
...props,
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
const renderItem = useCallback(
|
|
47
|
-
({ item, index }: { item: T; index: number }) => {
|
|
48
|
-
return (
|
|
49
|
-
<View
|
|
50
|
-
key={typeof item === 'string' ? item : index}
|
|
51
|
-
style={[
|
|
52
|
-
{
|
|
53
|
-
width: width,
|
|
54
|
-
height: '100%',
|
|
55
|
-
justifyContent: 'center',
|
|
56
|
-
alignItems: 'center',
|
|
57
|
-
marginHorizontal: itemSpacing / 2,
|
|
58
|
-
},
|
|
59
|
-
]}
|
|
60
|
-
>
|
|
61
|
-
{renderImage(item, index)}
|
|
62
|
-
</View>
|
|
63
|
-
);
|
|
64
|
-
},
|
|
65
|
-
[width, itemSpacing, renderImage],
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
const getItemLayout = useCallback(
|
|
69
|
-
(_: any, index: number) => ({
|
|
70
|
-
length: width + itemSpacing,
|
|
71
|
-
offset: (width + itemSpacing) * index,
|
|
72
|
-
index,
|
|
73
|
-
}),
|
|
74
|
-
[width, itemSpacing],
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
const keyExtractor = useCallback(
|
|
78
|
-
(item: T, index: number) => (typeof item === 'string' ? item : `image-${index}`),
|
|
79
|
-
[],
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
const gesture = useMemo(() => {
|
|
83
|
-
return Gesture.Race(dismissGesture, zoomGesture);
|
|
84
|
-
}, [zoomGesture, dismissGesture]);
|
|
85
|
-
|
|
86
|
-
useEffect(() => {
|
|
87
|
-
registry.createManager(id);
|
|
88
|
-
|
|
89
|
-
return () => registry.deleteManager(id);
|
|
90
|
-
}, [id]);
|
|
91
|
-
|
|
92
|
-
const commonProps: ScrollViewProps = useMemo(
|
|
93
|
-
() => ({
|
|
94
|
-
horizontal: true,
|
|
95
|
-
scrollEnabled: !isZoomed,
|
|
96
|
-
showsHorizontalScrollIndicator: false,
|
|
97
|
-
onMomentumScrollEnd: onMomentumScrollEnd,
|
|
98
|
-
snapToInterval: width + itemSpacing,
|
|
99
|
-
snapToAlignment: 'center',
|
|
100
|
-
decelerationRate: 'fast',
|
|
101
|
-
scrollEventThrottle: 16,
|
|
102
|
-
removeClippedSubviews: true,
|
|
103
|
-
}),
|
|
104
|
-
[width, itemSpacing, isZoomed, onMomentumScrollEnd],
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
const listComponent = (
|
|
108
|
-
<GestureHandlerRootView>
|
|
109
|
-
<GestureDetector gesture={gesture}>
|
|
110
|
-
<View style={[styles.container, containerStyle]}>
|
|
111
|
-
<Animated.View style={[styles.background, backdropStyleProps, backdropStyle]} />
|
|
112
|
-
<Animated.View style={[styles.content, animatedStyle]}>
|
|
113
|
-
{isScrollViewLike(ListComponent) ? (
|
|
114
|
-
<ListComponent ref={listRef} {...commonProps} {...listProps}>
|
|
115
|
-
{data.map((item, index) => renderItem({ item, index }))}
|
|
116
|
-
</ListComponent>
|
|
117
|
-
) : (
|
|
118
|
-
isFlatListLike(ListComponent) && (
|
|
119
|
-
<ListComponent
|
|
120
|
-
ref={listRef}
|
|
121
|
-
{...commonProps}
|
|
122
|
-
data={data}
|
|
123
|
-
renderItem={renderItem}
|
|
124
|
-
initialScrollIndex={initialIndex}
|
|
125
|
-
keyExtractor={keyExtractor}
|
|
126
|
-
estimatedItemSize={width + itemSpacing}
|
|
127
|
-
windowSize={3}
|
|
128
|
-
maxToRenderPerBatch={3}
|
|
129
|
-
getItemLayout={getItemLayout}
|
|
130
|
-
// NOTE - https://github.com/necolas/react-native-web/issues/1299
|
|
131
|
-
{...(Platform.OS === 'web' && { dataSet: { 'paging-enabled-fix': true } })}
|
|
132
|
-
{...listProps}
|
|
133
|
-
/>
|
|
134
|
-
)
|
|
135
|
-
)}
|
|
136
|
-
</Animated.View>
|
|
137
|
-
<WebPagingFix />
|
|
138
|
-
</View>
|
|
139
|
-
</GestureDetector>
|
|
140
|
-
</GestureHandlerRootView>
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
return renderContainer ? renderContainer(listComponent) : listComponent;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const styles = StyleSheet.create({
|
|
147
|
-
container: {
|
|
148
|
-
flex: 1,
|
|
149
|
-
},
|
|
150
|
-
content: {
|
|
151
|
-
flex: 1,
|
|
152
|
-
width: '100%',
|
|
153
|
-
height: '100%',
|
|
154
|
-
},
|
|
155
|
-
background: {
|
|
156
|
-
position: 'absolute',
|
|
157
|
-
top: 0,
|
|
158
|
-
left: 0,
|
|
159
|
-
right: 0,
|
|
160
|
-
bottom: 0,
|
|
161
|
-
backgroundColor: 'black',
|
|
162
|
-
},
|
|
163
|
-
});
|
|
@@ -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
package/src/types.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { StyleProp, ViewStyle } from 'react-native';
|
|
2
|
-
import type { SharedValue } from 'react-native-reanimated';
|
|
3
|
-
|
|
4
|
-
export interface GestureImageViewerProps<T = any> {
|
|
5
|
-
id?: string;
|
|
6
|
-
data: T[];
|
|
7
|
-
initialIndex?: number;
|
|
8
|
-
onIndexChange?: (index: number) => void;
|
|
9
|
-
onDismiss?: () => void;
|
|
10
|
-
renderImage: (item: T, index: number) => React.ReactElement;
|
|
11
|
-
renderContainer?: (children: React.ReactElement) => React.ReactElement;
|
|
12
|
-
ListComponent?: any; // FlatList, FlashList 등
|
|
13
|
-
width?: number;
|
|
14
|
-
dismissThreshold?: number;
|
|
15
|
-
swipeThreshold?: number;
|
|
16
|
-
velocityThreshold?: number;
|
|
17
|
-
enableDismissGesture?: boolean;
|
|
18
|
-
enableSwipeGesture?: boolean;
|
|
19
|
-
resistance?: number;
|
|
20
|
-
listProps?: any; // FlatList, FlashList에 전달할 추가 props
|
|
21
|
-
backdropStyle?: StyleProp<ViewStyle>;
|
|
22
|
-
containerStyle?: StyleProp<ViewStyle>;
|
|
23
|
-
animateBackdrop?: boolean;
|
|
24
|
-
enableZoomPanGesture?: boolean;
|
|
25
|
-
enableZoomGesture?: boolean;
|
|
26
|
-
enableDoubleTapGesture?: boolean;
|
|
27
|
-
maxZoomScale?: number;
|
|
28
|
-
itemSpacing?: number;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface GestureState {
|
|
32
|
-
currentIndex: number;
|
|
33
|
-
translateY: SharedValue<number>;
|
|
34
|
-
isGestureActive: boolean;
|
|
35
|
-
lockDirection: 'horizontal' | 'vertical' | null;
|
|
36
|
-
}
|
|
@@ -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,24 +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
|
-
|
|
4
|
-
export const isScrollViewLike = (component: any): component is typeof RNScrollView => {
|
|
5
|
-
return component === RNScrollView || component === GestureScrollView;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export const isFlatListLike = (component: any): component is typeof RNFlatList => {
|
|
9
|
-
try {
|
|
10
|
-
const FlashList = require('@shopify/flash-list')?.FlashList;
|
|
11
|
-
|
|
12
|
-
if (FlashList && component === FlashList) {
|
|
13
|
-
return true;
|
|
14
|
-
}
|
|
15
|
-
} catch {
|
|
16
|
-
// do nothing
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (component === RNFlatList || component === GestureFlatList) {
|
|
20
|
-
return true;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return component?.name === 'FlashList';
|
|
24
|
-
};
|