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
package/src/useGestureViewer.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { Platform, type View, useWindowDimensions } from 'react-native';
|
|
3
3
|
import { Gesture, type GestureType } from 'react-native-gesture-handler';
|
|
4
4
|
import {
|
|
@@ -14,10 +14,23 @@ import { scheduleOnRN } from 'react-native-worklets';
|
|
|
14
14
|
|
|
15
15
|
import type GestureViewerManager from './GestureViewerManager';
|
|
16
16
|
import { registry } from './GestureViewerRegistry';
|
|
17
|
+
import {
|
|
18
|
+
type ItemDimensionsRegistry,
|
|
19
|
+
pruneItemDimensionsRegistry,
|
|
20
|
+
registerItemDimensions,
|
|
21
|
+
resolveItemDimensions,
|
|
22
|
+
} from './itemDimensions';
|
|
17
23
|
import { scheduleInitialScroll } from './scheduleInitialScroll';
|
|
18
|
-
import type { GestureViewerProps, TriggerRect } from './types';
|
|
24
|
+
import type { GestureViewerItemDimensions, GestureViewerProps, TriggerRect } from './types';
|
|
19
25
|
import { useGestureViewerPaging } from './useGestureViewerPaging';
|
|
20
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
clampIndex,
|
|
28
|
+
clampTranslationToBounds,
|
|
29
|
+
createScrollAction,
|
|
30
|
+
getLoopAdjustedIndex,
|
|
31
|
+
getLoopPhysicalIndex,
|
|
32
|
+
resolveGeometrySyncTranslationMode,
|
|
33
|
+
} from './utils';
|
|
21
34
|
import { getDismissDistance, shouldDismissByDirection } from './utils/dismiss';
|
|
22
35
|
import { applyTapZoomAtPoint } from './utils/tapZoom';
|
|
23
36
|
import { calculateFocalPointTranslation, shouldAcceptFocalPoint } from './utils/zoom';
|
|
@@ -33,6 +46,43 @@ type UseGestureViewerProps<ItemT, LC> = Omit<
|
|
|
33
46
|
| 'enableSnapMode'
|
|
34
47
|
>;
|
|
35
48
|
|
|
49
|
+
type ViewerPositionSnapshot = Readonly<{
|
|
50
|
+
dataLength: number;
|
|
51
|
+
initialIndex: number;
|
|
52
|
+
pageStride: number;
|
|
53
|
+
usesLoopSentinels: boolean;
|
|
54
|
+
}>;
|
|
55
|
+
|
|
56
|
+
function getViewerPositionChanges(
|
|
57
|
+
previous: ViewerPositionSnapshot | null,
|
|
58
|
+
current: ViewerPositionSnapshot,
|
|
59
|
+
) {
|
|
60
|
+
return {
|
|
61
|
+
isInitial: previous === null,
|
|
62
|
+
didDataLengthChange: previous !== null && previous.dataLength !== current.dataLength,
|
|
63
|
+
didInitialIndexChange: previous !== null && previous.initialIndex !== current.initialIndex,
|
|
64
|
+
didLoopLayoutChange:
|
|
65
|
+
previous !== null && previous.usesLoopSentinels !== current.usesLoopSentinels,
|
|
66
|
+
didPageStrideChange: previous !== null && previous.pageStride !== current.pageStride,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function fitItemDimensions(
|
|
71
|
+
dimensions: GestureViewerItemDimensions | undefined,
|
|
72
|
+
viewport: GestureViewerItemDimensions,
|
|
73
|
+
): GestureViewerItemDimensions {
|
|
74
|
+
if (!dimensions) {
|
|
75
|
+
return viewport;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const fitScale = Math.min(viewport.width / dimensions.width, viewport.height / dimensions.height);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
width: dimensions.width * fitScale,
|
|
82
|
+
height: dimensions.height * fitScale,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
36
86
|
export const useGestureViewer = <ItemT, LC>({
|
|
37
87
|
data,
|
|
38
88
|
initialIndex = 0,
|
|
@@ -53,6 +103,8 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
53
103
|
triggerAnimation,
|
|
54
104
|
autoPlay = false,
|
|
55
105
|
autoPlayInterval = 3000,
|
|
106
|
+
getItemDimensions,
|
|
107
|
+
getItemKey,
|
|
56
108
|
}: UseGestureViewerProps<ItemT, LC>) => {
|
|
57
109
|
const { width: screenWidth, height: screenHeight } = useWindowDimensions();
|
|
58
110
|
const width = customWidth || screenWidth;
|
|
@@ -74,7 +126,11 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
74
126
|
const onAnimationCompleteRef = useRef(triggerAnimation?.onAnimationComplete);
|
|
75
127
|
const onSingleTapRef = useRef(onSingleTap);
|
|
76
128
|
const dataRef = useRef(data);
|
|
129
|
+
const getItemDimensionsRef = useRef(getItemDimensions);
|
|
130
|
+
const getItemKeyRef = useRef(getItemKey);
|
|
77
131
|
const managerRef = useRef(manager);
|
|
132
|
+
const configuredManagerRef = useRef<GestureViewerManager | null>(null);
|
|
133
|
+
const itemDimensionsRef = useRef<ItemDimensionsRegistry<ItemT>>(new Map());
|
|
78
134
|
|
|
79
135
|
const isValidTriggerRect = useCallback((rect: TriggerRect | null): rect is TriggerRect => {
|
|
80
136
|
return !!rect && rect.width > 0 && rect.height > 0;
|
|
@@ -89,6 +145,8 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
89
145
|
const scale = useSharedValue(1);
|
|
90
146
|
const backdropOpacity = useSharedValue(1);
|
|
91
147
|
const rotation = useSharedValue(0);
|
|
148
|
+
const contentWidth = useSharedValue(width);
|
|
149
|
+
const contentHeight = useSharedValue(height);
|
|
92
150
|
|
|
93
151
|
const triggerScale = useSharedValue(1);
|
|
94
152
|
const triggerTranslateX = useSharedValue(0);
|
|
@@ -102,7 +160,122 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
102
160
|
const hasActiveFocal = useSharedValue(false);
|
|
103
161
|
|
|
104
162
|
const dataLength = data?.length || 0;
|
|
105
|
-
const
|
|
163
|
+
const usesLoopSentinels = enableLoop && dataLength > 1;
|
|
164
|
+
const pageStride = width + itemSpacing;
|
|
165
|
+
const adjustedInitialIndex = getLoopPhysicalIndex(initialIndex, dataLength, enableLoop);
|
|
166
|
+
// Manager setup can rerender before a deferred list scroll runs, so each consumer tracks
|
|
167
|
+
// the last committed position inputs independently.
|
|
168
|
+
const managerPositionSnapshotRef = useRef<ViewerPositionSnapshot | null>(null);
|
|
169
|
+
const listPositionSnapshotRef = useRef<ViewerPositionSnapshot | null>(null);
|
|
170
|
+
const viewportRef = useRef({ height, width });
|
|
171
|
+
const loopConfigRef = useRef({ dataLength, enableLoop });
|
|
172
|
+
const activeGeometryRef = useRef<{
|
|
173
|
+
contentHeight: number;
|
|
174
|
+
contentWidth: number;
|
|
175
|
+
height: number;
|
|
176
|
+
width: number;
|
|
177
|
+
} | null>(null);
|
|
178
|
+
const activeGeometryIndexRef = useRef<number | null>(null);
|
|
179
|
+
|
|
180
|
+
const syncActiveContentDimensions = useCallback(
|
|
181
|
+
(logicalIndex = pendingIndexRef.current) => {
|
|
182
|
+
const viewport = viewportRef.current;
|
|
183
|
+
const fitted = fitItemDimensions(
|
|
184
|
+
resolveItemDimensions({
|
|
185
|
+
data: dataRef.current,
|
|
186
|
+
getItemDimensions: getItemDimensionsRef.current,
|
|
187
|
+
getItemKey: getItemKeyRef.current,
|
|
188
|
+
index: logicalIndex,
|
|
189
|
+
registry: itemDimensionsRef.current,
|
|
190
|
+
}),
|
|
191
|
+
viewport,
|
|
192
|
+
);
|
|
193
|
+
const nextGeometry = {
|
|
194
|
+
contentHeight: fitted.height,
|
|
195
|
+
contentWidth: fitted.width,
|
|
196
|
+
height: viewport.height,
|
|
197
|
+
width: viewport.width,
|
|
198
|
+
};
|
|
199
|
+
const previousGeometry = activeGeometryRef.current;
|
|
200
|
+
const previousGeometryIndex = activeGeometryIndexRef.current;
|
|
201
|
+
|
|
202
|
+
activeGeometryIndexRef.current = logicalIndex;
|
|
203
|
+
|
|
204
|
+
const currentScale = scale.get();
|
|
205
|
+
const translationMode = resolveGeometrySyncTranslationMode(
|
|
206
|
+
previousGeometryIndex,
|
|
207
|
+
logicalIndex,
|
|
208
|
+
currentScale,
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
if (translationMode === 'reset') {
|
|
212
|
+
// Complete the page-owned reset before the next item's geometry can reuse the old offset.
|
|
213
|
+
translateX.set(0);
|
|
214
|
+
translateY.set(0);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (
|
|
218
|
+
previousGeometry?.contentHeight === nextGeometry.contentHeight &&
|
|
219
|
+
previousGeometry.contentWidth === nextGeometry.contentWidth &&
|
|
220
|
+
previousGeometry.height === nextGeometry.height &&
|
|
221
|
+
previousGeometry.width === nextGeometry.width
|
|
222
|
+
) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
activeGeometryRef.current = nextGeometry;
|
|
227
|
+
|
|
228
|
+
if (contentWidth.get() !== fitted.width) {
|
|
229
|
+
contentWidth.set(fitted.width);
|
|
230
|
+
}
|
|
231
|
+
if (contentHeight.get() !== fitted.height) {
|
|
232
|
+
contentHeight.set(fitted.height);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (translationMode !== 'constrain') {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } =
|
|
240
|
+
clampTranslationToBounds({
|
|
241
|
+
contentHeight: fitted.height,
|
|
242
|
+
contentWidth: fitted.width,
|
|
243
|
+
height: viewport.height,
|
|
244
|
+
scale: currentScale,
|
|
245
|
+
translateX: translateX.get(),
|
|
246
|
+
translateY: translateY.get(),
|
|
247
|
+
width: viewport.width,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
translateX.set(withTiming(constrainedTranslateX));
|
|
251
|
+
translateY.set(withTiming(constrainedTranslateY));
|
|
252
|
+
},
|
|
253
|
+
[contentHeight, contentWidth, scale, translateX, translateY],
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
const setItemDimensions = useCallback(
|
|
257
|
+
(listIndex: number, item: ItemT, dimensions: GestureViewerItemDimensions) => {
|
|
258
|
+
const { dataLength: currentDataLength, enableLoop: currentEnableLoop } =
|
|
259
|
+
loopConfigRef.current;
|
|
260
|
+
const logicalIndex =
|
|
261
|
+
currentDataLength <= 0
|
|
262
|
+
? listIndex
|
|
263
|
+
: getLoopAdjustedIndex(listIndex, currentDataLength, currentEnableLoop).realIndex;
|
|
264
|
+
const didUpdateDimensions = registerItemDimensions({
|
|
265
|
+
data: dataRef.current,
|
|
266
|
+
dimensions,
|
|
267
|
+
getItemKey: getItemKeyRef.current,
|
|
268
|
+
index: logicalIndex,
|
|
269
|
+
item,
|
|
270
|
+
registry: itemDimensionsRef.current,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
if (didUpdateDimensions && logicalIndex === pendingIndexRef.current) {
|
|
274
|
+
syncActiveContentDimensions(logicalIndex);
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
[syncActiveContentDimensions],
|
|
278
|
+
);
|
|
106
279
|
|
|
107
280
|
const animationConfig = useMemo(
|
|
108
281
|
() => ({
|
|
@@ -130,26 +303,38 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
130
303
|
],
|
|
131
304
|
);
|
|
132
305
|
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
306
|
+
const constrainTranslation = useCallback(
|
|
307
|
+
({
|
|
308
|
+
scale: targetScale,
|
|
309
|
+
translateX: targetTranslateX,
|
|
310
|
+
translateY: targetTranslateY,
|
|
311
|
+
}: {
|
|
312
|
+
translateX: number;
|
|
313
|
+
translateY: number;
|
|
314
|
+
scale: number;
|
|
315
|
+
}) => {
|
|
316
|
+
'worklet';
|
|
317
|
+
|
|
318
|
+
return clampTranslationToBounds({
|
|
319
|
+
contentHeight: contentHeight.get(),
|
|
320
|
+
contentWidth: contentWidth.get(),
|
|
321
|
+
height,
|
|
322
|
+
scale: targetScale,
|
|
323
|
+
translateX: targetTranslateX,
|
|
324
|
+
translateY: targetTranslateY,
|
|
325
|
+
width,
|
|
326
|
+
});
|
|
327
|
+
},
|
|
328
|
+
[contentHeight, contentWidth, height, width],
|
|
144
329
|
);
|
|
145
330
|
|
|
146
331
|
const scrollTo = useCallback(
|
|
147
332
|
(index: number, animated: boolean) => {
|
|
148
|
-
const scrollAction = createScrollAction(listRef.current,
|
|
333
|
+
const scrollAction = createScrollAction(listRef.current, pageStride);
|
|
149
334
|
|
|
150
335
|
return scrollAction.scrollTo(index, animated);
|
|
151
336
|
},
|
|
152
|
-
[
|
|
337
|
+
[pageStride],
|
|
153
338
|
);
|
|
154
339
|
|
|
155
340
|
const resetTransformState = useCallback(() => {
|
|
@@ -168,9 +353,14 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
168
353
|
return;
|
|
169
354
|
}
|
|
170
355
|
|
|
356
|
+
if (nextIndex === pendingIndexRef.current) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
171
360
|
pendingIndexRef.current = nextIndex;
|
|
361
|
+
syncActiveContentDimensions(nextIndex);
|
|
172
362
|
},
|
|
173
|
-
[dataLength],
|
|
363
|
+
[dataLength, syncActiveContentDimensions],
|
|
174
364
|
);
|
|
175
365
|
|
|
176
366
|
const syncCurrentIndex = useCallback(
|
|
@@ -180,6 +370,7 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
180
370
|
}
|
|
181
371
|
|
|
182
372
|
pendingIndexRef.current = nextIndex;
|
|
373
|
+
syncActiveContentDimensions(nextIndex);
|
|
183
374
|
|
|
184
375
|
const managerCurrentIndex = manager.getState().currentIndex;
|
|
185
376
|
|
|
@@ -191,7 +382,7 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
191
382
|
manager.notifyStateChange();
|
|
192
383
|
resetTransformState();
|
|
193
384
|
},
|
|
194
|
-
[dataLength, manager, resetTransformState],
|
|
385
|
+
[dataLength, manager, resetTransformState, syncActiveContentDimensions],
|
|
195
386
|
);
|
|
196
387
|
|
|
197
388
|
const emitZoomChange = useCallback((currentScale: number, prevScale: number | null) => {
|
|
@@ -250,37 +441,78 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
250
441
|
}, [id]);
|
|
251
442
|
|
|
252
443
|
useEffect(() => {
|
|
253
|
-
|
|
444
|
+
const currentPositionSnapshot = {
|
|
445
|
+
dataLength,
|
|
446
|
+
initialIndex,
|
|
447
|
+
pageStride,
|
|
448
|
+
usesLoopSentinels,
|
|
449
|
+
};
|
|
450
|
+
const { didDataLengthChange, didInitialIndexChange, didLoopLayoutChange } =
|
|
451
|
+
getViewerPositionChanges(managerPositionSnapshotRef.current, currentPositionSnapshot);
|
|
452
|
+
|
|
453
|
+
managerPositionSnapshotRef.current = currentPositionSnapshot;
|
|
254
454
|
|
|
255
455
|
if (!manager) {
|
|
456
|
+
configuredManagerRef.current = null;
|
|
256
457
|
return;
|
|
257
458
|
}
|
|
258
459
|
|
|
460
|
+
const isNewManager = configuredManagerRef.current !== manager;
|
|
461
|
+
const shouldApplyInitialIndex =
|
|
462
|
+
isNewManager || didInitialIndexChange || didDataLengthChange || didLoopLayoutChange;
|
|
463
|
+
const shouldSyncInitialIndex =
|
|
464
|
+
didInitialIndexChange ||
|
|
465
|
+
didDataLengthChange ||
|
|
466
|
+
didLoopLayoutChange ||
|
|
467
|
+
activeGeometryIndexRef.current !== initialIndex;
|
|
468
|
+
|
|
259
469
|
manager.setDataLength(dataLength);
|
|
260
470
|
manager.setEnableHorizontalSwipe(enableHorizontalSwipe);
|
|
261
|
-
manager.
|
|
262
|
-
manager.
|
|
471
|
+
manager.setPagingStride(pageStride);
|
|
472
|
+
manager.setViewportWidth(width);
|
|
263
473
|
manager.setHeight(height);
|
|
264
|
-
manager.setZoomSharedValues(
|
|
474
|
+
manager.setZoomSharedValues({
|
|
475
|
+
contentHeight,
|
|
476
|
+
contentWidth,
|
|
477
|
+
maxZoomScale,
|
|
478
|
+
scale,
|
|
479
|
+
translateX,
|
|
480
|
+
translateY,
|
|
481
|
+
});
|
|
265
482
|
manager.setResetTransformCallback(resetTransformState);
|
|
266
483
|
manager.setRotation(rotation);
|
|
267
484
|
manager.setEnableLoop(enableLoop);
|
|
485
|
+
|
|
486
|
+
if (shouldApplyInitialIndex) {
|
|
487
|
+
pendingIndexRef.current = initialIndex;
|
|
488
|
+
manager.setCurrentIndex(initialIndex);
|
|
489
|
+
|
|
490
|
+
if (shouldSyncInitialIndex) {
|
|
491
|
+
syncActiveContentDimensions(initialIndex);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
configuredManagerRef.current = manager;
|
|
268
496
|
manager.notifyStateChange();
|
|
269
497
|
}, [
|
|
270
498
|
dataLength,
|
|
271
499
|
enableHorizontalSwipe,
|
|
272
500
|
initialIndex,
|
|
273
501
|
manager,
|
|
502
|
+
pageStride,
|
|
274
503
|
width,
|
|
275
|
-
itemSpacing,
|
|
276
504
|
maxZoomScale,
|
|
277
505
|
enableLoop,
|
|
278
506
|
scale,
|
|
279
507
|
height,
|
|
508
|
+
contentWidth,
|
|
509
|
+
contentHeight,
|
|
280
510
|
resetTransformState,
|
|
511
|
+
syncActiveContentDimensions,
|
|
281
512
|
translateX,
|
|
282
513
|
translateY,
|
|
283
514
|
rotation,
|
|
515
|
+
usesLoopSentinels,
|
|
284
516
|
]);
|
|
285
517
|
|
|
286
518
|
useEffect(() => {
|
|
@@ -292,10 +524,35 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
292
524
|
}, [manager]);
|
|
293
525
|
|
|
294
526
|
useEffect(() => {
|
|
295
|
-
const
|
|
296
|
-
|
|
527
|
+
const currentPositionSnapshot = {
|
|
528
|
+
dataLength,
|
|
529
|
+
initialIndex,
|
|
530
|
+
pageStride,
|
|
531
|
+
usesLoopSentinels,
|
|
532
|
+
};
|
|
533
|
+
const {
|
|
534
|
+
didDataLengthChange,
|
|
535
|
+
didInitialIndexChange,
|
|
536
|
+
didLoopLayoutChange,
|
|
537
|
+
didPageStrideChange,
|
|
538
|
+
isInitial: isInitialPositionRender,
|
|
539
|
+
} = getViewerPositionChanges(listPositionSnapshotRef.current, currentPositionSnapshot);
|
|
540
|
+
const commitPositionSnapshot = () => {
|
|
541
|
+
listPositionSnapshotRef.current = currentPositionSnapshot;
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
const shouldResetToInitialIndex =
|
|
545
|
+
isInitialPositionRender ||
|
|
546
|
+
didInitialIndexChange ||
|
|
547
|
+
didDataLengthChange ||
|
|
548
|
+
didLoopLayoutChange;
|
|
549
|
+
const shouldRealignCurrentIndex = didPageStrideChange && !shouldResetToInitialIndex;
|
|
550
|
+
|
|
551
|
+
if (!shouldResetToInitialIndex && !shouldRealignCurrentIndex) {
|
|
552
|
+
commitPositionSnapshot();
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
297
555
|
|
|
298
|
-
previousDataLengthRef.current = dataLength;
|
|
299
556
|
translateY.set(0);
|
|
300
557
|
translateX.set(0);
|
|
301
558
|
scale.set(1);
|
|
@@ -303,21 +560,44 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
303
560
|
startScale.set(1);
|
|
304
561
|
rotation.set(0);
|
|
305
562
|
|
|
306
|
-
if (
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
563
|
+
if (dataLength === 0 || !listRef.current) {
|
|
564
|
+
commitPositionSnapshot();
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (isInitialPositionRender && adjustedInitialIndex === 0) {
|
|
569
|
+
commitPositionSnapshot();
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const logicalIndex = shouldResetToInitialIndex
|
|
574
|
+
? initialIndex
|
|
575
|
+
: clampIndex(
|
|
576
|
+
managerRef.current?.getState().currentIndex ?? pendingIndexRef.current,
|
|
577
|
+
dataLength,
|
|
578
|
+
);
|
|
579
|
+
const physicalIndex = getLoopPhysicalIndex(logicalIndex, dataLength, enableLoop);
|
|
580
|
+
|
|
581
|
+
if (shouldRealignCurrentIndex) {
|
|
582
|
+
pendingIndexRef.current = logicalIndex;
|
|
583
|
+
syncActiveContentDimensions(logicalIndex);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if (shouldRealignCurrentIndex && physicalIndex === 0) {
|
|
587
|
+
commitPositionSnapshot();
|
|
311
588
|
return;
|
|
312
589
|
}
|
|
313
590
|
|
|
314
591
|
return scheduleInitialScroll(() => {
|
|
315
|
-
scrollTo(
|
|
592
|
+
scrollTo(physicalIndex, false);
|
|
593
|
+
commitPositionSnapshot();
|
|
316
594
|
});
|
|
317
595
|
}, [
|
|
318
596
|
adjustedInitialIndex,
|
|
319
597
|
dataLength,
|
|
598
|
+
enableLoop,
|
|
320
599
|
initialIndex,
|
|
600
|
+
pageStride,
|
|
321
601
|
translateY,
|
|
322
602
|
backdropOpacity,
|
|
323
603
|
translateX,
|
|
@@ -325,15 +605,36 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
325
605
|
startScale,
|
|
326
606
|
rotation,
|
|
327
607
|
scrollTo,
|
|
608
|
+
syncActiveContentDimensions,
|
|
609
|
+
usesLoopSentinels,
|
|
328
610
|
]);
|
|
329
611
|
|
|
330
612
|
useEffect(() => {
|
|
331
613
|
onAnimationCompleteRef.current = triggerAnimation?.onAnimationComplete;
|
|
332
614
|
}, [triggerAnimation?.onAnimationComplete]);
|
|
333
615
|
|
|
334
|
-
|
|
616
|
+
useLayoutEffect(() => {
|
|
617
|
+
// Retained virtualized-cell callbacks read only the most recently committed props.
|
|
335
618
|
dataRef.current = data;
|
|
336
|
-
|
|
619
|
+
getItemDimensionsRef.current = getItemDimensions;
|
|
620
|
+
getItemKeyRef.current = getItemKey;
|
|
621
|
+
viewportRef.current = { height, width };
|
|
622
|
+
loopConfigRef.current = { dataLength, enableLoop };
|
|
623
|
+
syncActiveContentDimensions();
|
|
624
|
+
}, [
|
|
625
|
+
data,
|
|
626
|
+
dataLength,
|
|
627
|
+
enableLoop,
|
|
628
|
+
getItemDimensions,
|
|
629
|
+
getItemKey,
|
|
630
|
+
height,
|
|
631
|
+
syncActiveContentDimensions,
|
|
632
|
+
width,
|
|
633
|
+
]);
|
|
634
|
+
|
|
635
|
+
useEffect(() => {
|
|
636
|
+
pruneItemDimensionsRegistry(itemDimensionsRef.current, dataLength);
|
|
637
|
+
}, [dataLength]);
|
|
337
638
|
|
|
338
639
|
useEffect(() => {
|
|
339
640
|
managerRef.current = manager;
|
|
@@ -768,6 +1069,8 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
768
1069
|
.numberOfTaps(2)
|
|
769
1070
|
.onEnd((event) => {
|
|
770
1071
|
applyTapZoomAtPoint({
|
|
1072
|
+
contentHeight: contentHeight.get(),
|
|
1073
|
+
contentWidth: contentWidth.get(),
|
|
771
1074
|
x: event.x,
|
|
772
1075
|
y: event.y,
|
|
773
1076
|
width,
|
|
@@ -778,7 +1081,17 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
778
1081
|
translateY,
|
|
779
1082
|
});
|
|
780
1083
|
}),
|
|
781
|
-
[
|
|
1084
|
+
[
|
|
1085
|
+
contentHeight,
|
|
1086
|
+
contentWidth,
|
|
1087
|
+
enableDoubleTapZoom,
|
|
1088
|
+
height,
|
|
1089
|
+
maxZoomScale,
|
|
1090
|
+
scale,
|
|
1091
|
+
translateX,
|
|
1092
|
+
translateY,
|
|
1093
|
+
width,
|
|
1094
|
+
],
|
|
782
1095
|
);
|
|
783
1096
|
|
|
784
1097
|
const tapGesture = useMemo(
|
|
@@ -827,6 +1140,8 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
827
1140
|
adjustedInitialIndex,
|
|
828
1141
|
autoPlay,
|
|
829
1142
|
autoPlayInterval,
|
|
1143
|
+
contentHeight,
|
|
1144
|
+
contentWidth,
|
|
830
1145
|
currentIndex,
|
|
831
1146
|
dataLength,
|
|
832
1147
|
enableDoubleTapZoom,
|
|
@@ -866,6 +1181,7 @@ export const useGestureViewer = <ItemT, LC>({
|
|
|
866
1181
|
onScroll,
|
|
867
1182
|
|
|
868
1183
|
onScrollBeginDrag,
|
|
1184
|
+
setItemDimensions,
|
|
869
1185
|
zoomGesture,
|
|
870
1186
|
};
|
|
871
1187
|
};
|
|
@@ -25,12 +25,10 @@ export function useGestureViewerPaging({
|
|
|
25
25
|
width,
|
|
26
26
|
}: UseGestureViewerPagingArgs): UseGestureViewerPagingResult {
|
|
27
27
|
const [activeListIndex, setActiveListIndex] = useState(adjustedInitialIndex);
|
|
28
|
-
const activeResetItemSpacing = adjustedInitialIndex > 0 ? itemSpacing : 0;
|
|
29
|
-
const activeResetWidth = adjustedInitialIndex > 0 ? width : 0;
|
|
30
28
|
|
|
31
29
|
useEffect(() => {
|
|
32
30
|
setActiveListIndex(adjustedInitialIndex);
|
|
33
|
-
}, [adjustedInitialIndex,
|
|
31
|
+
}, [adjustedInitialIndex, dataLength]);
|
|
34
32
|
|
|
35
33
|
useEffect(() => {
|
|
36
34
|
if (
|
|
@@ -8,6 +8,8 @@ export type UseGestureViewerPagingArgs = {
|
|
|
8
8
|
adjustedInitialIndex: number;
|
|
9
9
|
autoPlay: boolean;
|
|
10
10
|
autoPlayInterval: number;
|
|
11
|
+
contentHeight: SharedValue<number>;
|
|
12
|
+
contentWidth: SharedValue<number>;
|
|
11
13
|
currentIndex: number;
|
|
12
14
|
dataLength: number;
|
|
13
15
|
enableDoubleTapZoom: boolean;
|