react-native-gesture-image-viewer 2.4.0 → 2.5.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.
- package/README.md +19 -5
- package/lib/module/GestureViewer.js +31 -13
- package/lib/module/GestureViewer.js.map +1 -1
- package/lib/module/GestureViewerManager.js +43 -14
- package/lib/module/GestureViewerManager.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/itemDimensions.js +86 -0
- package/lib/module/itemDimensions.js.map +1 -0
- package/lib/module/useGestureViewer.js +244 -32
- package/lib/module/useGestureViewer.js.map +1 -1
- package/lib/module/useGestureViewerPaging.js +1 -3
- package/lib/module/useGestureViewerPaging.js.map +1 -1
- package/lib/module/useGestureViewerPaging.web.js +26 -18
- package/lib/module/useGestureViewerPaging.web.js.map +1 -1
- package/lib/module/utils/index.js +43 -10
- package/lib/module/utils/index.js.map +1 -1
- package/lib/module/utils/tapZoom.js +51 -13
- package/lib/module/utils/tapZoom.js.map +1 -1
- package/lib/typescript/src/GestureViewer.d.ts.map +1 -1
- package/lib/typescript/src/GestureViewerManager.d.ts +16 -3
- package/lib/typescript/src/GestureViewerManager.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/itemDimensions.d.ts +26 -0
- package/lib/typescript/src/itemDimensions.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +29 -0
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/useGestureViewer.d.ts +3 -2
- package/lib/typescript/src/useGestureViewer.d.ts.map +1 -1
- package/lib/typescript/src/useGestureViewerPaging.d.ts.map +1 -1
- package/lib/typescript/src/useGestureViewerPaging.types.d.ts +2 -0
- package/lib/typescript/src/useGestureViewerPaging.types.d.ts.map +1 -1
- package/lib/typescript/src/useGestureViewerPaging.web.d.ts +1 -1
- package/lib/typescript/src/useGestureViewerPaging.web.d.ts.map +1 -1
- package/lib/typescript/src/utils/index.d.ts +7 -3
- package/lib/typescript/src/utils/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/tapZoom.d.ts +17 -1
- package/lib/typescript/src/utils/tapZoom.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/GestureViewer.tsx +47 -16
- package/src/GestureViewerManager.ts +58 -19
- package/src/index.tsx +4 -0
- package/src/itemDimensions.ts +162 -0
- package/src/types.ts +39 -0
- package/src/useGestureViewer.ts +352 -36
- package/src/useGestureViewerPaging.ts +1 -3
- package/src/useGestureViewerPaging.types.ts +2 -0
- package/src/useGestureViewerPaging.web.ts +41 -21
- package/src/utils/index.ts +86 -27
- package/src/utils/tapZoom.ts +66 -13
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { UseGestureViewerPagingArgs, UseGestureViewerPagingResult } from './useGestureViewerPaging.types';
|
|
2
|
-
export declare function useGestureViewerPaging({ adjustedInitialIndex, autoPlay, autoPlayInterval, currentIndex, dataLength, enableDoubleTapZoom, enableHorizontalSwipe, enableLoop, height, onSingleTap, isRotated, isZoomed, itemSpacing, manager, maxZoomScale, scale, scrollTo, syncCurrentIndex, syncPendingIndex, translateX, translateY, width, }: UseGestureViewerPagingArgs): UseGestureViewerPagingResult;
|
|
2
|
+
export declare function useGestureViewerPaging({ adjustedInitialIndex, autoPlay, autoPlayInterval, contentHeight, contentWidth, currentIndex, dataLength, enableDoubleTapZoom, enableHorizontalSwipe, enableLoop, height, onSingleTap, isRotated, isZoomed, itemSpacing, manager, maxZoomScale, scale, scrollTo, syncCurrentIndex, syncPendingIndex, translateX, translateY, width, }: UseGestureViewerPagingArgs): UseGestureViewerPagingResult;
|
|
3
3
|
//# sourceMappingURL=useGestureViewerPaging.web.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useGestureViewerPaging.web.d.ts","sourceRoot":"","sources":["../../../src/useGestureViewerPaging.web.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,0BAA0B,EAC1B,4BAA4B,EAE7B,MAAM,gCAAgC,CAAC;AAqBxC,wBAAgB,sBAAsB,CAAC,EACrC,oBAAoB,EACpB,QAAQ,EACR,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,mBAAmB,EACnB,qBAAqB,EACrB,UAAU,EACV,MAAM,EACN,WAAW,EACX,SAAS,EACT,QAAQ,EACR,WAAW,EACX,OAAO,EACP,YAAY,EACZ,KAAK,EACL,QAAQ,EACR,gBAAgB,EAChB,gBAAgB,EAChB,UAAU,EACV,UAAU,EACV,KAAK,GACN,EAAE,0BAA0B,GAAG,4BAA4B,
|
|
1
|
+
{"version":3,"file":"useGestureViewerPaging.web.d.ts","sourceRoot":"","sources":["../../../src/useGestureViewerPaging.web.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,0BAA0B,EAC1B,4BAA4B,EAE7B,MAAM,gCAAgC,CAAC;AAqBxC,wBAAgB,sBAAsB,CAAC,EACrC,oBAAoB,EACpB,QAAQ,EACR,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,mBAAmB,EACnB,qBAAqB,EACrB,UAAU,EACV,MAAM,EACN,WAAW,EACX,SAAS,EACT,QAAQ,EACR,WAAW,EACX,OAAO,EACP,YAAY,EACZ,KAAK,EACL,QAAQ,EACR,gBAAgB,EAChB,gBAAgB,EAChB,UAAU,EACV,UAAU,EACV,KAAK,GACN,EAAE,0BAA0B,GAAG,4BAA4B,CAma3D"}
|
|
@@ -9,10 +9,13 @@ export declare const isFlashListLike: (component: React.ComponentType<any>) => b
|
|
|
9
9
|
* and we also avoid wrapping RNGH-provided scrollables to prevent double-applying Gesture.Native() to components that already participate in RNGH.
|
|
10
10
|
*/
|
|
11
11
|
export declare const shouldUseNativeScrollGesture: (platformOS: PlatformOSType, component: React.ComponentType<any>) => boolean;
|
|
12
|
-
export declare const
|
|
12
|
+
export declare const clampIndex: (index: number | undefined, dataLength: number) => number;
|
|
13
|
+
export declare const resolveGeometrySyncTranslationMode: (previousIndex: number | null, nextIndex: number, scale: number) => "constrain" | "none" | "reset";
|
|
14
|
+
export declare const clampTranslationToBounds: ({ width, height, contentWidth, contentHeight, scale, translateX, translateY, }: {
|
|
13
15
|
width: number;
|
|
14
16
|
height: number;
|
|
15
|
-
|
|
17
|
+
contentWidth?: number;
|
|
18
|
+
contentHeight?: number;
|
|
16
19
|
translateX: number;
|
|
17
20
|
translateY: number;
|
|
18
21
|
scale: number;
|
|
@@ -20,7 +23,8 @@ export declare const createBoundsConstraint: ({ width, height }: {
|
|
|
20
23
|
translateX: number;
|
|
21
24
|
translateY: number;
|
|
22
25
|
};
|
|
23
|
-
export declare const createLoopData: <T>(
|
|
26
|
+
export declare const createLoopData: <T>(data: T[], enableLoop: boolean) => T[];
|
|
27
|
+
export declare const getLoopPhysicalIndex: (logicalIndex: number, dataLength: number, enableLoop: boolean) => number;
|
|
24
28
|
export declare const getLoopAdjustedIndex: (scrollIndex: number, originalDataLength: number, enableLoop: boolean) => {
|
|
25
29
|
realIndex: number;
|
|
26
30
|
needsJump: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,cAAc,CAAC;AAMtB,OAAO,KAAK,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAIvE,eAAO,MAAM,gBAAgB,GAC3B,WAAW,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,KAClC,SAAS,IAAI,mBAC+C,CAAC;AAEhE,eAAO,MAAM,cAAc,GACzB,WAAW,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,KAClC,SAAS,IAAI,iBAAiB,CAAC,GAAG,CAMpC,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,WAAW,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,KAAG,OAMrE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,4BAA4B,GACvC,YAAY,cAAc,EAC1B,WAAW,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,KAClC,OAEF,CAAC;AAEF,eAAO,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,cAAc,CAAC;AAMtB,OAAO,KAAK,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAIvE,eAAO,MAAM,gBAAgB,GAC3B,WAAW,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,KAClC,SAAS,IAAI,mBAC+C,CAAC;AAEhE,eAAO,MAAM,cAAc,GACzB,WAAW,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,KAClC,SAAS,IAAI,iBAAiB,CAAC,GAAG,CAMpC,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,WAAW,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,KAAG,OAMrE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,4BAA4B,GACvC,YAAY,cAAc,EAC1B,WAAW,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,KAClC,OAEF,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,OAAO,MAAM,GAAG,SAAS,EAAE,YAAY,MAAM,KAAG,MAY1E,CAAC;AAEF,eAAO,MAAM,kCAAkC,GAC7C,eAAe,MAAM,GAAG,IAAI,EAC5B,WAAW,MAAM,EACjB,OAAO,MAAM,KACZ,WAAW,GAAG,MAAM,GAAG,OAMzB,CAAC;AAoBF,eAAO,MAAM,wBAAwB,GAAI,gFAQtC;IACD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;;;CAsBA,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,CAAC,EAAE,MAAM,CAAC,EAAE,EAAE,YAAY,OAAO,KAAG,CAAC,EAanE,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,cAAc,MAAM,EACpB,YAAY,MAAM,EAClB,YAAY,OAAO,KAClB,MAA0E,CAAC;AAE9E,eAAO,MAAM,oBAAoB,GAC/B,aAAa,MAAM,EACnB,oBAAoB,MAAM,EAC1B,YAAY,OAAO,KAClB;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAoB/D,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,SAAS,GAAG,EAAE,OAAO,MAAM;sBAC1C,MAAM,YAAY,OAAO;CAO3C,CAAC"}
|
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
import type { SharedValue } from 'react-native-reanimated';
|
|
2
|
-
export declare const
|
|
2
|
+
export declare const getTapZoomTarget: ({ x, y, width, height, contentWidth, contentHeight, maxZoomScale, scale, }: {
|
|
3
3
|
x: number;
|
|
4
4
|
y: number;
|
|
5
5
|
width: number;
|
|
6
6
|
height: number;
|
|
7
|
+
contentWidth?: number;
|
|
8
|
+
contentHeight?: number;
|
|
9
|
+
maxZoomScale: number;
|
|
10
|
+
scale: number;
|
|
11
|
+
}) => {
|
|
12
|
+
scale: number;
|
|
13
|
+
translateX: number;
|
|
14
|
+
translateY: number;
|
|
15
|
+
};
|
|
16
|
+
export declare const applyTapZoomAtPoint: ({ x, y, width, height, contentWidth, contentHeight, maxZoomScale, scale, translateX, translateY, }: {
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
contentWidth?: number;
|
|
22
|
+
contentHeight?: number;
|
|
7
23
|
maxZoomScale: number;
|
|
8
24
|
scale: SharedValue<number>;
|
|
9
25
|
translateX: SharedValue<number>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tapZoom.d.ts","sourceRoot":"","sources":["../../../../src/utils/tapZoom.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"tapZoom.d.ts","sourceRoot":"","sources":["../../../../src/utils/tapZoom.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAK3D,eAAO,MAAM,gBAAgB,GAAI,4EAS9B;IACD,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf;;;;CA2BA,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,oGAWjC;IACD,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC3B,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAChC,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACjC,SAsBA,CAAC"}
|
package/package.json
CHANGED
package/src/GestureViewer.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo,
|
|
1
|
+
import { useCallback, useEffect, useMemo, type ReactElement } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Platform,
|
|
4
4
|
type ScrollViewProps,
|
|
@@ -10,10 +10,16 @@ import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-g
|
|
|
10
10
|
import Animated from 'react-native-reanimated';
|
|
11
11
|
|
|
12
12
|
import { registry } from './GestureViewerRegistry';
|
|
13
|
-
import type {
|
|
13
|
+
import type {
|
|
14
|
+
GestureViewerItemDimensions,
|
|
15
|
+
GestureViewerProps,
|
|
16
|
+
GestureViewerRenderItemInfo,
|
|
17
|
+
} from './types';
|
|
14
18
|
import { useGestureViewer } from './useGestureViewer';
|
|
15
19
|
import {
|
|
20
|
+
clampIndex,
|
|
16
21
|
createLoopData,
|
|
22
|
+
getLoopPhysicalIndex,
|
|
17
23
|
isFlashListLike,
|
|
18
24
|
isFlatListLike,
|
|
19
25
|
isScrollViewLike,
|
|
@@ -21,6 +27,31 @@ import {
|
|
|
21
27
|
} from './utils';
|
|
22
28
|
import WebPagingFixStyle from './WebPagingFixStyle';
|
|
23
29
|
|
|
30
|
+
type GestureViewerItemCellProps<ItemT> = {
|
|
31
|
+
item: ItemT;
|
|
32
|
+
index: number;
|
|
33
|
+
isActive: boolean;
|
|
34
|
+
renderItem: (item: ItemT, index: number, info: GestureViewerRenderItemInfo) => ReactElement;
|
|
35
|
+
setItemDimensions: (index: number, item: ItemT, dimensions: GestureViewerItemDimensions) => void;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function GestureViewerItemCell<ItemT>({
|
|
39
|
+
item,
|
|
40
|
+
index,
|
|
41
|
+
isActive,
|
|
42
|
+
renderItem,
|
|
43
|
+
setItemDimensions,
|
|
44
|
+
}: GestureViewerItemCellProps<ItemT>) {
|
|
45
|
+
const registerItemDimensions = (dimensions: GestureViewerItemDimensions) => {
|
|
46
|
+
setItemDimensions(index, item, dimensions);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return renderItem(item, index, {
|
|
50
|
+
isActive,
|
|
51
|
+
setItemDimensions: registerItemDimensions,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
24
55
|
export function GestureViewer<ItemT, LC>({
|
|
25
56
|
id = 'default',
|
|
26
57
|
data,
|
|
@@ -40,14 +71,14 @@ export function GestureViewer<ItemT, LC>({
|
|
|
40
71
|
}: GestureViewerProps<ItemT, LC>) {
|
|
41
72
|
const Component = ListComponent as React.ComponentType<any>;
|
|
42
73
|
|
|
43
|
-
const dataRef = useRef(data);
|
|
44
|
-
|
|
45
74
|
const { width: screenWidth, height: screenHeight } = useWindowDimensions();
|
|
46
75
|
|
|
47
76
|
const width = customWidth || screenWidth;
|
|
48
77
|
const height = customHeight || screenHeight;
|
|
78
|
+
const normalizedInitialIndex = clampIndex(initialIndex, data.length);
|
|
79
|
+
const initialListIndex = getLoopPhysicalIndex(normalizedInitialIndex, data.length, enableLoop);
|
|
49
80
|
|
|
50
|
-
const loopData = useMemo(() => createLoopData(
|
|
81
|
+
const loopData = useMemo(() => createLoopData(data, enableLoop), [data, enableLoop]);
|
|
51
82
|
|
|
52
83
|
const isScrollView = isScrollViewLike(Component);
|
|
53
84
|
const isFlashList = isFlashListLike(Component);
|
|
@@ -68,12 +99,13 @@ export function GestureViewer<ItemT, LC>({
|
|
|
68
99
|
animatedStyle,
|
|
69
100
|
backdropStyle,
|
|
70
101
|
handleDismiss,
|
|
102
|
+
setItemDimensions,
|
|
71
103
|
} = useGestureViewer({
|
|
72
104
|
id,
|
|
73
105
|
data,
|
|
74
106
|
width,
|
|
75
107
|
height,
|
|
76
|
-
initialIndex,
|
|
108
|
+
initialIndex: normalizedInitialIndex,
|
|
77
109
|
itemSpacing,
|
|
78
110
|
enableLoop,
|
|
79
111
|
...props,
|
|
@@ -106,9 +138,13 @@ export function GestureViewer<ItemT, LC>({
|
|
|
106
138
|
styles.item,
|
|
107
139
|
]}
|
|
108
140
|
>
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
141
|
+
<GestureViewerItemCell
|
|
142
|
+
index={index}
|
|
143
|
+
isActive={index === activeListIndex && isFlashListCell}
|
|
144
|
+
item={item}
|
|
145
|
+
renderItem={renderItemProp}
|
|
146
|
+
setItemDimensions={setItemDimensions}
|
|
147
|
+
/>
|
|
112
148
|
</View>
|
|
113
149
|
);
|
|
114
150
|
},
|
|
@@ -121,6 +157,7 @@ export function GestureViewer<ItemT, LC>({
|
|
|
121
157
|
isScrollView,
|
|
122
158
|
isFlashList,
|
|
123
159
|
height,
|
|
160
|
+
setItemDimensions,
|
|
124
161
|
],
|
|
125
162
|
);
|
|
126
163
|
|
|
@@ -137,10 +174,6 @@ export function GestureViewer<ItemT, LC>({
|
|
|
137
174
|
return Gesture.Race(dismissGesture, zoomGesture);
|
|
138
175
|
}, [zoomGesture, dismissGesture]);
|
|
139
176
|
|
|
140
|
-
useEffect(() => {
|
|
141
|
-
dataRef.current = data;
|
|
142
|
-
}, [data]);
|
|
143
|
-
|
|
144
177
|
useEffect(() => {
|
|
145
178
|
registry.createManager(id);
|
|
146
179
|
|
|
@@ -225,9 +258,7 @@ export function GestureViewer<ItemT, LC>({
|
|
|
225
258
|
{...commonProps}
|
|
226
259
|
data={loopData}
|
|
227
260
|
renderItem={renderItem}
|
|
228
|
-
initialScrollIndex={
|
|
229
|
-
enableLoop && data.length > 1 ? initialIndex + 1 : initialIndex
|
|
230
|
-
}
|
|
261
|
+
initialScrollIndex={initialListIndex}
|
|
231
262
|
keyExtractor={keyExtractor}
|
|
232
263
|
{...(isFlashList
|
|
233
264
|
? // NOTE - Deprecated estimatedItemSize for FlashList V2 (https://shopify.github.io/flash-list/docs/v2-changes#deprecated)
|
|
@@ -6,12 +6,22 @@ import type {
|
|
|
6
6
|
GestureViewerEventType,
|
|
7
7
|
GestureViewerState,
|
|
8
8
|
} from './types';
|
|
9
|
-
import {
|
|
9
|
+
import { clampTranslationToBounds, createScrollAction } from './utils';
|
|
10
|
+
|
|
11
|
+
type ZoomSharedValuesOptions = {
|
|
12
|
+
scale: SharedValue<number>;
|
|
13
|
+
translateX: SharedValue<number>;
|
|
14
|
+
translateY: SharedValue<number>;
|
|
15
|
+
maxZoomScale: number;
|
|
16
|
+
contentWidth?: SharedValue<number>;
|
|
17
|
+
contentHeight?: SharedValue<number>;
|
|
18
|
+
};
|
|
10
19
|
|
|
11
20
|
class GestureViewerManager {
|
|
12
21
|
private currentIndex = 0;
|
|
13
22
|
private dataLength = 0;
|
|
14
|
-
private
|
|
23
|
+
private pagingStride = 0;
|
|
24
|
+
private viewportWidth = 0;
|
|
15
25
|
private height = 0;
|
|
16
26
|
private maxZoomScale = 2;
|
|
17
27
|
private enableHorizontalSwipe = true;
|
|
@@ -22,6 +32,8 @@ class GestureViewerManager {
|
|
|
22
32
|
private rotation: SharedValue<number> | null = null;
|
|
23
33
|
private translateX: SharedValue<number> | null = null;
|
|
24
34
|
private translateY: SharedValue<number> | null = null;
|
|
35
|
+
private contentWidth: SharedValue<number> | null = null;
|
|
36
|
+
private contentHeight: SharedValue<number> | null = null;
|
|
25
37
|
private resetTransformCallback: (() => void) | null = null;
|
|
26
38
|
|
|
27
39
|
private loopCallback: (() => void) | null = null;
|
|
@@ -105,8 +117,12 @@ class GestureViewerManager {
|
|
|
105
117
|
this.enableLoop = enabled;
|
|
106
118
|
}
|
|
107
119
|
|
|
108
|
-
|
|
109
|
-
this.
|
|
120
|
+
setPagingStride(pagingStride: number) {
|
|
121
|
+
this.pagingStride = pagingStride;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
setViewportWidth(width: number) {
|
|
125
|
+
this.viewportWidth = width;
|
|
110
126
|
}
|
|
111
127
|
|
|
112
128
|
setHeight(height: number) {
|
|
@@ -131,16 +147,20 @@ class GestureViewerManager {
|
|
|
131
147
|
}
|
|
132
148
|
}
|
|
133
149
|
|
|
134
|
-
setZoomSharedValues(
|
|
135
|
-
scale
|
|
136
|
-
translateX
|
|
137
|
-
translateY
|
|
138
|
-
maxZoomScale
|
|
139
|
-
|
|
150
|
+
setZoomSharedValues({
|
|
151
|
+
scale,
|
|
152
|
+
translateX,
|
|
153
|
+
translateY,
|
|
154
|
+
maxZoomScale,
|
|
155
|
+
contentWidth,
|
|
156
|
+
contentHeight,
|
|
157
|
+
}: ZoomSharedValuesOptions) {
|
|
140
158
|
this.scale = scale;
|
|
141
159
|
this.translateX = translateX;
|
|
142
160
|
this.translateY = translateY;
|
|
143
161
|
this.maxZoomScale = maxZoomScale;
|
|
162
|
+
this.contentWidth = contentWidth ?? null;
|
|
163
|
+
this.contentHeight = contentHeight ?? null;
|
|
144
164
|
}
|
|
145
165
|
|
|
146
166
|
setResetTransformCallback(callback: (() => void) | null) {
|
|
@@ -203,10 +223,7 @@ class GestureViewerManager {
|
|
|
203
223
|
|
|
204
224
|
this.scale.set(withTiming(nextScale));
|
|
205
225
|
|
|
206
|
-
const { translateX, translateY } =
|
|
207
|
-
width: this.width,
|
|
208
|
-
height: this.height,
|
|
209
|
-
})({
|
|
226
|
+
const { translateX, translateY } = this.constrainZoomTranslation({
|
|
210
227
|
translateX: this.translateX.get(),
|
|
211
228
|
translateY: this.translateY.get(),
|
|
212
229
|
scale: nextScale,
|
|
@@ -237,10 +254,7 @@ class GestureViewerManager {
|
|
|
237
254
|
return;
|
|
238
255
|
}
|
|
239
256
|
|
|
240
|
-
const { translateX, translateY } =
|
|
241
|
-
width: this.width,
|
|
242
|
-
height: this.height,
|
|
243
|
-
})({
|
|
257
|
+
const { translateX, translateY } = this.constrainZoomTranslation({
|
|
244
258
|
translateX: this.translateX.get(),
|
|
245
259
|
translateY: this.translateY.get(),
|
|
246
260
|
scale: nextScale,
|
|
@@ -273,7 +287,7 @@ class GestureViewerManager {
|
|
|
273
287
|
|
|
274
288
|
this.cancelPendingLoopTransition();
|
|
275
289
|
|
|
276
|
-
const { scrollTo } = createScrollAction(this.listRef, this.
|
|
290
|
+
const { scrollTo } = createScrollAction(this.listRef, this.pagingStride || this.viewportWidth);
|
|
277
291
|
|
|
278
292
|
if (this.enableLoop && this.dataLength > 1) {
|
|
279
293
|
if (index < 0) {
|
|
@@ -358,9 +372,14 @@ class GestureViewerManager {
|
|
|
358
372
|
this.currentIndex = 0;
|
|
359
373
|
this.dataLength = 0;
|
|
360
374
|
this.maxZoomScale = 2;
|
|
375
|
+
this.pagingStride = 0;
|
|
376
|
+
this.viewportWidth = 0;
|
|
377
|
+
this.height = 0;
|
|
361
378
|
this.scale = null;
|
|
362
379
|
this.translateX = null;
|
|
363
380
|
this.translateY = null;
|
|
381
|
+
this.contentWidth = null;
|
|
382
|
+
this.contentHeight = null;
|
|
364
383
|
this.rotation = null;
|
|
365
384
|
this.resetTransformCallback = null;
|
|
366
385
|
this.eventListeners.clear();
|
|
@@ -370,6 +389,26 @@ class GestureViewerManager {
|
|
|
370
389
|
this.currentIndex = targetIndex;
|
|
371
390
|
this.notifyListeners();
|
|
372
391
|
};
|
|
392
|
+
|
|
393
|
+
private constrainZoomTranslation = ({
|
|
394
|
+
scale,
|
|
395
|
+
translateX,
|
|
396
|
+
translateY,
|
|
397
|
+
}: {
|
|
398
|
+
scale: number;
|
|
399
|
+
translateX: number;
|
|
400
|
+
translateY: number;
|
|
401
|
+
}) => {
|
|
402
|
+
return clampTranslationToBounds({
|
|
403
|
+
contentHeight: this.contentHeight?.get(),
|
|
404
|
+
contentWidth: this.contentWidth?.get(),
|
|
405
|
+
height: this.height,
|
|
406
|
+
scale,
|
|
407
|
+
translateX,
|
|
408
|
+
translateY,
|
|
409
|
+
width: this.viewportWidth || this.pagingStride,
|
|
410
|
+
});
|
|
411
|
+
};
|
|
373
412
|
}
|
|
374
413
|
|
|
375
414
|
export default GestureViewerManager;
|
package/src/index.tsx
CHANGED
|
@@ -6,6 +6,10 @@ export type {
|
|
|
6
6
|
GestureViewerEventCallback,
|
|
7
7
|
GestureViewerEventData,
|
|
8
8
|
GestureViewerEventType,
|
|
9
|
+
GestureViewerItemDimensions,
|
|
10
|
+
GestureViewerItemDimensionsResolver,
|
|
11
|
+
GestureViewerItemKey,
|
|
12
|
+
GestureViewerItemKeyResolver,
|
|
9
13
|
GestureViewerProps,
|
|
10
14
|
GestureViewerRenderItemInfo,
|
|
11
15
|
GestureViewerState,
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
GestureViewerItemDimensions,
|
|
3
|
+
GestureViewerItemDimensionsResolver,
|
|
4
|
+
GestureViewerItemKey,
|
|
5
|
+
GestureViewerItemKeyResolver,
|
|
6
|
+
} from './types';
|
|
7
|
+
|
|
8
|
+
type ItemDimensionsRegistryEntry<ItemT> = Readonly<{
|
|
9
|
+
item: ItemT;
|
|
10
|
+
itemKey?: GestureViewerItemKey;
|
|
11
|
+
dimensions: GestureViewerItemDimensions;
|
|
12
|
+
}>;
|
|
13
|
+
|
|
14
|
+
export type ItemDimensionsRegistry<ItemT> = Map<number, ItemDimensionsRegistryEntry<ItemT>>;
|
|
15
|
+
|
|
16
|
+
export const isValidItemDimensions = (
|
|
17
|
+
dimensions: GestureViewerItemDimensions | null | undefined,
|
|
18
|
+
): dimensions is GestureViewerItemDimensions => {
|
|
19
|
+
return (
|
|
20
|
+
dimensions !== null &&
|
|
21
|
+
dimensions !== undefined &&
|
|
22
|
+
Number.isFinite(dimensions.width) &&
|
|
23
|
+
Number.isFinite(dimensions.height) &&
|
|
24
|
+
dimensions.width > 0 &&
|
|
25
|
+
dimensions.height > 0
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const resolveItemKey = <ItemT>(
|
|
30
|
+
getItemKey: GestureViewerItemKeyResolver<ItemT> | undefined,
|
|
31
|
+
item: ItemT,
|
|
32
|
+
index: number,
|
|
33
|
+
): GestureViewerItemKey | undefined => {
|
|
34
|
+
const itemKey = getItemKey?.(item, index);
|
|
35
|
+
|
|
36
|
+
if (typeof itemKey === 'string') {
|
|
37
|
+
return itemKey;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return typeof itemKey === 'number' && Number.isFinite(itemKey) ? itemKey : undefined;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const doesEntryMatchItem = <ItemT>(
|
|
44
|
+
entry: ItemDimensionsRegistryEntry<ItemT>,
|
|
45
|
+
item: ItemT,
|
|
46
|
+
itemKey: GestureViewerItemKey | undefined,
|
|
47
|
+
): boolean => {
|
|
48
|
+
if (itemKey !== undefined && entry.itemKey !== undefined) {
|
|
49
|
+
return entry.itemKey === itemKey;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return Object.is(entry.item, item);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const pruneItemDimensionsRegistry = <ItemT>(
|
|
56
|
+
registry: ItemDimensionsRegistry<ItemT>,
|
|
57
|
+
dataLength: number,
|
|
58
|
+
): void => {
|
|
59
|
+
for (const index of registry.keys()) {
|
|
60
|
+
if (index < 0 || index >= dataLength) {
|
|
61
|
+
registry.delete(index);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const resolveItemDimensions = <ItemT>({
|
|
67
|
+
registry,
|
|
68
|
+
data,
|
|
69
|
+
index,
|
|
70
|
+
getItemDimensions,
|
|
71
|
+
getItemKey,
|
|
72
|
+
}: {
|
|
73
|
+
registry: ItemDimensionsRegistry<ItemT>;
|
|
74
|
+
data: readonly ItemT[];
|
|
75
|
+
index: number;
|
|
76
|
+
getItemDimensions?: GestureViewerItemDimensionsResolver<ItemT>;
|
|
77
|
+
getItemKey?: GestureViewerItemKeyResolver<ItemT>;
|
|
78
|
+
}): GestureViewerItemDimensions | undefined => {
|
|
79
|
+
if (index < 0 || index >= data.length) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// `as ItemT` restores the viewer's dense-list invariant, which TypeScript cannot infer from
|
|
84
|
+
// generic indexed access when `noUncheckedIndexedAccess` is enabled.
|
|
85
|
+
const item = data[index] as ItemT;
|
|
86
|
+
const registered = registry.get(index);
|
|
87
|
+
const itemKey = registered ? resolveItemKey(getItemKey, item, index) : undefined;
|
|
88
|
+
|
|
89
|
+
if (
|
|
90
|
+
registered &&
|
|
91
|
+
doesEntryMatchItem(registered, item, itemKey) &&
|
|
92
|
+
isValidItemDimensions(registered.dimensions)
|
|
93
|
+
) {
|
|
94
|
+
return registered.dimensions;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const resolved = getItemDimensions?.(item, index);
|
|
98
|
+
|
|
99
|
+
if (isValidItemDimensions(resolved)) {
|
|
100
|
+
return resolved;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return undefined;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const registerItemDimensions = <ItemT>({
|
|
107
|
+
registry,
|
|
108
|
+
data,
|
|
109
|
+
index,
|
|
110
|
+
item,
|
|
111
|
+
dimensions,
|
|
112
|
+
getItemKey,
|
|
113
|
+
}: {
|
|
114
|
+
registry: ItemDimensionsRegistry<ItemT>;
|
|
115
|
+
data: readonly ItemT[];
|
|
116
|
+
index: number;
|
|
117
|
+
item: ItemT;
|
|
118
|
+
dimensions: GestureViewerItemDimensions;
|
|
119
|
+
getItemKey?: GestureViewerItemKeyResolver<ItemT>;
|
|
120
|
+
}): boolean => {
|
|
121
|
+
if (index < 0 || index >= data.length || !isValidItemDimensions(dimensions)) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// `as ItemT` restores the viewer's dense-list invariant, which TypeScript cannot infer from
|
|
126
|
+
// generic indexed access when `noUncheckedIndexedAccess` is enabled.
|
|
127
|
+
const currentItem = data[index] as ItemT;
|
|
128
|
+
const currentItemKey = resolveItemKey(getItemKey, currentItem, index);
|
|
129
|
+
const reportedItemKey = Object.is(currentItem, item)
|
|
130
|
+
? currentItemKey
|
|
131
|
+
: resolveItemKey(getItemKey, item, index);
|
|
132
|
+
const hasMatchingKey =
|
|
133
|
+
currentItemKey !== undefined &&
|
|
134
|
+
reportedItemKey !== undefined &&
|
|
135
|
+
currentItemKey === reportedItemKey;
|
|
136
|
+
|
|
137
|
+
if (!Object.is(currentItem, item) && !hasMatchingKey) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const registered = registry.get(index);
|
|
142
|
+
|
|
143
|
+
if (
|
|
144
|
+
registered &&
|
|
145
|
+
doesEntryMatchItem(registered, currentItem, currentItemKey) &&
|
|
146
|
+
registered.dimensions.width === dimensions.width &&
|
|
147
|
+
registered.dimensions.height === dimensions.height
|
|
148
|
+
) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
registry.set(index, {
|
|
153
|
+
item: currentItem,
|
|
154
|
+
itemKey: currentItemKey,
|
|
155
|
+
dimensions: {
|
|
156
|
+
height: dimensions.height,
|
|
157
|
+
width: dimensions.width,
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return true;
|
|
162
|
+
};
|
package/src/types.ts
CHANGED
|
@@ -82,12 +82,40 @@ export type GestureViewerSingleTapEvent<ItemT> = {
|
|
|
82
82
|
item: ItemT;
|
|
83
83
|
};
|
|
84
84
|
|
|
85
|
+
export type GestureViewerItemDimensions = Readonly<{
|
|
86
|
+
/**
|
|
87
|
+
* Natural/source content width. Must be finite and greater than zero.
|
|
88
|
+
*/
|
|
89
|
+
width: number;
|
|
90
|
+
/**
|
|
91
|
+
* Natural/source content height. Must be finite and greater than zero.
|
|
92
|
+
*/
|
|
93
|
+
height: number;
|
|
94
|
+
}>;
|
|
95
|
+
|
|
96
|
+
export type GestureViewerItemDimensionsResolver<ItemT> = (
|
|
97
|
+
item: ItemT,
|
|
98
|
+
index: number,
|
|
99
|
+
) => GestureViewerItemDimensions | undefined;
|
|
100
|
+
|
|
101
|
+
export type GestureViewerItemKey = string | number;
|
|
102
|
+
|
|
103
|
+
export type GestureViewerItemKeyResolver<ItemT> = (
|
|
104
|
+
item: ItemT,
|
|
105
|
+
index: number,
|
|
106
|
+
) => GestureViewerItemKey;
|
|
107
|
+
|
|
85
108
|
export type GestureViewerRenderItemInfo = {
|
|
86
109
|
/**
|
|
87
110
|
* Whether the rendered item is currently active.
|
|
88
111
|
* @remarks The current item remains active during a page transition. When the transition finishes on another item, that item becomes active.
|
|
89
112
|
*/
|
|
90
113
|
readonly isActive: boolean;
|
|
114
|
+
/**
|
|
115
|
+
* Registers natural/source dimensions for the rendered item after they become available.
|
|
116
|
+
* @remarks Call this from an image load/event callback or a passive effect after commit. Do not call it directly while rendering or from descendant layout effects.
|
|
117
|
+
*/
|
|
118
|
+
readonly setItemDimensions: (dimensions: GestureViewerItemDimensions) => void;
|
|
91
119
|
};
|
|
92
120
|
|
|
93
121
|
export type GestureViewerDismissDirection = 'down' | 'up' | 'both';
|
|
@@ -122,6 +150,7 @@ export interface GestureViewerProps<ItemT, LC> {
|
|
|
122
150
|
data: ItemT[];
|
|
123
151
|
/**
|
|
124
152
|
* The index of the item to display in the `GestureViewer` when the component is mounted.
|
|
153
|
+
* @remarks The value is normalized to the current data: non-finite or negative values use `0`, values above the available range use the last index, and empty data uses `0`. Updating this prop repositions a mounted viewer.
|
|
125
154
|
* @defaultValue 0
|
|
126
155
|
*/
|
|
127
156
|
initialIndex?: number;
|
|
@@ -149,6 +178,16 @@ export interface GestureViewerProps<ItemT, LC> {
|
|
|
149
178
|
* - Prefer this callback over overlaying a pressable in `renderContainer` for fullscreen tap handling.
|
|
150
179
|
*/
|
|
151
180
|
onSingleTap?: (event: GestureViewerSingleTapEvent<ItemT>) => void;
|
|
181
|
+
/**
|
|
182
|
+
* Returns natural/source dimensions for an item when they are already known.
|
|
183
|
+
* @remarks Return `undefined` while dimensions are unavailable. Invalid dimensions fall back to the viewer cell size.
|
|
184
|
+
*/
|
|
185
|
+
getItemDimensions?: GestureViewerItemDimensionsResolver<ItemT>;
|
|
186
|
+
/**
|
|
187
|
+
* Returns a stable key used to retain loaded dimensions when equivalent item objects are recreated at the same index.
|
|
188
|
+
* @remarks Keys must be unique within `data` and change when the rendered content's natural dimensions can change. Do not use the index by itself. This is only needed when object items are recreated and dimensions are reported through `setItemDimensions`.
|
|
189
|
+
*/
|
|
190
|
+
getItemKey?: GestureViewerItemKeyResolver<ItemT>;
|
|
152
191
|
/**
|
|
153
192
|
* A callback function that is called to render the container.
|
|
154
193
|
* @remarks Useful for composing additional UI (e.g., close button, toolbars) around the viewer.
|