react-native-gesture-image-viewer 2.4.0 → 3.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/lib/module/GestureViewer.js +96 -147
- package/lib/module/GestureViewer.js.map +1 -1
- package/lib/module/GestureViewerManager.js +49 -105
- package/lib/module/GestureViewerManager.js.map +1 -1
- package/lib/module/gestureViewerAnimation.js +18 -0
- package/lib/module/gestureViewerAnimation.js.map +1 -0
- package/lib/module/gestureViewerGestures.js +12 -0
- package/lib/module/gestureViewerGestures.js.map +1 -0
- package/lib/module/gestureViewerPaging.js +66 -0
- package/lib/module/gestureViewerPaging.js.map +1 -0
- package/lib/module/gestureViewerRenderWindow.js +29 -0
- package/lib/module/gestureViewerRenderWindow.js.map +1 -0
- package/lib/module/getWebContentProps.js +6 -0
- package/lib/module/getWebContentProps.js.map +1 -0
- package/lib/module/getWebContentProps.web.js +8 -0
- package/lib/module/getWebContentProps.web.js.map +1 -0
- package/lib/module/platformTapGestures.js +4 -0
- package/lib/module/platformTapGestures.js.map +1 -0
- package/lib/module/platformTapGestures.web.js +4 -0
- package/lib/module/platformTapGestures.web.js.map +1 -0
- package/lib/module/renderWindow.js +160 -0
- package/lib/module/renderWindow.js.map +1 -0
- package/lib/module/useGestureViewer.js +459 -211
- package/lib/module/useGestureViewer.js.map +1 -1
- package/lib/module/useGestureViewerController.js +3 -3
- package/lib/module/useGestureViewerController.js.map +1 -1
- package/lib/module/useGestureViewerManagerBridge.js +85 -0
- package/lib/module/useGestureViewerManagerBridge.js.map +1 -0
- package/lib/module/useGestureViewerPaging.js +168 -79
- package/lib/module/useGestureViewerPaging.js.map +1 -1
- package/lib/module/useWebClickHandler.js +7 -0
- package/lib/module/useWebClickHandler.js.map +1 -0
- package/lib/module/useWebClickHandler.web.js +85 -0
- package/lib/module/useWebClickHandler.web.js.map +1 -0
- package/lib/module/useWebSingleTapTimer.js +12 -0
- package/lib/module/useWebSingleTapTimer.js.map +1 -0
- package/lib/module/useWebSingleTapTimer.web.js +25 -0
- package/lib/module/useWebSingleTapTimer.web.js.map +1 -0
- package/lib/module/utils/index.js +0 -77
- package/lib/module/utils/index.js.map +1 -1
- package/lib/module/utils/tapZoom.js +4 -3
- package/lib/module/utils/tapZoom.js.map +1 -1
- package/lib/typescript/src/GestureViewer.d.ts +1 -1
- package/lib/typescript/src/GestureViewer.d.ts.map +1 -1
- package/lib/typescript/src/GestureViewerManager.d.ts +20 -20
- package/lib/typescript/src/GestureViewerManager.d.ts.map +1 -1
- package/lib/typescript/src/gestureViewerAnimation.d.ts +15 -0
- package/lib/typescript/src/gestureViewerAnimation.d.ts.map +1 -0
- package/lib/typescript/src/gestureViewerGestures.d.ts +10 -0
- package/lib/typescript/src/gestureViewerGestures.d.ts.map +1 -0
- package/lib/typescript/src/gestureViewerPaging.d.ts +13 -0
- package/lib/typescript/src/gestureViewerPaging.d.ts.map +1 -0
- package/lib/typescript/src/gestureViewerRenderWindow.d.ts +15 -0
- package/lib/typescript/src/gestureViewerRenderWindow.d.ts.map +1 -0
- package/lib/typescript/src/getWebContentProps.d.ts +3 -0
- package/lib/typescript/src/getWebContentProps.d.ts.map +1 -0
- package/lib/typescript/src/getWebContentProps.web.d.ts +5 -0
- package/lib/typescript/src/getWebContentProps.web.d.ts.map +1 -0
- package/lib/typescript/src/platformTapGestures.d.ts +2 -0
- package/lib/typescript/src/platformTapGestures.d.ts.map +1 -0
- package/lib/typescript/src/platformTapGestures.web.d.ts +2 -0
- package/lib/typescript/src/platformTapGestures.web.d.ts.map +1 -0
- package/lib/typescript/src/renderWindow.d.ts +44 -0
- package/lib/typescript/src/renderWindow.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +36 -53
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/useGestureViewer.d.ts +11 -10
- package/lib/typescript/src/useGestureViewer.d.ts.map +1 -1
- package/lib/typescript/src/useGestureViewerController.d.ts +1 -1
- package/lib/typescript/src/useGestureViewerManagerBridge.d.ts +28 -0
- package/lib/typescript/src/useGestureViewerManagerBridge.d.ts.map +1 -0
- package/lib/typescript/src/useGestureViewerPaging.d.ts +33 -2
- package/lib/typescript/src/useGestureViewerPaging.d.ts.map +1 -1
- package/lib/typescript/src/useWebClickHandler.d.ts +35 -0
- package/lib/typescript/src/useWebClickHandler.d.ts.map +1 -0
- package/lib/typescript/src/useWebClickHandler.web.d.ts +36 -0
- package/lib/typescript/src/useWebClickHandler.web.d.ts.map +1 -0
- package/lib/typescript/src/useWebSingleTapTimer.d.ts +6 -0
- package/lib/typescript/src/useWebSingleTapTimer.d.ts.map +1 -0
- package/lib/typescript/src/useWebSingleTapTimer.web.d.ts +6 -0
- package/lib/typescript/src/useWebSingleTapTimer.web.d.ts.map +1 -0
- package/lib/typescript/src/utils/index.d.ts +0 -20
- package/lib/typescript/src/utils/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/tapZoom.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/GestureViewer.tsx +113 -208
- package/src/GestureViewerManager.ts +77 -122
- package/src/gestureViewerAnimation.ts +18 -0
- package/src/gestureViewerGestures.ts +20 -0
- package/src/gestureViewerPaging.ts +116 -0
- package/src/gestureViewerRenderWindow.ts +56 -0
- package/src/getWebContentProps.ts +5 -0
- package/src/getWebContentProps.web.ts +5 -0
- package/src/platformTapGestures.ts +1 -0
- package/src/platformTapGestures.web.ts +1 -0
- package/src/renderWindow.ts +253 -0
- package/src/types.ts +34 -100
- package/src/useGestureViewer.ts +665 -275
- package/src/useGestureViewerController.ts +3 -3
- package/src/useGestureViewerManagerBridge.ts +118 -0
- package/src/useGestureViewerPaging.ts +306 -112
- package/src/useWebClickHandler.ts +44 -0
- package/src/useWebClickHandler.web.ts +146 -0
- package/src/useWebSingleTapTimer.ts +10 -0
- package/src/useWebSingleTapTimer.web.ts +30 -0
- package/src/utils/index.ts +0 -102
- package/src/utils/tapZoom.ts +4 -3
- package/lib/module/WebPagingFixStyle.js +0 -25
- package/lib/module/WebPagingFixStyle.js.map +0 -1
- package/lib/module/scheduleInitialScroll.js +0 -21
- package/lib/module/scheduleInitialScroll.js.map +0 -1
- package/lib/module/useGestureViewerPaging.types.js +0 -4
- package/lib/module/useGestureViewerPaging.types.js.map +0 -1
- package/lib/module/useGestureViewerPaging.web.js +0 -291
- package/lib/module/useGestureViewerPaging.web.js.map +0 -1
- package/lib/module/utils/FlashList.js +0 -9
- package/lib/module/utils/FlashList.js.map +0 -1
- package/lib/module/utils/webScroll.js +0 -70
- package/lib/module/utils/webScroll.js.map +0 -1
- package/lib/typescript/src/WebPagingFixStyle.d.ts +0 -6
- package/lib/typescript/src/WebPagingFixStyle.d.ts.map +0 -1
- package/lib/typescript/src/scheduleInitialScroll.d.ts +0 -3
- package/lib/typescript/src/scheduleInitialScroll.d.ts.map +0 -1
- package/lib/typescript/src/useGestureViewerPaging.types.d.ts +0 -42
- package/lib/typescript/src/useGestureViewerPaging.types.d.ts.map +0 -1
- package/lib/typescript/src/useGestureViewerPaging.web.d.ts +0 -3
- package/lib/typescript/src/useGestureViewerPaging.web.d.ts.map +0 -1
- package/lib/typescript/src/utils/FlashList.d.ts +0 -2
- package/lib/typescript/src/utils/FlashList.d.ts.map +0 -1
- package/lib/typescript/src/utils/webScroll.d.ts +0 -18
- package/lib/typescript/src/utils/webScroll.d.ts.map +0 -1
- package/src/WebPagingFixStyle.tsx +0 -25
- package/src/scheduleInitialScroll.ts +0 -36
- package/src/useGestureViewerPaging.types.ts +0 -42
- package/src/useGestureViewerPaging.web.ts +0 -453
- package/src/utils/FlashList.ts +0 -7
- package/src/utils/webScroll.ts +0 -96
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Gesture, type ComposedGesture, type GestureType } from 'react-native-gesture-handler';
|
|
2
|
+
|
|
3
|
+
type GestureViewerGestures = {
|
|
4
|
+
dismissGesture: GestureType;
|
|
5
|
+
horizontalPagingGesture: GestureType;
|
|
6
|
+
zoomGesture: ComposedGesture;
|
|
7
|
+
zoomPinchGesture: GestureType;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function composeGestureViewerGestures({
|
|
11
|
+
dismissGesture,
|
|
12
|
+
horizontalPagingGesture,
|
|
13
|
+
zoomGesture,
|
|
14
|
+
zoomPinchGesture,
|
|
15
|
+
}: GestureViewerGestures): ComposedGesture {
|
|
16
|
+
return Gesture.Simultaneous(
|
|
17
|
+
zoomPinchGesture,
|
|
18
|
+
Gesture.Race(dismissGesture, horizontalPagingGesture, zoomGesture),
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
export type HorizontalSwipeDirection = -1 | 0 | 1;
|
|
2
|
+
|
|
3
|
+
export type HorizontalPagingTarget =
|
|
4
|
+
| {
|
|
5
|
+
kind: 'move';
|
|
6
|
+
targetVirtualIndex: number;
|
|
7
|
+
}
|
|
8
|
+
| {
|
|
9
|
+
kind: 'settle';
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function normalizeHorizontalSwipeThreshold(
|
|
13
|
+
value: number | undefined,
|
|
14
|
+
fallback: number,
|
|
15
|
+
): number {
|
|
16
|
+
if (value === undefined) {
|
|
17
|
+
return fallback;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (Number.isFinite(value) && value >= 0) {
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return fallback;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function resolveHorizontalSwipeDirection(
|
|
28
|
+
translationX: number,
|
|
29
|
+
velocityX: number,
|
|
30
|
+
width: number,
|
|
31
|
+
thresholdRatio: number,
|
|
32
|
+
velocityThreshold: number,
|
|
33
|
+
): HorizontalSwipeDirection {
|
|
34
|
+
'worklet';
|
|
35
|
+
|
|
36
|
+
const translationThreshold = width * thresholdRatio;
|
|
37
|
+
|
|
38
|
+
if (-translationX > translationThreshold || velocityX < -velocityThreshold) {
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (translationX > translationThreshold || velocityX > velocityThreshold) {
|
|
43
|
+
return -1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function applyHorizontalEdgeResistance(
|
|
50
|
+
nextPage: number,
|
|
51
|
+
currentIndex: number,
|
|
52
|
+
dataLength: number,
|
|
53
|
+
centerVirtualIndex: number,
|
|
54
|
+
enableLoop: boolean,
|
|
55
|
+
resistance: number,
|
|
56
|
+
): number {
|
|
57
|
+
'worklet';
|
|
58
|
+
|
|
59
|
+
if (dataLength <= 1) {
|
|
60
|
+
return centerVirtualIndex;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (enableLoop) {
|
|
64
|
+
return nextPage;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (currentIndex <= 0 && nextPage < centerVirtualIndex) {
|
|
68
|
+
return centerVirtualIndex + (nextPage - centerVirtualIndex) * resistance;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (currentIndex >= dataLength - 1 && nextPage > centerVirtualIndex) {
|
|
72
|
+
return centerVirtualIndex + (nextPage - centerVirtualIndex) * resistance;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return nextPage;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function canMoveHorizontalPage(
|
|
79
|
+
currentIndex: number,
|
|
80
|
+
dataLength: number,
|
|
81
|
+
direction: HorizontalSwipeDirection,
|
|
82
|
+
enableLoop: boolean,
|
|
83
|
+
): boolean {
|
|
84
|
+
'worklet';
|
|
85
|
+
|
|
86
|
+
if (direction === 0 || dataLength <= 1) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (enableLoop) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const nextIndex = currentIndex + direction;
|
|
95
|
+
|
|
96
|
+
return nextIndex >= 0 && nextIndex < dataLength;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function resolveHorizontalPagingTarget(
|
|
100
|
+
centerVirtualIndex: number,
|
|
101
|
+
currentIndex: number,
|
|
102
|
+
dataLength: number,
|
|
103
|
+
direction: HorizontalSwipeDirection,
|
|
104
|
+
enableLoop: boolean,
|
|
105
|
+
): HorizontalPagingTarget {
|
|
106
|
+
'worklet';
|
|
107
|
+
|
|
108
|
+
if (!canMoveHorizontalPage(currentIndex, dataLength, direction, enableLoop)) {
|
|
109
|
+
return { kind: 'settle' };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
kind: 'move',
|
|
114
|
+
targetVirtualIndex: centerVirtualIndex + direction,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type RenderWindowSlot,
|
|
3
|
+
clampIndex,
|
|
4
|
+
createRenderWindow,
|
|
5
|
+
getVirtualIndexForLogicalIndex,
|
|
6
|
+
normalizeWindowSize,
|
|
7
|
+
} from './renderWindow';
|
|
8
|
+
|
|
9
|
+
export type GestureViewerRenderWindowState<ItemT> = {
|
|
10
|
+
centerVirtualIndex: number;
|
|
11
|
+
currentIndex: number;
|
|
12
|
+
slots: RenderWindowSlot<ItemT>[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function resolveGestureViewerRenderWindow<ItemT>({
|
|
16
|
+
centerVirtualIndex,
|
|
17
|
+
currentIndex,
|
|
18
|
+
data,
|
|
19
|
+
enableLoop,
|
|
20
|
+
isTriggerOpening,
|
|
21
|
+
windowSize,
|
|
22
|
+
}: {
|
|
23
|
+
centerVirtualIndex: number;
|
|
24
|
+
currentIndex: number;
|
|
25
|
+
data: ItemT[];
|
|
26
|
+
enableLoop: boolean;
|
|
27
|
+
isTriggerOpening: boolean;
|
|
28
|
+
windowSize?: number;
|
|
29
|
+
}): GestureViewerRenderWindowState<ItemT> {
|
|
30
|
+
const dataLength = data.length;
|
|
31
|
+
const normalizedWindowSize = normalizeWindowSize(windowSize);
|
|
32
|
+
const renderCurrentIndex = clampIndex(currentIndex, dataLength);
|
|
33
|
+
const renderCenterVirtualIndex =
|
|
34
|
+
getVirtualIndexForLogicalIndex(
|
|
35
|
+
renderCurrentIndex,
|
|
36
|
+
centerVirtualIndex,
|
|
37
|
+
dataLength,
|
|
38
|
+
enableLoop,
|
|
39
|
+
) ?? renderCurrentIndex;
|
|
40
|
+
|
|
41
|
+
const fullSlots = createRenderWindow({
|
|
42
|
+
centerVirtualIndex: renderCenterVirtualIndex,
|
|
43
|
+
data,
|
|
44
|
+
enableLoop,
|
|
45
|
+
windowSize: normalizedWindowSize,
|
|
46
|
+
});
|
|
47
|
+
const slots = isTriggerOpening
|
|
48
|
+
? fullSlots.filter((slot) => slot.virtualIndex === renderCenterVirtualIndex)
|
|
49
|
+
: fullSlots;
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
centerVirtualIndex: renderCenterVirtualIndex,
|
|
53
|
+
currentIndex: renderCurrentIndex,
|
|
54
|
+
slots,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const enableNativeTapGestures = true;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const enableNativeTapGestures = false;
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
export const DEFAULT_WINDOW_SIZE = 3;
|
|
2
|
+
export const MIN_WINDOW_SIZE = 3;
|
|
3
|
+
|
|
4
|
+
type NavigationDirection = -1 | 1;
|
|
5
|
+
|
|
6
|
+
export type RenderWindowSlot<ItemT> = {
|
|
7
|
+
item: ItemT;
|
|
8
|
+
logicalIndex: number;
|
|
9
|
+
slotKey: string;
|
|
10
|
+
virtualIndex: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type NavigationResolution =
|
|
14
|
+
| {
|
|
15
|
+
kind: 'noop';
|
|
16
|
+
}
|
|
17
|
+
| {
|
|
18
|
+
direction: NavigationDirection;
|
|
19
|
+
kind: 'step';
|
|
20
|
+
targetIndex: number;
|
|
21
|
+
}
|
|
22
|
+
| {
|
|
23
|
+
kind: 'jump';
|
|
24
|
+
targetIndex: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type NavigationOptions = {
|
|
28
|
+
animated?: boolean;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const positiveModulo = (value: number, divisor: number) => ((value % divisor) + divisor) % divisor;
|
|
32
|
+
|
|
33
|
+
const getStepTargetIndex = (
|
|
34
|
+
currentIndex: number,
|
|
35
|
+
dataLength: number,
|
|
36
|
+
enableLoop: boolean,
|
|
37
|
+
direction: NavigationDirection,
|
|
38
|
+
) => {
|
|
39
|
+
const adjacentIndex = currentIndex + direction;
|
|
40
|
+
|
|
41
|
+
if (adjacentIndex >= 0 && adjacentIndex < dataLength) {
|
|
42
|
+
return adjacentIndex;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!enableLoop || dataLength <= 1) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (direction === 1 && currentIndex === dataLength - 1) {
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (direction === -1 && currentIndex === 0) {
|
|
54
|
+
return dataLength - 1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return null;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export function normalizeWindowSize(windowSize?: number): number {
|
|
61
|
+
const candidateWindowSize = windowSize ?? DEFAULT_WINDOW_SIZE;
|
|
62
|
+
|
|
63
|
+
if (!Number.isFinite(candidateWindowSize)) {
|
|
64
|
+
return DEFAULT_WINDOW_SIZE;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const integerWindowSize = Math.floor(candidateWindowSize);
|
|
68
|
+
const boundedWindowSize = Math.max(MIN_WINDOW_SIZE, integerWindowSize);
|
|
69
|
+
|
|
70
|
+
return boundedWindowSize % 2 === 0 ? boundedWindowSize + 1 : boundedWindowSize;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function normalizePageSpacing(pageSpacing?: number): number {
|
|
74
|
+
const candidatePageSpacing = pageSpacing ?? 0;
|
|
75
|
+
|
|
76
|
+
if (!Number.isFinite(candidatePageSpacing) || candidatePageSpacing < 0) {
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return candidatePageSpacing;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function getPageStride(width: number, pageSpacing?: number): number {
|
|
84
|
+
return width + normalizePageSpacing(pageSpacing);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function clampIndex(index: number | undefined, dataLength: number): number {
|
|
88
|
+
if (dataLength <= 0) {
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const candidateIndex = index ?? 0;
|
|
93
|
+
|
|
94
|
+
if (!Number.isFinite(candidateIndex)) {
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return Math.min(Math.max(Math.trunc(candidateIndex), 0), dataLength - 1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function getLogicalIndex(
|
|
102
|
+
virtualIndex: number,
|
|
103
|
+
dataLength: number,
|
|
104
|
+
enableLoop: boolean,
|
|
105
|
+
): number | null {
|
|
106
|
+
if (dataLength <= 0 || !Number.isFinite(virtualIndex)) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (enableLoop) {
|
|
111
|
+
return positiveModulo(Math.trunc(virtualIndex), dataLength);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (virtualIndex < 0 || virtualIndex >= dataLength) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return Math.trunc(virtualIndex);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function getVirtualIndexForLogicalIndex(
|
|
122
|
+
logicalIndex: number,
|
|
123
|
+
centerVirtualIndex: number,
|
|
124
|
+
dataLength: number,
|
|
125
|
+
enableLoop: boolean,
|
|
126
|
+
): number | null {
|
|
127
|
+
if (dataLength <= 0 || logicalIndex < 0 || logicalIndex >= dataLength) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!enableLoop) {
|
|
132
|
+
return logicalIndex;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const currentLogicalIndex = getLogicalIndex(centerVirtualIndex, dataLength, true);
|
|
136
|
+
|
|
137
|
+
if (currentLogicalIndex === null) {
|
|
138
|
+
return logicalIndex;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const forwardDistance = positiveModulo(logicalIndex - currentLogicalIndex, dataLength);
|
|
142
|
+
const backwardDistance = positiveModulo(currentLogicalIndex - logicalIndex, dataLength);
|
|
143
|
+
|
|
144
|
+
return forwardDistance <= backwardDistance
|
|
145
|
+
? centerVirtualIndex + forwardDistance
|
|
146
|
+
: centerVirtualIndex - backwardDistance;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function createRenderWindow<ItemT>({
|
|
150
|
+
centerVirtualIndex,
|
|
151
|
+
data,
|
|
152
|
+
enableLoop,
|
|
153
|
+
windowSize,
|
|
154
|
+
}: {
|
|
155
|
+
centerVirtualIndex: number;
|
|
156
|
+
data: ItemT[];
|
|
157
|
+
enableLoop: boolean;
|
|
158
|
+
windowSize?: number;
|
|
159
|
+
}): RenderWindowSlot<ItemT>[] {
|
|
160
|
+
const dataLength = data.length;
|
|
161
|
+
|
|
162
|
+
if (dataLength === 0) {
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const normalizedWindowSize = normalizeWindowSize(windowSize);
|
|
167
|
+
const halfWindowSize = enableLoop && dataLength === 1 ? 0 : Math.floor(normalizedWindowSize / 2);
|
|
168
|
+
const slots: RenderWindowSlot<ItemT>[] = [];
|
|
169
|
+
|
|
170
|
+
for (let offset = -halfWindowSize; offset <= halfWindowSize; offset += 1) {
|
|
171
|
+
const virtualIndex = centerVirtualIndex + offset;
|
|
172
|
+
const logicalIndex = getLogicalIndex(virtualIndex, dataLength, enableLoop);
|
|
173
|
+
|
|
174
|
+
if (logicalIndex === null || !(logicalIndex in data)) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
slots.push({
|
|
179
|
+
item: data[logicalIndex] as ItemT,
|
|
180
|
+
logicalIndex,
|
|
181
|
+
slotKey: `slot-${virtualIndex}`,
|
|
182
|
+
virtualIndex,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return slots;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function resolveNavigation({
|
|
190
|
+
currentIndex,
|
|
191
|
+
dataLength,
|
|
192
|
+
enableLoop,
|
|
193
|
+
preferredDirection,
|
|
194
|
+
targetIndex,
|
|
195
|
+
}: {
|
|
196
|
+
currentIndex: number;
|
|
197
|
+
dataLength: number;
|
|
198
|
+
enableLoop: boolean;
|
|
199
|
+
preferredDirection?: NavigationDirection;
|
|
200
|
+
targetIndex: number;
|
|
201
|
+
}): NavigationResolution {
|
|
202
|
+
if (
|
|
203
|
+
dataLength <= 0 ||
|
|
204
|
+
targetIndex < 0 ||
|
|
205
|
+
targetIndex >= dataLength ||
|
|
206
|
+
currentIndex < 0 ||
|
|
207
|
+
currentIndex >= dataLength
|
|
208
|
+
) {
|
|
209
|
+
return { kind: 'noop' };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (targetIndex === currentIndex) {
|
|
213
|
+
return { kind: 'noop' };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (preferredDirection !== undefined) {
|
|
217
|
+
const preferredTargetIndex = getStepTargetIndex(
|
|
218
|
+
currentIndex,
|
|
219
|
+
dataLength,
|
|
220
|
+
enableLoop,
|
|
221
|
+
preferredDirection,
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
if (preferredTargetIndex === targetIndex) {
|
|
225
|
+
return { direction: preferredDirection, kind: 'step', targetIndex };
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const forwardTargetIndex = getStepTargetIndex(currentIndex, dataLength, enableLoop, 1);
|
|
230
|
+
|
|
231
|
+
if (forwardTargetIndex === targetIndex) {
|
|
232
|
+
return { direction: 1, kind: 'step', targetIndex };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const backwardTargetIndex = getStepTargetIndex(currentIndex, dataLength, enableLoop, -1);
|
|
236
|
+
|
|
237
|
+
if (backwardTargetIndex === targetIndex) {
|
|
238
|
+
return { direction: -1, kind: 'step', targetIndex };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return { kind: 'jump', targetIndex };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function shouldRunNavigationDuringTransition(
|
|
245
|
+
resolution: NavigationResolution,
|
|
246
|
+
options?: NavigationOptions,
|
|
247
|
+
): boolean {
|
|
248
|
+
if (resolution.kind === 'noop') {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return resolution.kind === 'jump' || options?.animated === false;
|
|
253
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,73 +1,7 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
|
-
import type {
|
|
3
|
-
FlatListProps,
|
|
4
|
-
ListRenderItem,
|
|
5
|
-
FlatList as RNFlatList,
|
|
6
|
-
ScrollView as RNScrollView,
|
|
7
|
-
StyleProp,
|
|
8
|
-
ViewabilityConfig,
|
|
9
|
-
ViewStyle,
|
|
10
|
-
ViewToken,
|
|
11
|
-
} from 'react-native';
|
|
12
|
-
import type {
|
|
13
|
-
FlatList as GHFlatList,
|
|
14
|
-
ScrollView as GHScrollView,
|
|
15
|
-
} from 'react-native-gesture-handler';
|
|
2
|
+
import type { StyleProp, ViewStyle } from 'react-native';
|
|
16
3
|
import type { WithTimingConfig } from 'react-native-reanimated';
|
|
17
4
|
|
|
18
|
-
export type FlatListComponent<ItemT> = typeof RNFlatList<ItemT> | typeof GHFlatList<ItemT>;
|
|
19
|
-
export type ScrollViewComponent = typeof RNScrollView | typeof GHScrollView;
|
|
20
|
-
|
|
21
|
-
interface ViewabilityConfigCallbackPair<TItem> {
|
|
22
|
-
viewabilityConfig: ViewabilityConfig;
|
|
23
|
-
onViewableItemsChanged:
|
|
24
|
-
| ((info: { viewableItems: ViewToken<TItem>[]; changed: ViewToken<TItem>[] }) => void)
|
|
25
|
-
| null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Helper type to instantiate generic components with specific type parameter
|
|
29
|
-
export type InstantiateGeneric<ItemT, LC> =
|
|
30
|
-
LC extends React.ComponentType<infer P>
|
|
31
|
-
? P extends Record<string, any>
|
|
32
|
-
? {
|
|
33
|
-
[K in keyof P]: K extends 'data'
|
|
34
|
-
? ArrayLike<ItemT> | null | undefined
|
|
35
|
-
: K extends 'renderItem'
|
|
36
|
-
? ListRenderItem<ItemT>
|
|
37
|
-
: K extends 'keyExtractor'
|
|
38
|
-
? (item: ItemT, index: number) => string
|
|
39
|
-
: K extends 'getItemType'
|
|
40
|
-
? (item: ItemT, index: number, extraData?: any) => string | number | undefined
|
|
41
|
-
: K extends 'overrideItemLayout'
|
|
42
|
-
? (
|
|
43
|
-
layout: { span?: number; size?: number },
|
|
44
|
-
item: ItemT,
|
|
45
|
-
index: number,
|
|
46
|
-
maxColumns?: number,
|
|
47
|
-
extraData?: any,
|
|
48
|
-
) => void
|
|
49
|
-
: K extends 'onViewableItemsChanged'
|
|
50
|
-
? FlatListProps<ItemT>['onViewableItemsChanged']
|
|
51
|
-
: K extends 'viewabilityConfigCallbackPairs'
|
|
52
|
-
? ViewabilityConfigCallbackPair<ItemT>[]
|
|
53
|
-
: P[K];
|
|
54
|
-
}
|
|
55
|
-
: P
|
|
56
|
-
: never;
|
|
57
|
-
|
|
58
|
-
export type GetComponentProps<ItemT, LC> =
|
|
59
|
-
LC extends React.ComponentType<any> ? InstantiateGeneric<ItemT, LC> : never;
|
|
60
|
-
|
|
61
|
-
type ConditionalListProps<ItemT, LC> = LC extends typeof RNFlatList<ItemT>
|
|
62
|
-
? React.ComponentProps<typeof RNFlatList<ItemT>>
|
|
63
|
-
: LC extends typeof GHFlatList<ItemT>
|
|
64
|
-
? React.ComponentProps<typeof GHFlatList<ItemT>>
|
|
65
|
-
: LC extends typeof RNScrollView
|
|
66
|
-
? React.ComponentProps<typeof RNScrollView>
|
|
67
|
-
: LC extends typeof GHScrollView
|
|
68
|
-
? React.ComponentProps<typeof GHScrollView>
|
|
69
|
-
: GetComponentProps<ItemT, LC>;
|
|
70
|
-
|
|
71
5
|
export type TriggerRect = {
|
|
72
6
|
x: number;
|
|
73
7
|
y: number;
|
|
@@ -109,7 +43,7 @@ export interface TriggerAnimationConfig extends WithTimingConfig {
|
|
|
109
43
|
onAnimationComplete?: () => void;
|
|
110
44
|
}
|
|
111
45
|
|
|
112
|
-
export interface GestureViewerProps<ItemT
|
|
46
|
+
export interface GestureViewerProps<ItemT> {
|
|
113
47
|
/**
|
|
114
48
|
* When you want to efficiently manage multiple `GestureViewer` instances, you can use the `id` prop to use multiple `GestureViewer` components.
|
|
115
49
|
* @remarks `GestureViewer` automatically removes instances from memory when components are unmounted, so no manual memory management is required.
|
|
@@ -137,8 +71,8 @@ export interface GestureViewerProps<ItemT, LC> {
|
|
|
137
71
|
/**
|
|
138
72
|
* A callback function that is called to render the item.
|
|
139
73
|
* @param item - The item to render.
|
|
140
|
-
* @param index - The
|
|
141
|
-
* @param info - Render state for this
|
|
74
|
+
* @param index - The index of the item in `data`.
|
|
75
|
+
* @param info - Render state for this mounted slot.
|
|
142
76
|
*/
|
|
143
77
|
renderItem: (item: ItemT, index: number, info: GestureViewerRenderItemInfo) => React.ReactElement;
|
|
144
78
|
/**
|
|
@@ -163,10 +97,6 @@ export interface GestureViewerProps<ItemT, LC> {
|
|
|
163
97
|
children: React.ReactElement,
|
|
164
98
|
helpers: { dismiss: () => void },
|
|
165
99
|
) => React.ReactElement;
|
|
166
|
-
/**
|
|
167
|
-
* Support for any list component like `ScrollView`, `FlatList`, `FlashList` through the `ListComponent` prop.
|
|
168
|
-
*/
|
|
169
|
-
ListComponent: LC;
|
|
170
100
|
/**
|
|
171
101
|
* The width of the `GestureViewer`.
|
|
172
102
|
* @remarks If you don't set this prop, the width of the `GestureViewer` will be the same as the width of the screen.
|
|
@@ -179,11 +109,6 @@ export interface GestureViewerProps<ItemT, LC> {
|
|
|
179
109
|
* @defaultValue screen height
|
|
180
110
|
*/
|
|
181
111
|
height?: number;
|
|
182
|
-
/**
|
|
183
|
-
* The props to pass to the list component.
|
|
184
|
-
* @remarks The `listProps` provides **type inference based on the selected list component**, ensuring accurate autocompletion and type safety in your IDE.
|
|
185
|
-
*/
|
|
186
|
-
listProps?: Partial<ConditionalListProps<ItemT, LC>>;
|
|
187
112
|
/**
|
|
188
113
|
* The style of the backdrop.
|
|
189
114
|
*/
|
|
@@ -245,11 +170,28 @@ export interface GestureViewerProps<ItemT, LC> {
|
|
|
245
170
|
fadeBackdrop?: boolean;
|
|
246
171
|
};
|
|
247
172
|
/**
|
|
248
|
-
*
|
|
249
|
-
* @remarks
|
|
250
|
-
* @defaultValue true
|
|
173
|
+
* Horizontal swipe gesture options.
|
|
174
|
+
* @remarks Controls user-driven left/right page swipe gestures. Controller navigation and autoplay remain available when disabled.
|
|
251
175
|
*/
|
|
252
|
-
|
|
176
|
+
horizontalSwipe?: {
|
|
177
|
+
/**
|
|
178
|
+
* When `false`, user-driven horizontal page swipe gestures are disabled.
|
|
179
|
+
* @defaultValue true
|
|
180
|
+
*/
|
|
181
|
+
enabled?: boolean;
|
|
182
|
+
/**
|
|
183
|
+
* Distance threshold as a ratio of viewer width.
|
|
184
|
+
* @remarks A page commits when absolute horizontal translation is strictly greater than `width * distanceThresholdRatio`, or when velocity passes `velocityThreshold`. Zero and finite values greater than `1` are accepted; negative and non-finite values fall back to the default.
|
|
185
|
+
* @defaultValue 0.25
|
|
186
|
+
*/
|
|
187
|
+
distanceThresholdRatio?: number;
|
|
188
|
+
/**
|
|
189
|
+
* Horizontal velocity threshold in points per second.
|
|
190
|
+
* @remarks A page commits when absolute horizontal velocity is strictly greater than `velocityThreshold`, or when distance passes `distanceThresholdRatio`. Zero and every finite non-negative value are accepted; negative and non-finite values fall back to the default.
|
|
191
|
+
* @defaultValue 800
|
|
192
|
+
*/
|
|
193
|
+
velocityThreshold?: number;
|
|
194
|
+
};
|
|
253
195
|
/**
|
|
254
196
|
* Only works when zoom is active, allows moving item position when zoomed.
|
|
255
197
|
* @remarks When `false`, gesture movement is disabled during zoom.
|
|
@@ -274,25 +216,17 @@ export interface GestureViewerProps<ItemT, LC> {
|
|
|
274
216
|
*/
|
|
275
217
|
enableLoop?: boolean;
|
|
276
218
|
/**
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
* @
|
|
280
|
-
* **`false` (default)**: Paging mode (`pagingEnabled: true`)
|
|
281
|
-
* - Scrolls by full screen size increments
|
|
282
|
-
*
|
|
283
|
-
* **`true`**: Snap mode (`snapToInterval` auto-calculated)
|
|
284
|
-
* - `snapToInterval` is automatically calculated based on `width` and `itemSpacing` values
|
|
285
|
-
* - Use this option when you need item spacing
|
|
286
|
-
* @defaultValue false
|
|
287
|
-
*
|
|
219
|
+
* Number of mounted render-window slots including the current item.
|
|
220
|
+
* @remarks Values are normalized to an odd number of at least 3. For example, `4` becomes `5`.
|
|
221
|
+
* @defaultValue 3
|
|
288
222
|
*/
|
|
289
|
-
|
|
223
|
+
windowSize?: number;
|
|
290
224
|
/**
|
|
291
|
-
*
|
|
292
|
-
* @remarks
|
|
225
|
+
* Horizontal visual space between pages.
|
|
226
|
+
* @remarks Page stride is `width + pageSpacing`; zoom bounds still use `width` and `height`.
|
|
293
227
|
* @defaultValue 0
|
|
294
228
|
*/
|
|
295
|
-
|
|
229
|
+
pageSpacing?: number;
|
|
296
230
|
/**
|
|
297
231
|
* The maximum zoom scale.
|
|
298
232
|
* @defaultValue 2
|
|
@@ -334,7 +268,7 @@ export type GestureViewerController = {
|
|
|
334
268
|
* Updates the currentIndex in the controller state.
|
|
335
269
|
*
|
|
336
270
|
* @param index - The target index (must be between 0 and totalCount - 1)
|
|
337
|
-
*
|
|
271
|
+
* Out-of-range indexes are ignored.
|
|
338
272
|
*
|
|
339
273
|
* @example
|
|
340
274
|
* ```typescript
|
|
@@ -344,7 +278,7 @@ export type GestureViewerController = {
|
|
|
344
278
|
* controller.goToIndex(totalCount - 1); // Go to last item
|
|
345
279
|
* ```
|
|
346
280
|
*/
|
|
347
|
-
goToIndex: (index: number) => void;
|
|
281
|
+
goToIndex: (index: number, options?: { animated?: boolean }) => void;
|
|
348
282
|
|
|
349
283
|
/**
|
|
350
284
|
* Navigates to the previous item in the sequence.
|