react-native-gesture-image-viewer 3.0.0-beta.2 → 3.0.0-beta.3
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/lib/module/GestureViewer.js +8 -1
- package/lib/module/GestureViewer.js.map +1 -1
- package/lib/module/GestureViewerManager.js +32 -10
- 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 +127 -10
- package/lib/module/useGestureViewer.js.map +1 -1
- package/lib/module/useGestureViewerManagerBridge.js +11 -2
- package/lib/module/useGestureViewerManagerBridge.js.map +1 -1
- package/lib/module/useWebClickHandler.js.map +1 -1
- package/lib/module/useWebClickHandler.web.js +9 -1
- package/lib/module/useWebClickHandler.web.js.map +1 -1
- package/lib/module/utils/index.js +27 -7
- package/lib/module/utils/index.js.map +1 -1
- package/lib/module/utils/tapZoom.js +47 -14
- 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 +12 -1
- 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 +28 -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/useGestureViewerManagerBridge.d.ts +3 -1
- package/lib/typescript/src/useGestureViewerManagerBridge.d.ts.map +1 -1
- package/lib/typescript/src/useWebClickHandler.d.ts +2 -0
- package/lib/typescript/src/useWebClickHandler.d.ts.map +1 -1
- package/lib/typescript/src/useWebClickHandler.web.d.ts +4 -2
- package/lib/typescript/src/useWebClickHandler.web.d.ts.map +1 -1
- package/lib/typescript/src/utils/index.d.ts +4 -2
- 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 +16 -2
- package/src/GestureViewerManager.ts +47 -15
- package/src/index.tsx +4 -0
- package/src/itemDimensions.ts +162 -0
- package/src/types.ts +38 -0
- package/src/useGestureViewer.ts +184 -13
- package/src/useGestureViewerManagerBridge.ts +24 -2
- package/src/useWebClickHandler.ts +2 -0
- package/src/useWebClickHandler.web.ts +12 -0
- package/src/utils/index.ts +55 -22
- package/src/utils/tapZoom.ts +55 -15
package/src/useGestureViewer.ts
CHANGED
|
@@ -22,6 +22,12 @@ import type GestureViewerManager from './GestureViewerManager';
|
|
|
22
22
|
import { normalizeHorizontalSwipeThreshold } from './gestureViewerPaging';
|
|
23
23
|
import { registry } from './GestureViewerRegistry';
|
|
24
24
|
import { resolveGestureViewerRenderWindow } from './gestureViewerRenderWindow';
|
|
25
|
+
import {
|
|
26
|
+
type ItemDimensionsRegistry,
|
|
27
|
+
pruneItemDimensionsRegistry,
|
|
28
|
+
registerItemDimensions,
|
|
29
|
+
resolveItemDimensions,
|
|
30
|
+
} from './itemDimensions';
|
|
25
31
|
import { enableNativeTapGestures } from './platformTapGestures';
|
|
26
32
|
import {
|
|
27
33
|
type NavigationOptions,
|
|
@@ -33,12 +39,12 @@ import {
|
|
|
33
39
|
resolveNavigation,
|
|
34
40
|
shouldRunNavigationDuringTransition,
|
|
35
41
|
} from './renderWindow';
|
|
36
|
-
import type { GestureViewerProps, TriggerRect } from './types';
|
|
42
|
+
import type { GestureViewerItemDimensions, GestureViewerProps, TriggerRect } from './types';
|
|
37
43
|
import { useGestureViewerManagerBridge } from './useGestureViewerManagerBridge';
|
|
38
44
|
import { useGestureViewerPaging } from './useGestureViewerPaging';
|
|
39
45
|
import { type EmitSingleTap, useWebClickHandler } from './useWebClickHandler';
|
|
40
46
|
import { useWebSingleTapTimer } from './useWebSingleTapTimer';
|
|
41
|
-
import {
|
|
47
|
+
import { clampTranslationToBounds, resolveGeometrySyncTranslationMode } from './utils';
|
|
42
48
|
import { getDismissDistance, shouldDismissByDirection } from './utils/dismiss';
|
|
43
49
|
import { applyTapZoomAtPoint } from './utils/tapZoom';
|
|
44
50
|
import { calculateFocalPointTranslation, shouldAcceptFocalPoint } from './utils/zoom';
|
|
@@ -54,6 +60,23 @@ type UseGestureViewerProps<ItemT> = Omit<
|
|
|
54
60
|
'renderItem' | 'renderContainer' | 'containerStyle' | 'backdropStyle'
|
|
55
61
|
>;
|
|
56
62
|
|
|
63
|
+
type ContentViewport = Readonly<{
|
|
64
|
+
height: number;
|
|
65
|
+
width: number;
|
|
66
|
+
}>;
|
|
67
|
+
|
|
68
|
+
function fitItemDimensions(
|
|
69
|
+
dimensions: GestureViewerItemDimensions | undefined,
|
|
70
|
+
viewport: ContentViewport,
|
|
71
|
+
): GestureViewerItemDimensions {
|
|
72
|
+
if (!dimensions) {
|
|
73
|
+
return viewport;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const fit = Math.min(viewport.width / dimensions.width, viewport.height / dimensions.height);
|
|
77
|
+
return { height: dimensions.height * fit, width: dimensions.width * fit };
|
|
78
|
+
}
|
|
79
|
+
|
|
57
80
|
export const useGestureViewer = <ItemT>({
|
|
58
81
|
data,
|
|
59
82
|
initialIndex = 0,
|
|
@@ -75,6 +98,8 @@ export const useGestureViewer = <ItemT>({
|
|
|
75
98
|
triggerAnimation,
|
|
76
99
|
autoPlay = false,
|
|
77
100
|
autoPlayInterval = 3000,
|
|
101
|
+
getItemDimensions,
|
|
102
|
+
getItemKey,
|
|
78
103
|
}: UseGestureViewerProps<ItemT>) => {
|
|
79
104
|
const { width: screenWidth, height: screenHeight } = useWindowDimensions();
|
|
80
105
|
const width = customWidth || screenWidth;
|
|
@@ -117,12 +142,23 @@ export const useGestureViewer = <ItemT>({
|
|
|
117
142
|
const onAnimationCompleteRef = useRef(triggerAnimation?.onAnimationComplete);
|
|
118
143
|
const onSingleTapRef = useRef(onSingleTap);
|
|
119
144
|
const dataRef = useRef(data);
|
|
145
|
+
const getItemDimensionsRef = useRef(getItemDimensions);
|
|
146
|
+
const getItemKeyRef = useRef(getItemKey);
|
|
120
147
|
const dataLengthRef = useRef(dataLength);
|
|
121
148
|
const enableLoopRef = useRef(enableLoop);
|
|
122
149
|
const managerRef = useRef<GestureViewerManager | null>(null);
|
|
123
150
|
const isZoomedRef = useRef(isZoomed);
|
|
124
151
|
const isRotatedRef = useRef(isRotated);
|
|
125
152
|
const isPinchingRef = useRef(isPinching);
|
|
153
|
+
const itemDimensionsRef = useRef<ItemDimensionsRegistry<ItemT>>(new Map());
|
|
154
|
+
const viewportRef = useRef({ height, width });
|
|
155
|
+
const activeGeometryRef = useRef<{
|
|
156
|
+
contentHeight: number;
|
|
157
|
+
contentWidth: number;
|
|
158
|
+
height: number;
|
|
159
|
+
width: number;
|
|
160
|
+
} | null>(null);
|
|
161
|
+
const activeGeometryIndexRef = useRef<number | null>(null);
|
|
126
162
|
|
|
127
163
|
const initialTranslateY = useSharedValue(0);
|
|
128
164
|
const initialTranslateX = useSharedValue(0);
|
|
@@ -132,6 +168,8 @@ export const useGestureViewer = <ItemT>({
|
|
|
132
168
|
const translateX = useSharedValue(0);
|
|
133
169
|
const scale = useSharedValue(1);
|
|
134
170
|
const rotation = useSharedValue(0);
|
|
171
|
+
const contentWidth = useSharedValue(width);
|
|
172
|
+
const contentHeight = useSharedValue(height);
|
|
135
173
|
|
|
136
174
|
const triggerScale = useSharedValue(1);
|
|
137
175
|
const triggerTranslateX = useSharedValue(0);
|
|
@@ -189,9 +227,121 @@ export const useGestureViewer = <ItemT>({
|
|
|
189
227
|
windowSize,
|
|
190
228
|
});
|
|
191
229
|
|
|
192
|
-
const
|
|
193
|
-
() =>
|
|
194
|
-
|
|
230
|
+
const syncActiveContentDimensions = useCallback(
|
|
231
|
+
(index = pendingIndexRef.current) => {
|
|
232
|
+
const viewport = viewportRef.current;
|
|
233
|
+
const fitted = fitItemDimensions(
|
|
234
|
+
resolveItemDimensions({
|
|
235
|
+
data: dataRef.current,
|
|
236
|
+
getItemKey: getItemKeyRef.current,
|
|
237
|
+
getItemDimensions: getItemDimensionsRef.current,
|
|
238
|
+
index,
|
|
239
|
+
registry: itemDimensionsRef.current,
|
|
240
|
+
}),
|
|
241
|
+
viewport,
|
|
242
|
+
);
|
|
243
|
+
const nextGeometry = {
|
|
244
|
+
contentHeight: fitted.height,
|
|
245
|
+
contentWidth: fitted.width,
|
|
246
|
+
height: viewport.height,
|
|
247
|
+
width: viewport.width,
|
|
248
|
+
};
|
|
249
|
+
const previousGeometry = activeGeometryRef.current;
|
|
250
|
+
const previousGeometryIndex = activeGeometryIndexRef.current;
|
|
251
|
+
|
|
252
|
+
activeGeometryIndexRef.current = index;
|
|
253
|
+
|
|
254
|
+
const currentScale = scale.get();
|
|
255
|
+
const translationMode = resolveGeometrySyncTranslationMode(
|
|
256
|
+
previousGeometryIndex,
|
|
257
|
+
index,
|
|
258
|
+
currentScale,
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
if (translationMode === 'reset') {
|
|
262
|
+
// Complete the page-owned reset before the next item's geometry can reuse the old offset.
|
|
263
|
+
translateX.set(0);
|
|
264
|
+
translateY.set(0);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (
|
|
268
|
+
previousGeometry?.contentHeight === nextGeometry.contentHeight &&
|
|
269
|
+
previousGeometry.contentWidth === nextGeometry.contentWidth &&
|
|
270
|
+
previousGeometry.height === nextGeometry.height &&
|
|
271
|
+
previousGeometry.width === nextGeometry.width
|
|
272
|
+
) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
activeGeometryRef.current = nextGeometry;
|
|
277
|
+
|
|
278
|
+
if (contentWidth.get() !== fitted.width) {
|
|
279
|
+
contentWidth.set(fitted.width);
|
|
280
|
+
}
|
|
281
|
+
if (contentHeight.get() !== fitted.height) {
|
|
282
|
+
contentHeight.set(fitted.height);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (translationMode !== 'constrain') {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const constrained = clampTranslationToBounds({
|
|
290
|
+
contentHeight: fitted.height,
|
|
291
|
+
contentWidth: fitted.width,
|
|
292
|
+
height: viewport.height,
|
|
293
|
+
width: viewport.width,
|
|
294
|
+
scale: currentScale,
|
|
295
|
+
translateX: translateX.get(),
|
|
296
|
+
translateY: translateY.get(),
|
|
297
|
+
});
|
|
298
|
+
translateX.set(withTiming(constrained.translateX));
|
|
299
|
+
translateY.set(withTiming(constrained.translateY));
|
|
300
|
+
},
|
|
301
|
+
[contentHeight, contentWidth, scale, translateX, translateY],
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
const setItemDimensions = useCallback(
|
|
305
|
+
(index: number, item: ItemT, dimensions: GestureViewerItemDimensions) => {
|
|
306
|
+
if (
|
|
307
|
+
registerItemDimensions({
|
|
308
|
+
data: dataRef.current,
|
|
309
|
+
dimensions,
|
|
310
|
+
getItemKey: getItemKeyRef.current,
|
|
311
|
+
index,
|
|
312
|
+
item,
|
|
313
|
+
registry: itemDimensionsRef.current,
|
|
314
|
+
}) === 'updated' &&
|
|
315
|
+
index === pendingIndexRef.current
|
|
316
|
+
) {
|
|
317
|
+
syncActiveContentDimensions(index);
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
[syncActiveContentDimensions],
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
const constrainTranslation = useCallback(
|
|
324
|
+
({
|
|
325
|
+
scale: targetScale,
|
|
326
|
+
translateX: targetX,
|
|
327
|
+
translateY: targetY,
|
|
328
|
+
}: {
|
|
329
|
+
scale: number;
|
|
330
|
+
translateX: number;
|
|
331
|
+
translateY: number;
|
|
332
|
+
}) => {
|
|
333
|
+
'worklet';
|
|
334
|
+
return clampTranslationToBounds({
|
|
335
|
+
contentHeight: contentHeight.get(),
|
|
336
|
+
contentWidth: contentWidth.get(),
|
|
337
|
+
height,
|
|
338
|
+
scale: targetScale,
|
|
339
|
+
translateX: targetX,
|
|
340
|
+
translateY: targetY,
|
|
341
|
+
width,
|
|
342
|
+
});
|
|
343
|
+
},
|
|
344
|
+
[contentHeight, contentWidth, height, width],
|
|
195
345
|
);
|
|
196
346
|
|
|
197
347
|
const resetTransformState = useCallback(() => {
|
|
@@ -221,12 +371,16 @@ export const useGestureViewer = <ItemT>({
|
|
|
221
371
|
rotation.set(0);
|
|
222
372
|
}, [initialTranslateX, initialTranslateY, rotation, scale, startScale, translateX, translateY]);
|
|
223
373
|
|
|
224
|
-
const commitCurrentIndex = useCallback(
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
374
|
+
const commitCurrentIndex = useCallback(
|
|
375
|
+
(nextIndex: number) => {
|
|
376
|
+
pendingIndexRef.current = nextIndex;
|
|
377
|
+
currentIndexRef.current = nextIndex;
|
|
378
|
+
syncActiveContentDimensions(nextIndex);
|
|
379
|
+
setCurrentIndex(nextIndex);
|
|
380
|
+
managerRef.current?.notifyStateChange();
|
|
381
|
+
},
|
|
382
|
+
[syncActiveContentDimensions],
|
|
383
|
+
);
|
|
230
384
|
|
|
231
385
|
const commitVirtualIndexOnly = useCallback(
|
|
232
386
|
(nextVirtualIndex: number) => {
|
|
@@ -360,6 +514,8 @@ export const useGestureViewer = <ItemT>({
|
|
|
360
514
|
}, [navigateByDirection]);
|
|
361
515
|
|
|
362
516
|
useGestureViewerManagerBridge({
|
|
517
|
+
contentHeight,
|
|
518
|
+
contentWidth,
|
|
363
519
|
currentIndexRef,
|
|
364
520
|
dataLengthRef,
|
|
365
521
|
goToIndex: navigateToIndex,
|
|
@@ -433,9 +589,17 @@ export const useGestureViewer = <ItemT>({
|
|
|
433
589
|
});
|
|
434
590
|
}, [id]);
|
|
435
591
|
|
|
436
|
-
|
|
592
|
+
useLayoutEffect(() => {
|
|
437
593
|
dataRef.current = data;
|
|
438
|
-
|
|
594
|
+
getItemDimensionsRef.current = getItemDimensions;
|
|
595
|
+
getItemKeyRef.current = getItemKey;
|
|
596
|
+
viewportRef.current = { height, width };
|
|
597
|
+
syncActiveContentDimensions();
|
|
598
|
+
}, [data, dataLength, getItemDimensions, getItemKey, height, syncActiveContentDimensions, width]);
|
|
599
|
+
|
|
600
|
+
useEffect(() => {
|
|
601
|
+
pruneItemDimensionsRegistry(itemDimensionsRef.current, dataLength);
|
|
602
|
+
}, [dataLength]);
|
|
439
603
|
|
|
440
604
|
useEffect(() => {
|
|
441
605
|
isZoomedRef.current = isZoomed;
|
|
@@ -910,6 +1074,8 @@ export const useGestureViewer = <ItemT>({
|
|
|
910
1074
|
}
|
|
911
1075
|
|
|
912
1076
|
applyTapZoomAtPoint({
|
|
1077
|
+
contentHeight: contentHeight.get(),
|
|
1078
|
+
contentWidth: contentWidth.get(),
|
|
913
1079
|
x: event.x,
|
|
914
1080
|
y: event.y,
|
|
915
1081
|
width,
|
|
@@ -921,6 +1087,8 @@ export const useGestureViewer = <ItemT>({
|
|
|
921
1087
|
});
|
|
922
1088
|
}),
|
|
923
1089
|
[
|
|
1090
|
+
contentHeight,
|
|
1091
|
+
contentWidth,
|
|
924
1092
|
enableDoubleTapZoom,
|
|
925
1093
|
height,
|
|
926
1094
|
maxZoomScale,
|
|
@@ -1199,6 +1367,8 @@ export const useGestureViewer = <ItemT>({
|
|
|
1199
1367
|
|
|
1200
1368
|
const onWebClick = useWebClickHandler({
|
|
1201
1369
|
clearPendingWebSingleTap,
|
|
1370
|
+
contentHeight,
|
|
1371
|
+
contentWidth,
|
|
1202
1372
|
emitSingleTap,
|
|
1203
1373
|
enableDoubleTapZoom,
|
|
1204
1374
|
getCurrentTapTarget,
|
|
@@ -1255,6 +1425,7 @@ export const useGestureViewer = <ItemT>({
|
|
|
1255
1425
|
pageStride,
|
|
1256
1426
|
renderWindowSlots,
|
|
1257
1427
|
visualPage,
|
|
1428
|
+
setItemDimensions,
|
|
1258
1429
|
zoomGesture,
|
|
1259
1430
|
zoomPinchGesture,
|
|
1260
1431
|
};
|
|
@@ -7,6 +7,8 @@ import { registry } from './GestureViewerRegistry';
|
|
|
7
7
|
|
|
8
8
|
type UseGestureViewerManagerBridgeOptions = {
|
|
9
9
|
currentIndexRef: RefObject<number>;
|
|
10
|
+
contentHeight: SharedValue<number>;
|
|
11
|
+
contentWidth: SharedValue<number>;
|
|
10
12
|
dataLengthRef: RefObject<number>;
|
|
11
13
|
goToIndex: (index: number, options?: GestureViewerNavigationOptions) => void;
|
|
12
14
|
goToNext: () => void;
|
|
@@ -29,6 +31,8 @@ type UseGestureViewerManagerBridgeOptions = {
|
|
|
29
31
|
*/
|
|
30
32
|
export function useGestureViewerManagerBridge({
|
|
31
33
|
currentIndexRef,
|
|
34
|
+
contentHeight,
|
|
35
|
+
contentWidth,
|
|
32
36
|
dataLengthRef,
|
|
33
37
|
goToIndex,
|
|
34
38
|
goToNext,
|
|
@@ -61,9 +65,27 @@ export function useGestureViewerManagerBridge({
|
|
|
61
65
|
|
|
62
66
|
manager.setWidth(width);
|
|
63
67
|
manager.setHeight(height);
|
|
64
|
-
manager.setZoomSharedValues(
|
|
68
|
+
manager.setZoomSharedValues({
|
|
69
|
+
scale,
|
|
70
|
+
translateX,
|
|
71
|
+
translateY,
|
|
72
|
+
maxZoomScale,
|
|
73
|
+
contentWidth,
|
|
74
|
+
contentHeight,
|
|
75
|
+
});
|
|
65
76
|
manager.setRotation(rotation);
|
|
66
|
-
}, [
|
|
77
|
+
}, [
|
|
78
|
+
contentHeight,
|
|
79
|
+
contentWidth,
|
|
80
|
+
height,
|
|
81
|
+
manager,
|
|
82
|
+
maxZoomScale,
|
|
83
|
+
rotation,
|
|
84
|
+
scale,
|
|
85
|
+
translateX,
|
|
86
|
+
translateY,
|
|
87
|
+
width,
|
|
88
|
+
]);
|
|
67
89
|
|
|
68
90
|
useEffect(() => {
|
|
69
91
|
if (!manager) {
|
|
@@ -26,6 +26,8 @@ export type EmitSingleTap<ItemT> = (x: number, y: number, tapTarget?: WebTapTarg
|
|
|
26
26
|
|
|
27
27
|
export type WebClickHandlerConfig<ItemT> = {
|
|
28
28
|
clearPendingWebSingleTap: () => void;
|
|
29
|
+
contentHeight: SharedValue<number>;
|
|
30
|
+
contentWidth: SharedValue<number>;
|
|
29
31
|
emitSingleTap: EmitSingleTap<ItemT>;
|
|
30
32
|
enableDoubleTapZoom: boolean;
|
|
31
33
|
getCurrentTapTarget: () => WebTapTarget<ItemT> | null;
|
|
@@ -27,6 +27,8 @@ export type EmitSingleTap<ItemT> = (x: number, y: number, tapTarget?: WebTapTarg
|
|
|
27
27
|
|
|
28
28
|
export type WebClickHandlerConfig<ItemT> = {
|
|
29
29
|
clearPendingWebSingleTap: () => void;
|
|
30
|
+
contentHeight: SharedValue<number>;
|
|
31
|
+
contentWidth: SharedValue<number>;
|
|
30
32
|
emitSingleTap: EmitSingleTap<ItemT>;
|
|
31
33
|
enableDoubleTapZoom: boolean;
|
|
32
34
|
getCurrentTapTarget: () => WebTapTarget<ItemT> | null;
|
|
@@ -42,6 +44,8 @@ export type WebClickHandlerConfig<ItemT> = {
|
|
|
42
44
|
|
|
43
45
|
export function createWebClickHandler<ItemT>({
|
|
44
46
|
clearPendingWebSingleTap,
|
|
47
|
+
contentHeight,
|
|
48
|
+
contentWidth,
|
|
45
49
|
emitSingleTap,
|
|
46
50
|
enableDoubleTapZoom,
|
|
47
51
|
getCurrentTapTarget,
|
|
@@ -78,6 +82,8 @@ export function createWebClickHandler<ItemT>({
|
|
|
78
82
|
if (event.detail === 2) {
|
|
79
83
|
clearPendingWebSingleTap();
|
|
80
84
|
applyTapZoomAtPoint({
|
|
85
|
+
contentHeight: contentHeight.get(),
|
|
86
|
+
contentWidth: contentWidth.get(),
|
|
81
87
|
x,
|
|
82
88
|
y,
|
|
83
89
|
width,
|
|
@@ -100,6 +106,8 @@ export function createWebClickHandler<ItemT>({
|
|
|
100
106
|
|
|
101
107
|
export function useWebClickHandler<ItemT>({
|
|
102
108
|
clearPendingWebSingleTap,
|
|
109
|
+
contentHeight,
|
|
110
|
+
contentWidth,
|
|
103
111
|
emitSingleTap,
|
|
104
112
|
enableDoubleTapZoom,
|
|
105
113
|
getCurrentTapTarget,
|
|
@@ -116,6 +124,8 @@ export function useWebClickHandler<ItemT>({
|
|
|
116
124
|
() =>
|
|
117
125
|
createWebClickHandler({
|
|
118
126
|
clearPendingWebSingleTap,
|
|
127
|
+
contentHeight,
|
|
128
|
+
contentWidth,
|
|
119
129
|
emitSingleTap,
|
|
120
130
|
enableDoubleTapZoom,
|
|
121
131
|
getCurrentTapTarget,
|
|
@@ -130,6 +140,8 @@ export function useWebClickHandler<ItemT>({
|
|
|
130
140
|
}),
|
|
131
141
|
[
|
|
132
142
|
clearPendingWebSingleTap,
|
|
143
|
+
contentHeight,
|
|
144
|
+
contentWidth,
|
|
133
145
|
emitSingleTap,
|
|
134
146
|
enableDoubleTapZoom,
|
|
135
147
|
getCurrentTapTarget,
|
package/src/utils/index.ts
CHANGED
|
@@ -1,28 +1,61 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
(
|
|
4
|
-
|
|
5
|
-
translateX,
|
|
6
|
-
translateY,
|
|
7
|
-
}: {
|
|
8
|
-
translateX: number;
|
|
9
|
-
translateY: number;
|
|
10
|
-
scale: number;
|
|
11
|
-
}) => {
|
|
12
|
-
'worklet';
|
|
1
|
+
const isValidDimension = (value: number): boolean => {
|
|
2
|
+
'worklet';
|
|
3
|
+
return Number.isFinite(value) && value > 0;
|
|
4
|
+
};
|
|
13
5
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
6
|
+
export const resolveGeometrySyncTranslationMode = (
|
|
7
|
+
previousIndex: number | null,
|
|
8
|
+
nextIndex: number,
|
|
9
|
+
scale: number,
|
|
10
|
+
): 'constrain' | 'none' | 'reset' => {
|
|
11
|
+
if (previousIndex !== null && previousIndex !== nextIndex) {
|
|
12
|
+
return 'reset';
|
|
13
|
+
}
|
|
20
14
|
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
return scale > 1 ? 'constrain' : 'none';
|
|
16
|
+
};
|
|
23
17
|
|
|
18
|
+
const clampTranslation = (value: number, max: number): number => {
|
|
19
|
+
'worklet';
|
|
20
|
+
const result = Math.max(-max, Math.min(max, value));
|
|
21
|
+
return result === 0 ? 0 : result;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const clampTranslationToBounds = ({
|
|
25
|
+
width,
|
|
26
|
+
height,
|
|
27
|
+
contentWidth,
|
|
28
|
+
contentHeight,
|
|
29
|
+
scale,
|
|
30
|
+
translateX,
|
|
31
|
+
translateY,
|
|
32
|
+
}: {
|
|
33
|
+
width: number;
|
|
34
|
+
height: number;
|
|
35
|
+
contentWidth?: number;
|
|
36
|
+
contentHeight?: number;
|
|
37
|
+
translateX: number;
|
|
38
|
+
translateY: number;
|
|
39
|
+
scale: number;
|
|
40
|
+
}) => {
|
|
41
|
+
'worklet';
|
|
42
|
+
|
|
43
|
+
if (scale <= 1) {
|
|
24
44
|
return {
|
|
25
|
-
translateX
|
|
26
|
-
translateY
|
|
45
|
+
translateX,
|
|
46
|
+
translateY,
|
|
27
47
|
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const baseWidth =
|
|
51
|
+
contentWidth !== undefined && isValidDimension(contentWidth) ? contentWidth : width;
|
|
52
|
+
const baseHeight =
|
|
53
|
+
contentHeight !== undefined && isValidDimension(contentHeight) ? contentHeight : height;
|
|
54
|
+
const maxTranslateX = Math.max(0, (baseWidth * scale - width) / 2);
|
|
55
|
+
const maxTranslateY = Math.max(0, (baseHeight * scale - height) / 2);
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
translateX: clampTranslation(translateX, maxTranslateX),
|
|
59
|
+
translateY: clampTranslation(translateY, maxTranslateY),
|
|
28
60
|
};
|
|
61
|
+
};
|
package/src/utils/tapZoom.ts
CHANGED
|
@@ -1,11 +1,51 @@
|
|
|
1
1
|
import type { SharedValue } from 'react-native-reanimated';
|
|
2
2
|
import { Easing, withTiming } from 'react-native-reanimated';
|
|
3
3
|
|
|
4
|
+
import { clampTranslationToBounds } from '.';
|
|
5
|
+
|
|
6
|
+
export const getTapZoomTarget = ({
|
|
7
|
+
x,
|
|
8
|
+
y,
|
|
9
|
+
width,
|
|
10
|
+
height,
|
|
11
|
+
contentWidth,
|
|
12
|
+
contentHeight,
|
|
13
|
+
maxZoomScale,
|
|
14
|
+
scale,
|
|
15
|
+
}: {
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
contentWidth?: number;
|
|
21
|
+
contentHeight?: number;
|
|
22
|
+
maxZoomScale: number;
|
|
23
|
+
scale: number;
|
|
24
|
+
}) => {
|
|
25
|
+
'worklet';
|
|
26
|
+
const nextScale = scale > 1 ? 1 : maxZoomScale;
|
|
27
|
+
if (nextScale <= 1) return { scale: nextScale, translateX: 0, translateY: 0 };
|
|
28
|
+
return {
|
|
29
|
+
scale: nextScale,
|
|
30
|
+
...clampTranslationToBounds({
|
|
31
|
+
contentHeight,
|
|
32
|
+
contentWidth,
|
|
33
|
+
height,
|
|
34
|
+
scale: nextScale,
|
|
35
|
+
translateX: -(x - width / 2) * (nextScale - 1),
|
|
36
|
+
translateY: -(y - height / 2) * (nextScale - 1),
|
|
37
|
+
width,
|
|
38
|
+
}),
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
|
|
4
42
|
export const applyTapZoomAtPoint = ({
|
|
5
43
|
x,
|
|
6
44
|
y,
|
|
7
45
|
width,
|
|
8
46
|
height,
|
|
47
|
+
contentWidth,
|
|
48
|
+
contentHeight,
|
|
9
49
|
maxZoomScale,
|
|
10
50
|
scale,
|
|
11
51
|
translateX,
|
|
@@ -15,6 +55,8 @@ export const applyTapZoomAtPoint = ({
|
|
|
15
55
|
y: number;
|
|
16
56
|
width: number;
|
|
17
57
|
height: number;
|
|
58
|
+
contentWidth?: number;
|
|
59
|
+
contentHeight?: number;
|
|
18
60
|
maxZoomScale: number;
|
|
19
61
|
scale: SharedValue<number>;
|
|
20
62
|
translateX: SharedValue<number>;
|
|
@@ -22,24 +64,22 @@ export const applyTapZoomAtPoint = ({
|
|
|
22
64
|
}) => {
|
|
23
65
|
'worklet';
|
|
24
66
|
|
|
25
|
-
const
|
|
67
|
+
const target = getTapZoomTarget({
|
|
68
|
+
contentHeight,
|
|
69
|
+
contentWidth,
|
|
70
|
+
height,
|
|
71
|
+
maxZoomScale,
|
|
72
|
+
scale: scale.get(),
|
|
73
|
+
width,
|
|
74
|
+
x,
|
|
75
|
+
y,
|
|
76
|
+
});
|
|
26
77
|
const timingConfig = {
|
|
27
78
|
duration: 300,
|
|
28
79
|
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
|
|
29
80
|
};
|
|
30
81
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// NOTE 확대로 밀려난 거리만큼 반대로 이동해서 탭 지점을 제자리에 유지
|
|
36
|
-
translateX.set(withTiming(-centerX * (nextScale - 1), timingConfig));
|
|
37
|
-
translateY.set(withTiming(-centerY * (nextScale - 1), timingConfig));
|
|
38
|
-
scale.set(withTiming(nextScale, timingConfig));
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
translateX.set(withTiming(0, timingConfig));
|
|
43
|
-
translateY.set(withTiming(0, timingConfig));
|
|
44
|
-
scale.set(withTiming(nextScale, timingConfig));
|
|
82
|
+
translateX.set(withTiming(target.translateX, timingConfig));
|
|
83
|
+
translateY.set(withTiming(target.translateY, timingConfig));
|
|
84
|
+
scale.set(withTiming(target.scale, timingConfig));
|
|
45
85
|
};
|