react-native-screen-transitions 1.0.2 → 1.1.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/dist/index.mjs CHANGED
@@ -5,21 +5,59 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // src/index.ts
8
- import { Pressable, View } from "react-native";
8
+ import { FlatList, Pressable, ScrollView, View as View2 } from "react-native";
9
9
 
10
- // src/configs/presets.ts
11
- var presets_exports = {};
12
- __export(presets_exports, {
13
- DraggableCard: () => DraggableCard,
14
- ElasticCard: () => ElasticCard,
15
- SlideFromBottom: () => SlideFromBottom,
16
- SlideFromTop: () => SlideFromTop,
17
- ZoomIn: () => ZoomIn
18
- });
10
+ // src/components/create-transition-aware-component.tsx
11
+ import { forwardRef, memo } from "react";
12
+ import { StyleSheet } from "react-native";
13
+ import Animated, {
14
+ useAnimatedStyle as useAnimatedStyle2
15
+ } from "react-native-reanimated";
16
+
17
+ // src/components/transition-gesture-handler-provider.tsx
18
+ import { useMemo as useMemo2 } from "react";
19
+ import { GestureDetector } from "react-native-gesture-handler";
20
+ import { useSharedValue as useSharedValue3 } from "react-native-reanimated";
21
+
22
+ // src/contexts/gesture.ts
23
+ import { createContext, useContext } from "react";
24
+ import { Gesture } from "react-native-gesture-handler";
25
+ import { useSharedValue } from "react-native-reanimated";
26
+ var GestureContext = createContext(
27
+ void 0
28
+ );
29
+ var useGestureContext = () => {
30
+ const context = useContext(GestureContext);
31
+ const scrollProgressFallback = useSharedValue({
32
+ x: 0,
33
+ y: 0,
34
+ contentHeight: 0,
35
+ contentWidth: 0,
36
+ layoutHeight: 0,
37
+ layoutWidth: 0
38
+ });
39
+ if (!context) {
40
+ return {
41
+ panGesture: Gesture.Pan(),
42
+ nativeGesture: Gesture.Native(),
43
+ scrollProgress: scrollProgressFallback,
44
+ isPlaceholder: true
45
+ };
46
+ }
47
+ return context;
48
+ };
49
+
50
+ // src/hooks/use-build-gestures.tsx
51
+ import { useNavigation } from "@react-navigation/native";
52
+ import { useCallback, useMemo } from "react";
53
+ import { useWindowDimensions } from "react-native";
54
+ import {
55
+ Gesture as Gesture2
56
+ } from "react-native-gesture-handler";
19
57
  import {
20
- Extrapolation,
21
58
  interpolate,
22
- interpolateColor
59
+ runOnJS as runOnJS2,
60
+ useSharedValue as useSharedValue2
23
61
  } from "react-native-reanimated";
24
62
 
25
63
  // src/configs/specs.ts
@@ -35,244 +73,6 @@ var DefaultSpec = {
35
73
  restSpeedThreshold: 0.01
36
74
  };
37
75
 
38
- // src/configs/presets.ts
39
- var SlideFromTop = (config = {}) => {
40
- return {
41
- gestureEnabled: true,
42
- gestureDirection: "vertical-inverted",
43
- screenStyleInterpolator: ({
44
- current,
45
- next,
46
- layouts: {
47
- screen: { height }
48
- }
49
- }) => {
50
- "worklet";
51
- const progress = current.progress.value + (next?.progress.value ?? 0);
52
- const y = interpolate(progress, [0, 1, 2], [-height, 0, height]);
53
- return {
54
- contentStyle: {
55
- transform: [{ translateY: y }]
56
- }
57
- };
58
- },
59
- transitionSpec: {
60
- open: DefaultSpec,
61
- close: DefaultSpec
62
- },
63
- ...config
64
- };
65
- };
66
- var ZoomIn = (config = {}) => {
67
- return {
68
- gestureEnabled: false,
69
- screenStyleInterpolator: ({ current, next }) => {
70
- "worklet";
71
- const progress = current.progress.value + (next?.progress.value ?? 0);
72
- const scale = interpolate(
73
- progress,
74
- [0, 1, 2],
75
- [0.5, 1, 0.5],
76
- Extrapolation.CLAMP
77
- );
78
- const opacity = interpolate(
79
- progress,
80
- [0, 1, 2],
81
- [0, 1, 0],
82
- Extrapolation.CLAMP
83
- );
84
- return {
85
- contentStyle: {
86
- transform: [{ scale }],
87
- opacity
88
- }
89
- };
90
- },
91
- transitionSpec: {
92
- open: DefaultSpec,
93
- close: DefaultSpec
94
- },
95
- ...config
96
- };
97
- };
98
- var SlideFromBottom = (config = {}) => {
99
- return {
100
- gestureEnabled: true,
101
- gestureDirection: "vertical",
102
- screenStyleInterpolator: ({
103
- current,
104
- next,
105
- layouts: {
106
- screen: { height }
107
- }
108
- }) => {
109
- "worklet";
110
- const progress = current.progress.value + (next?.progress.value ?? 0);
111
- const y = interpolate(progress, [0, 1, 2], [height, 0, -height]);
112
- return {
113
- contentStyle: {
114
- transform: [{ translateY: y }]
115
- }
116
- };
117
- },
118
- transitionSpec: {
119
- open: DefaultSpec,
120
- close: DefaultSpec
121
- },
122
- ...config
123
- };
124
- };
125
- var DraggableCard = (config = {}) => {
126
- return {
127
- gestureEnabled: true,
128
- gestureDirection: ["horizontal", "vertical"],
129
- screenStyleInterpolator: ({ current, next, layouts: { screen } }) => {
130
- "worklet";
131
- const progress = current.progress.value + (next?.progress.value ?? 0);
132
- const scale = interpolate(progress, [0, 1, 2], [0, 1, 0.75]);
133
- const translateY = interpolate(
134
- current.gesture.normalizedY.value,
135
- [-1, 1],
136
- [-screen.height * 0.5, screen.height * 0.5],
137
- "clamp"
138
- );
139
- const translateX = interpolate(
140
- current.gesture.normalizedX.value,
141
- [-1, 1],
142
- [-screen.width * 0.5, screen.width * 0.5],
143
- "clamp"
144
- );
145
- return {
146
- contentStyle: {
147
- transform: [{ scale }, { translateY }, { translateX }]
148
- }
149
- };
150
- },
151
- transitionSpec: {
152
- open: DefaultSpec,
153
- close: DefaultSpec
154
- },
155
- ...config
156
- };
157
- };
158
- var ElasticCard = (config = { elasticFactor: 0.5 }) => {
159
- return {
160
- gestureEnabled: true,
161
- gestureDirection: "bidirectional",
162
- screenStyleInterpolator: ({ current, next, layouts: { screen } }) => {
163
- "worklet";
164
- const progress = current.progress.value + (next?.progress.value ?? 0);
165
- const scale = interpolate(progress, [0, 1, 2], [0, 1, 0.8]);
166
- const maxElasticityX = screen.width * (config.elasticFactor ?? 0.5);
167
- const maxElasticityY = screen.height * (config.elasticFactor ?? 0.5);
168
- const translateX = interpolate(
169
- current.gesture.normalizedX.value,
170
- [-1, 0, 1],
171
- [-maxElasticityX, 0, maxElasticityX],
172
- "clamp"
173
- );
174
- const translateY = interpolate(
175
- current.gesture.normalizedY.value,
176
- [-1, 0, 1],
177
- [-maxElasticityY, 0, maxElasticityY],
178
- "clamp"
179
- );
180
- const overlayColor = interpolateColor(
181
- next?.progress.value || 0,
182
- [0, 1],
183
- ["rgba(0,0,0,0)", "rgba(0,0,0,0.5)"]
184
- );
185
- return {
186
- contentStyle: {
187
- transform: [{ scale }, { translateX }, { translateY }]
188
- },
189
- overlayStyle: {
190
- backgroundColor: overlayColor
191
- }
192
- };
193
- },
194
- ...config
195
- };
196
- };
197
-
198
- // src/hooks/use-screen-animation.tsx
199
- import { useNavigation } from "@react-navigation/native";
200
- import { useMemo } from "react";
201
- import { useWindowDimensions } from "react-native";
202
- import { useSharedValue } from "react-native-reanimated";
203
- import { useSafeAreaInsets } from "react-native-safe-area-context";
204
-
205
- // src/store/utils/use-shallow.tsx
206
- import React from "react";
207
-
208
- // src/store/utils/shallow.ts
209
- var isIterable = (obj) => Symbol.iterator in obj;
210
- var hasIterableEntries = (value) => (
211
- // HACK: avoid checking entries type
212
- "entries" in value
213
- );
214
- var compareEntries = (valueA, valueB) => {
215
- const mapA = valueA instanceof Map ? valueA : new Map(valueA.entries());
216
- const mapB = valueB instanceof Map ? valueB : new Map(valueB.entries());
217
- if (mapA.size !== mapB.size) {
218
- return false;
219
- }
220
- for (const [key, value] of mapA) {
221
- if (!Object.is(value, mapB.get(key))) {
222
- return false;
223
- }
224
- }
225
- return true;
226
- };
227
- var compareIterables = (valueA, valueB) => {
228
- const iteratorA = valueA[Symbol.iterator]();
229
- const iteratorB = valueB[Symbol.iterator]();
230
- let nextA = iteratorA.next();
231
- let nextB = iteratorB.next();
232
- while (!nextA.done && !nextB.done) {
233
- if (!Object.is(nextA.value, nextB.value)) {
234
- return false;
235
- }
236
- nextA = iteratorA.next();
237
- nextB = iteratorB.next();
238
- }
239
- return !!nextA.done && !!nextB.done;
240
- };
241
- function shallow(valueA, valueB) {
242
- if (Object.is(valueA, valueB)) {
243
- return true;
244
- }
245
- if (typeof valueA !== "object" || valueA === null || typeof valueB !== "object" || valueB === null) {
246
- return false;
247
- }
248
- if (Object.getPrototypeOf(valueA) !== Object.getPrototypeOf(valueB)) {
249
- return false;
250
- }
251
- if (isIterable(valueA) && isIterable(valueB)) {
252
- if (hasIterableEntries(valueA) && hasIterableEntries(valueB)) {
253
- return compareEntries(valueA, valueB);
254
- }
255
- return compareIterables(valueA, valueB);
256
- }
257
- return compareEntries(
258
- { entries: () => Object.entries(valueA) },
259
- { entries: () => Object.entries(valueB) }
260
- );
261
- }
262
-
263
- // src/store/utils/use-shallow.tsx
264
- function useShallow(selector) {
265
- const prev = React.useRef(void 0);
266
- return (state) => {
267
- const next = selector(state);
268
- if (shallow(prev.current, next)) {
269
- return prev.current;
270
- }
271
- prev.current = next;
272
- return next;
273
- };
274
- }
275
-
276
76
  // src/animation-engine.ts
277
77
  import {
278
78
  cancelAnimation,
@@ -323,54 +123,93 @@ function createVanillaStore(initialState) {
323
123
  return useStore;
324
124
  }
325
125
 
326
- // src/store/index.ts
327
- var useRouteStore = createVanillaStore({
328
- routes: {},
329
- routeKeys: []
330
- });
331
- var RouteStore = {
332
- use: useRouteStore,
333
- updateRoute: (key, value) => {
334
- if (!key) return;
335
- useRouteStore.setState(({ routeKeys, routes }) => {
336
- const currentRoute = routes[key];
337
- if (currentRoute) {
338
- routes[key] = {
339
- ...currentRoute,
340
- ...value
341
- };
342
- } else {
343
- const { name = "", status = 0, closing = false, ...rest } = value;
344
- const newIndex = routeKeys.length;
345
- routes[key] = {
346
- id: key,
347
- index: newIndex,
348
- name,
349
- status,
350
- closing,
351
- ...rest
352
- };
353
- routeKeys.push(key);
354
- }
355
- });
356
- },
357
- removeRoute: (key) => {
358
- if (!key) return;
359
- useRouteStore.setState(({ routes, routeKeys }) => {
360
- delete routes[key];
361
- const indexToRemove = routeKeys.indexOf(key);
362
- if (indexToRemove > -1) {
363
- routeKeys.splice(indexToRemove, 1);
364
- }
126
+ // src/store/utils/handle-screen-dismiss.tsx
127
+ import { StackActions } from "@react-navigation/native";
128
+ var handleScreenDismiss = (screenBeingDismissed, navigation) => {
129
+ const { screens } = ScreenStore.use.getState();
130
+ const dismissedScreen = screens[screenBeingDismissed];
131
+ if (!dismissedScreen) {
132
+ navigation.goBack();
133
+ return;
134
+ }
135
+ const childScreens = Object.values(screens).filter(
136
+ (screen) => screen.parentNavigatorKey === dismissedScreen.navigatorKey
137
+ );
138
+ if (childScreens.length > 0) {
139
+ ScreenStore.updateScreen(dismissedScreen.id, {
140
+ closing: true
365
141
  });
366
- },
367
- getPreviousRoute: (key) => {
368
- if (!key) return null;
369
- const index = useRouteStore.getState().routeKeys.indexOf(key);
370
- return index > -1 ? useRouteStore.getState().routes[useRouteStore.getState().routeKeys[index - 1]] : null;
142
+ navigation.dispatch(StackActions.pop(childScreens.length));
143
+ } else {
144
+ navigation.goBack();
371
145
  }
372
146
  };
373
147
 
148
+ // src/store/utils/remove-screen.tsx
149
+ var removeScreen = (key) => {
150
+ if (!key) return;
151
+ ScreenStore.use.setState(({ screens, screenKeys }) => {
152
+ delete screens[key];
153
+ const indexToRemove = screenKeys.indexOf(key);
154
+ if (indexToRemove > -1) {
155
+ screenKeys.splice(indexToRemove, 1);
156
+ }
157
+ });
158
+ };
159
+
160
+ // src/store/utils/should-skip-prevent-default.tsx
161
+ var shouldSkipPreventDefault = (key, navigatorState) => {
162
+ if (!key) return false;
163
+ const { screens } = ScreenStore.use.getState();
164
+ const currentScreen = screens[key];
165
+ const isLastScreenInStack = navigatorState.routes.length === 1 && navigatorState.routes[0].key === key;
166
+ const isParentNavigatorExiting = Boolean(
167
+ currentScreen?.parentNavigatorKey && Object.values(screens).some(
168
+ (screen) => screen.navigatorKey === currentScreen.parentNavigatorKey && screen.closing
169
+ )
170
+ );
171
+ return isLastScreenInStack || isParentNavigatorExiting;
172
+ };
173
+
174
+ // src/store/utils/update-screen.tsx
175
+ var updateScreen = (key, value) => {
176
+ if (!key) return;
177
+ ScreenStore.use.setState(({ screenKeys, screens }) => {
178
+ const currentScreen = screens[key];
179
+ if (currentScreen) {
180
+ screens[key] = {
181
+ ...currentScreen,
182
+ ...value
183
+ };
184
+ } else {
185
+ const { name = "", status = 0, closing = false, ...rest } = value;
186
+ const newIndex = screenKeys.length;
187
+ screens[key] = {
188
+ id: key,
189
+ index: newIndex,
190
+ name,
191
+ status,
192
+ closing,
193
+ ...rest
194
+ };
195
+ screenKeys.push(key);
196
+ }
197
+ });
198
+ };
199
+
200
+ // src/store/index.ts
201
+ var useScreenStore = createVanillaStore({
202
+ screens: {},
203
+ screenKeys: []
204
+ });
205
+ var ScreenStore = {
206
+ use: useScreenStore,
207
+ updateScreen,
208
+ removeScreen,
209
+ handleScreenDismiss,
210
+ shouldSkipPreventDefault
211
+ };
212
+
374
213
  // src/utils/animate.ts
375
214
  import {
376
215
  withSpring,
@@ -392,14 +231,15 @@ var animationValues = {
392
231
  gestureY: {},
393
232
  normalizedGestureX: {},
394
233
  normalizedGestureY: {},
395
- gestureDragging: {}
234
+ gestureDragging: {},
235
+ isDismissing: {}
396
236
  };
397
- var triggerAnimation = (route) => {
237
+ var triggerAnimation = (screen) => {
398
238
  "worklet";
399
- const { id, closing, status, transitionSpec, onAnimationFinish } = route;
239
+ const { id, closing, status, transitionSpec, onAnimationFinish } = screen;
400
240
  const progressValue = animationValues.screenProgress[id];
401
241
  if (!progressValue && __DEV__) {
402
- console.warn(`Animation values not found for route: ${id}`);
242
+ console.warn(`Animation values not found for screen: ${id}`);
403
243
  return;
404
244
  }
405
245
  const animationConfig = closing ? transitionSpec?.close : transitionSpec?.open;
@@ -411,14 +251,16 @@ var triggerAnimation = (route) => {
411
251
  }
412
252
  });
413
253
  };
414
- RouteStore.use.subscribeWithSelector(
415
- (state) => state.routes,
416
- (currRoutes, prevRoutes) => {
417
- const currKeys = Object.keys(currRoutes);
418
- const prevKeys = Object.keys(prevRoutes);
254
+ ScreenStore.use.subscribeWithSelector(
255
+ (state) => state.screens,
256
+ (currScreens, prevScreens) => {
257
+ const currKeys = Object.keys(currScreens);
258
+ const prevKeys = Object.keys(prevScreens);
419
259
  const incomingKeys = currKeys.filter((k) => !prevKeys.includes(k));
420
260
  const removedKeys = prevKeys.filter((k) => !currKeys.includes(k));
421
- const changedKeys = currKeys.filter((k) => currRoutes[k] !== prevRoutes[k]);
261
+ const changedKeys = currKeys.filter(
262
+ (k) => currScreens[k] !== prevScreens[k]
263
+ );
422
264
  const animatableValues = Object.values(animationValues);
423
265
  for (const incomingKey of incomingKeys) {
424
266
  for (const value of animatableValues) {
@@ -432,31 +274,92 @@ RouteStore.use.subscribeWithSelector(
432
274
  }
433
275
  }
434
276
  for (const changedKey of changedKeys) {
435
- const currentRoute = currRoutes[changedKey];
436
- if (currentRoute) {
437
- triggerAnimation(currentRoute);
277
+ const currentScreen = currScreens[changedKey];
278
+ if (currentScreen) {
279
+ triggerAnimation(currentScreen);
438
280
  }
439
281
  }
440
282
  }
441
283
  );
442
284
 
443
- // src/utils/gesture/build-gesture-detector.ts
444
- import { Gesture } from "react-native-gesture-handler";
445
- import {
446
- interpolate as interpolate2,
447
- runOnJS as runOnJS2
448
- } from "react-native-reanimated";
449
-
450
- // src/utils/gesture/create-gesture-activation-criteria.ts
451
- var createGestureActivationCriteria = ({
452
- gestureDirection,
453
- gestureResponseDistance
285
+ // src/utils/create-config.ts
286
+ var createConfig = ({
287
+ navigation: reactNavigation,
288
+ route,
289
+ ...config
454
290
  }) => {
455
- const directions = Array.isArray(gestureDirection) ? gestureDirection : [gestureDirection];
456
- if (directions.includes("bidirectional")) {
457
- return {
458
- activeOffsetX: [
459
- -gestureResponseDistance,
291
+ return {
292
+ focus: (e) => {
293
+ const parentNavigatorKey = reactNavigation.getParent()?.getState?.()?.key;
294
+ const navigatorKey = reactNavigation.getState().key;
295
+ ScreenStore.updateScreen(e.target, {
296
+ id: e.target,
297
+ name: route.name,
298
+ status: 1,
299
+ closing: false,
300
+ navigatorKey,
301
+ parentNavigatorKey,
302
+ ...config
303
+ });
304
+ },
305
+ beforeRemove: (e) => {
306
+ const shouldSkipPreventDefault2 = ScreenStore.shouldSkipPreventDefault(
307
+ e.target,
308
+ reactNavigation.getState()
309
+ );
310
+ if (shouldSkipPreventDefault2) {
311
+ ScreenStore.removeScreen(e.target);
312
+ return;
313
+ }
314
+ e.preventDefault();
315
+ const handleFinish = (finished) => {
316
+ if (!finished) return;
317
+ if (reactNavigation.canGoBack()) {
318
+ reactNavigation.dispatch(e.data?.action);
319
+ ScreenStore.removeScreen(e.target);
320
+ }
321
+ };
322
+ ScreenStore.updateScreen(e.target, {
323
+ status: 0,
324
+ closing: true,
325
+ onAnimationFinish: handleFinish
326
+ });
327
+ }
328
+ };
329
+ };
330
+ var createScreenConfig = (config) => {
331
+ return {
332
+ listeners: (l) => createConfig({ ...l, ...config || {} })
333
+ };
334
+ };
335
+
336
+ // src/utils/default-screen-options.ts
337
+ var defaultScreenOptions = () => ({
338
+ presentation: "containedTransparentModal",
339
+ headerShown: false,
340
+ animation: "none"
341
+ });
342
+
343
+ // src/utils/noop-interpolator.ts
344
+ var noopinterpolator = () => {
345
+ "worklet";
346
+ return {
347
+ contentStyle: {},
348
+ overlayStyle: {}
349
+ };
350
+ };
351
+
352
+ // src/utils/gesture/apply-gesture-activation-criteria.ts
353
+ var applyGestureActivationCriteria = ({
354
+ gestureDirection,
355
+ gestureResponseDistance,
356
+ panGesture
357
+ }) => {
358
+ const directions = Array.isArray(gestureDirection) ? gestureDirection : [gestureDirection];
359
+ if (directions.includes("bidirectional")) {
360
+ return {
361
+ activeOffsetX: [
362
+ -gestureResponseDistance,
460
363
  gestureResponseDistance
461
364
  ],
462
365
  activeOffsetY: [
@@ -507,7 +410,20 @@ var createGestureActivationCriteria = ({
507
410
  } else {
508
411
  result.failOffsetY = [-toleranceY, toleranceY];
509
412
  }
510
- return result;
413
+ if (result?.activeOffsetX) {
414
+ panGesture.activeOffsetX(result.activeOffsetX);
415
+ }
416
+ if (result?.activeOffsetY) {
417
+ panGesture.activeOffsetY(result.activeOffsetY);
418
+ }
419
+ if (result?.failOffsetX) {
420
+ panGesture.failOffsetX(result.failOffsetX);
421
+ }
422
+ if (result?.failOffsetY) {
423
+ panGesture.failOffsetY(result.failOffsetY);
424
+ }
425
+ panGesture.enableTrackpadTwoFingerGesture(true);
426
+ return panGesture;
511
427
  };
512
428
 
513
429
  // src/utils/gesture/map-gesture-to-progress.ts
@@ -517,310 +433,393 @@ var mapGestureToProgress = (translation, dimension) => {
517
433
  return Math.max(0, Math.min(1, rawProgress));
518
434
  };
519
435
 
520
- // src/utils/gesture/build-gesture-detector.ts
436
+ // src/hooks/use-key.tsx
437
+ import { useRoute } from "@react-navigation/native";
438
+ var useKey = () => useRoute().key;
439
+
440
+ // src/hooks/use-build-gestures.tsx
521
441
  var GESTURE_VELOCITY_IMPACT = 0.3;
522
442
  var DEFAULT_GESTURE_RESPONSE_DISTANCE = 50;
523
- var buildGestureDetector = ({
524
- key,
525
- progress,
526
- config,
527
- width,
528
- height,
529
- goBack
443
+ var DEFAULT_GESTURE_DIRECTION = "horizontal";
444
+ var DEFAULT_GESTURE_ENABLED = false;
445
+ var useBuildGestures = ({
446
+ scrollProgress
530
447
  }) => {
531
- const _translateX = animationValues.gestureX[key];
532
- const _translateY = animationValues.gestureY[key];
533
- const _normalizedGestureX = animationValues.normalizedGestureX[key];
534
- const _normalizedGestureY = animationValues.normalizedGestureY[key];
535
- const _isDragging = animationValues.gestureDragging[key];
448
+ const key = useKey();
449
+ const dimensions = useWindowDimensions();
450
+ const navigation = useNavigation();
451
+ const currentScreen = ScreenStore.use(
452
+ useCallback((state) => state.screens[key], [key])
453
+ );
454
+ const handleDismiss = useCallback(
455
+ (screenBeingDismissed) => {
456
+ ScreenStore.handleScreenDismiss(screenBeingDismissed, navigation);
457
+ },
458
+ [navigation]
459
+ );
460
+ const initialTouch = useSharedValue2({
461
+ x: 0,
462
+ y: 0
463
+ });
464
+ const translateX = animationValues.gestureX[key];
465
+ const translateY = animationValues.gestureY[key];
466
+ const normalizedGestureX = animationValues.normalizedGestureX[key];
467
+ const normalizedGestureY = animationValues.normalizedGestureY[key];
468
+ const isDragging = animationValues.gestureDragging[key];
469
+ const isDismissing = animationValues.isDismissing[key];
470
+ const progress = animationValues.screenProgress[key] || 0;
536
471
  const {
537
- gestureDirection = "horizontal",
538
- gestureEnabled = false,
539
- transitionSpec,
472
+ gestureDirection = DEFAULT_GESTURE_DIRECTION,
473
+ gestureEnabled = DEFAULT_GESTURE_ENABLED,
474
+ transitionSpec = {
475
+ open: DefaultSpec,
476
+ close: DefaultSpec
477
+ },
540
478
  gestureVelocityImpact = GESTURE_VELOCITY_IMPACT,
541
479
  gestureResponseDistance = DEFAULT_GESTURE_RESPONSE_DISTANCE
542
- } = config;
480
+ } = currentScreen ?? {};
543
481
  const directions = Array.isArray(gestureDirection) ? gestureDirection : [gestureDirection];
544
- const panGesture = Gesture.Pan().enabled(gestureEnabled).onStart(() => {
545
- "worklet";
546
- _isDragging.value = 1;
547
- }).onUpdate((event) => {
548
- "worklet";
549
- let gestureProgress = 0;
550
- _translateX.value = event.translationX;
551
- _translateY.value = event.translationY;
552
- _normalizedGestureX.value = interpolate2(
553
- event.translationX,
554
- [-width, width],
555
- [-1, 1],
556
- "clamp"
557
- );
558
- _normalizedGestureY.value = interpolate2(
559
- event.translationY,
560
- [-height, height],
561
- [-1, 1],
562
- "clamp"
563
- );
564
- if (directions.includes("bidirectional")) {
565
- const distance = Math.sqrt(
566
- event.translationX ** 2 + event.translationY ** 2
567
- );
568
- gestureProgress = mapGestureToProgress(distance, width);
569
- } else {
570
- let maxProgress = 0;
571
- const allowedDown = directions.includes("vertical");
572
- const allowedUp = directions.includes("vertical-inverted");
573
- const allowedRight = directions.includes("horizontal");
574
- const allowedLeft = directions.includes("horizontal-inverted");
575
- if (allowedRight || allowedLeft) {
576
- const absX = Math.abs(event.translationX);
577
- const currentProgress = mapGestureToProgress(absX, width);
578
- maxProgress = Math.max(maxProgress, currentProgress);
482
+ const nativeGesture = Gesture2.Native();
483
+ const onTouchesDown = useCallback(
484
+ (e) => {
485
+ "worklet";
486
+ const firstTouch = e.changedTouches[0];
487
+ initialTouch.value = { x: firstTouch.x, y: firstTouch.y };
488
+ },
489
+ [initialTouch]
490
+ );
491
+ const onTouchesMove = useCallback(
492
+ (e, manager) => {
493
+ "worklet";
494
+ const touch = e.changedTouches[0];
495
+ const deltaX = touch.x - initialTouch.value.x;
496
+ const deltaY = touch.y - initialTouch.value.y;
497
+ const isVerticalSwipe = Math.abs(deltaY) > Math.abs(deltaX);
498
+ const isHorizontalSwipe = Math.abs(deltaX) > Math.abs(deltaY);
499
+ const isSwipingDown = isVerticalSwipe && deltaY > 0;
500
+ const isSwipingUp = isVerticalSwipe && deltaY < 0;
501
+ const isSwipingRight = isHorizontalSwipe && deltaX > 0;
502
+ const isSwipingLeft = isHorizontalSwipe && deltaX < 0;
503
+ const minMovement = 5;
504
+ const hasEnoughMovement = Math.abs(deltaX) > minMovement || Math.abs(deltaY) > minMovement;
505
+ if (!hasEnoughMovement) return;
506
+ if (isDragging.value) {
507
+ manager.activate();
508
+ return;
509
+ }
510
+ let shouldActivate = false;
511
+ for (const direction of directions) {
512
+ switch (direction) {
513
+ case "vertical":
514
+ if (isSwipingDown) {
515
+ shouldActivate = scrollProgress.value.y <= 0;
516
+ }
517
+ break;
518
+ case "vertical-inverted":
519
+ if (isSwipingUp) {
520
+ const maxScrollableY = scrollProgress.value.contentHeight - scrollProgress.value.layoutHeight;
521
+ shouldActivate = scrollProgress.value.y >= maxScrollableY;
522
+ }
523
+ break;
524
+ case "horizontal":
525
+ if (isSwipingRight) {
526
+ shouldActivate = scrollProgress.value.x <= 0;
527
+ }
528
+ break;
529
+ case "horizontal-inverted":
530
+ if (isSwipingLeft) {
531
+ const maxProgress = scrollProgress.value.contentWidth - scrollProgress.value.layoutWidth;
532
+ shouldActivate = scrollProgress.value.x >= maxProgress;
533
+ }
534
+ break;
535
+ case "bidirectional":
536
+ if (isSwipingDown) {
537
+ shouldActivate = scrollProgress.value.y <= 0;
538
+ } else if (isSwipingUp) {
539
+ shouldActivate = scrollProgress.value.y <= 0;
540
+ } else if (isSwipingRight || isSwipingLeft) {
541
+ shouldActivate = true;
542
+ }
543
+ break;
544
+ }
545
+ if (shouldActivate) break;
579
546
  }
580
- if (allowedUp || allowedDown) {
581
- const absY = Math.abs(event.translationY);
582
- const currentProgress = mapGestureToProgress(absY, height);
583
- maxProgress = Math.max(maxProgress, currentProgress);
547
+ if ((shouldActivate || isDragging.value) && !isDismissing.value) {
548
+ manager.activate();
549
+ } else {
550
+ manager.fail();
584
551
  }
585
- gestureProgress = maxProgress;
586
- }
587
- progress.value = 1 - gestureProgress;
588
- }).onEnd((event) => {
552
+ },
553
+ [initialTouch, directions, scrollProgress, isDragging, isDismissing]
554
+ );
555
+ const onStart = useCallback(() => {
589
556
  "worklet";
590
- const { translationX, translationY, velocityX, velocityY } = event;
591
- let shouldDismiss = false;
592
- const dismissThreshold = 0.5;
593
- if (directions.includes("bidirectional")) {
594
- const finalX = Math.abs(
595
- translationX + velocityX * gestureVelocityImpact
557
+ isDragging.value = 1;
558
+ isDismissing.value = 0;
559
+ }, [isDragging, isDismissing]);
560
+ const onUpdate = useCallback(
561
+ (event) => {
562
+ "worklet";
563
+ let gestureProgress = 0;
564
+ translateX.value = event.translationX;
565
+ translateY.value = event.translationY;
566
+ normalizedGestureX.value = interpolate(
567
+ event.translationX,
568
+ [-dimensions.width, dimensions.width],
569
+ [-1, 1],
570
+ "clamp"
596
571
  );
597
- const finalY = Math.abs(
598
- translationY + velocityY * gestureVelocityImpact
572
+ normalizedGestureY.value = interpolate(
573
+ event.translationY,
574
+ [-dimensions.height, dimensions.height],
575
+ [-1, 1],
576
+ "clamp"
599
577
  );
600
- const finalDistance = Math.sqrt(finalX ** 2 + finalY ** 2);
601
- shouldDismiss = finalDistance > width * dismissThreshold;
602
- } else {
603
- const allowedDown = directions.includes("vertical");
604
- const allowedUp = directions.includes("vertical-inverted");
605
- const allowedRight = directions.includes("horizontal");
606
- const allowedLeft = directions.includes("horizontal-inverted");
607
- if (allowedRight && translationX + velocityX * gestureVelocityImpact > width * dismissThreshold) {
608
- shouldDismiss = true;
609
- } else if (allowedLeft && -translationX - velocityX * gestureVelocityImpact > width * dismissThreshold) {
610
- shouldDismiss = true;
611
- } else if (allowedDown && translationY + velocityY * gestureVelocityImpact > height * dismissThreshold) {
612
- shouldDismiss = true;
613
- } else if (allowedUp && -translationY - velocityY * gestureVelocityImpact > height * dismissThreshold) {
614
- shouldDismiss = true;
578
+ if (directions.includes("bidirectional")) {
579
+ const distance = Math.sqrt(
580
+ event.translationX ** 2 + event.translationY ** 2
581
+ );
582
+ gestureProgress = mapGestureToProgress(distance, dimensions.width);
583
+ } else {
584
+ let maxProgress = 0;
585
+ const allowedDown = directions.includes("vertical");
586
+ const allowedUp = directions.includes("vertical-inverted");
587
+ const allowedRight = directions.includes("horizontal");
588
+ const allowedLeft = directions.includes("horizontal-inverted");
589
+ if (allowedRight && event.translationX > 0) {
590
+ const currentProgress = mapGestureToProgress(
591
+ event.translationX,
592
+ dimensions.width
593
+ );
594
+ maxProgress = Math.max(maxProgress, currentProgress);
595
+ }
596
+ if (allowedLeft && event.translationX < 0) {
597
+ const currentProgress = mapGestureToProgress(
598
+ -event.translationX,
599
+ dimensions.width
600
+ );
601
+ maxProgress = Math.max(maxProgress, currentProgress);
602
+ }
603
+ if (allowedDown && event.translationY > 0) {
604
+ const currentProgress = mapGestureToProgress(
605
+ event.translationY,
606
+ dimensions.height
607
+ );
608
+ maxProgress = Math.max(maxProgress, currentProgress);
609
+ }
610
+ if (allowedUp && event.translationY < 0) {
611
+ const currentProgress = mapGestureToProgress(
612
+ -event.translationY,
613
+ dimensions.height
614
+ );
615
+ maxProgress = Math.max(maxProgress, currentProgress);
616
+ }
617
+ gestureProgress = maxProgress;
615
618
  }
616
- }
617
- const finalProgress = shouldDismiss ? 0 : 1;
618
- const spec = shouldDismiss ? transitionSpec?.close : transitionSpec?.open;
619
- const onFinish = shouldDismiss ? (isFinished) => {
619
+ progress.value = 1 - gestureProgress;
620
+ },
621
+ [
622
+ dimensions,
623
+ directions,
624
+ translateX,
625
+ translateY,
626
+ normalizedGestureX,
627
+ normalizedGestureY,
628
+ progress
629
+ ]
630
+ );
631
+ const onEnd = useCallback(
632
+ (event) => {
620
633
  "worklet";
621
- if (isFinished) runOnJS2(goBack)();
622
- } : void 0;
623
- progress.value = animate(finalProgress, spec, onFinish);
624
- _translateX.value = animate(0, spec);
625
- _translateY.value = animate(0, spec);
626
- _normalizedGestureX.value = animate(0, spec);
627
- _normalizedGestureY.value = animate(0, spec);
628
- });
629
- const criteria = createGestureActivationCriteria({
634
+ const { translationX, translationY, velocityX, velocityY } = event;
635
+ const dismissThreshold = 0.5;
636
+ if (directions.includes("bidirectional")) {
637
+ const finalX = Math.abs(
638
+ translationX + velocityX * gestureVelocityImpact
639
+ );
640
+ const finalY = Math.abs(
641
+ translationY + velocityY * gestureVelocityImpact
642
+ );
643
+ const finalDistance = Math.sqrt(finalX ** 2 + finalY ** 2);
644
+ isDismissing.value = Number(
645
+ finalDistance > dimensions.width * dismissThreshold
646
+ );
647
+ } else {
648
+ const allowedDown = directions.includes("vertical");
649
+ const allowedUp = directions.includes("vertical-inverted");
650
+ const allowedRight = directions.includes("horizontal");
651
+ const allowedLeft = directions.includes("horizontal-inverted");
652
+ if (allowedRight && translationX + velocityX * gestureVelocityImpact > dimensions.width * dismissThreshold) {
653
+ isDismissing.value = 1;
654
+ } else if (allowedLeft && -translationX - velocityX * gestureVelocityImpact > dimensions.width * dismissThreshold) {
655
+ isDismissing.value = 1;
656
+ } else if (allowedDown && translationY + velocityY * gestureVelocityImpact > dimensions.height * dismissThreshold) {
657
+ isDismissing.value = 1;
658
+ } else if (allowedUp && -translationY - velocityY * gestureVelocityImpact > dimensions.height * dismissThreshold) {
659
+ isDismissing.value = 1;
660
+ }
661
+ }
662
+ const finalProgress = isDismissing.value ? 0 : 1;
663
+ const spec = isDismissing.value ? transitionSpec?.close : transitionSpec?.open;
664
+ const onFinish = isDismissing.value ? (isFinished) => {
665
+ "worklet";
666
+ if (isFinished) runOnJS2(handleDismiss)(currentScreen?.id);
667
+ } : void 0;
668
+ progress.value = animate(finalProgress, spec, onFinish);
669
+ translateX.value = animate(0, spec);
670
+ translateY.value = animate(0, spec);
671
+ normalizedGestureX.value = animate(0, spec);
672
+ normalizedGestureY.value = animate(0, spec);
673
+ isDragging.value = 0;
674
+ },
675
+ [
676
+ dimensions,
677
+ directions,
678
+ translateX,
679
+ translateY,
680
+ normalizedGestureX,
681
+ normalizedGestureY,
682
+ progress,
683
+ handleDismiss,
684
+ currentScreen?.id,
685
+ transitionSpec?.close,
686
+ transitionSpec?.open,
687
+ gestureVelocityImpact,
688
+ isDragging,
689
+ isDismissing
690
+ ]
691
+ );
692
+ const panGesture = useMemo(
693
+ () => Gesture2.Pan().enabled(gestureEnabled).manualActivation(true).onTouchesDown(onTouchesDown).onTouchesMove(onTouchesMove).onStart(onStart).onUpdate(onUpdate).onEnd(onEnd).blocksExternalGesture(nativeGesture),
694
+ [
695
+ gestureEnabled,
696
+ nativeGesture,
697
+ onTouchesDown,
698
+ onTouchesMove,
699
+ onStart,
700
+ onUpdate,
701
+ onEnd
702
+ ]
703
+ );
704
+ applyGestureActivationCriteria({
630
705
  gestureDirection,
631
- gestureResponseDistance
706
+ gestureResponseDistance,
707
+ panGesture
632
708
  });
633
- if (criteria?.activeOffsetX) {
634
- panGesture.activeOffsetX(criteria.activeOffsetX);
635
- }
636
- if (criteria?.activeOffsetY) {
637
- panGesture.activeOffsetY(criteria.activeOffsetY);
638
- }
639
- if (criteria?.failOffsetX) {
640
- panGesture.failOffsetX(criteria.failOffsetX);
641
- }
642
- if (criteria?.failOffsetY) {
643
- panGesture.failOffsetY(criteria.failOffsetY);
644
- }
645
- panGesture.enableTrackpadTwoFingerGesture(true);
646
- const nativeGesture = Gesture.Native().shouldCancelWhenOutside(false);
647
- return Gesture.Race(panGesture, nativeGesture);
709
+ return { panGesture, nativeGesture };
648
710
  };
649
711
 
650
- // src/utils/noop-interpolator.ts
651
- var noopinterpolator = () => {
652
- "worklet";
653
- return {
654
- contentStyle: {},
655
- overlayStyle: {}
656
- };
712
+ // src/components/transition-gesture-handler-provider.tsx
713
+ import { jsx } from "react/jsx-runtime";
714
+ var TransitionGestureHandlerProvider = ({
715
+ children
716
+ }) => {
717
+ const scrollProgress = useSharedValue3({
718
+ x: 0,
719
+ y: 0,
720
+ contentHeight: 0,
721
+ contentWidth: 0,
722
+ layoutHeight: 0,
723
+ layoutWidth: 0
724
+ });
725
+ const { panGesture, nativeGesture } = useBuildGestures({
726
+ scrollProgress
727
+ });
728
+ const value = useMemo2(() => {
729
+ return {
730
+ panGesture,
731
+ scrollProgress,
732
+ nativeGesture
733
+ };
734
+ }, [panGesture, scrollProgress, nativeGesture]);
735
+ return /* @__PURE__ */ jsx(GestureContext.Provider, { value, children: /* @__PURE__ */ jsx(GestureDetector, { gesture: panGesture, children }) });
657
736
  };
658
737
 
659
- // src/hooks/use-key.tsx
660
- import { useRoute } from "@react-navigation/native";
661
- var useKey = () => useRoute().key;
662
-
663
738
  // src/hooks/use-screen-animation.tsx
739
+ import { useCallback as useCallback2, useMemo as useMemo3 } from "react";
740
+ import { useWindowDimensions as useWindowDimensions2 } from "react-native";
741
+ import { useSharedValue as useSharedValue4 } from "react-native-reanimated";
742
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
664
743
  var useAnimationBuilder = () => {
665
744
  const key = useKey();
666
- const dimensions = useWindowDimensions();
745
+ const dimensions = useWindowDimensions2();
667
746
  const insets = useSafeAreaInsets();
668
- const navigation = useNavigation();
669
- const { currentRoute, nextRoute } = RouteStore.use(
670
- useShallow(({ routes, routeKeys }) => {
671
- const current = routes[key];
672
- if (!current) {
673
- return { currentRoute: void 0, nextRoute: void 0 };
674
- }
675
- const currentScreenIndex = current.index;
676
- const nextKey = routeKeys[currentScreenIndex + 1];
677
- const next = nextKey ? routes[nextKey] : void 0;
678
- const isSameNavigator = next?.navigatorKey === current.navigatorKey;
679
- if (!isSameNavigator) {
680
- return {
681
- currentRoute: current,
682
- nextRoute: void 0
683
- };
684
- }
685
- return {
686
- currentRoute: current,
687
- nextRoute: next
688
- };
689
- })
747
+ const progressFallback = useSharedValue4(0);
748
+ const gestureDraggingFallback = useSharedValue4(0);
749
+ const gestureXFallback = useSharedValue4(0);
750
+ const gestureYFallback = useSharedValue4(0);
751
+ const normalizedGestureXFallback = useSharedValue4(0);
752
+ const normalizedGestureYFallback = useSharedValue4(0);
753
+ const isDismissingFallback = useSharedValue4(0);
754
+ const currentScreen = ScreenStore.use(
755
+ useCallback2((state) => state.screens[key], [key])
690
756
  );
691
- const panGesture = useMemo(
692
- () => buildGestureDetector({
693
- key,
694
- progress: animationValues.screenProgress[key],
695
- config: currentRoute || {
696
- id: key,
697
- name: key,
698
- index: 0,
699
- status: 0,
700
- closing: false
757
+ const actualNextScreen = ScreenStore.use(
758
+ useCallback2(
759
+ (state) => {
760
+ const current = state.screens[key];
761
+ if (!current) return void 0;
762
+ const nextKey = state.screenKeys[current.index + 1];
763
+ const nextScreen = nextKey ? state.screens[nextKey] : void 0;
764
+ const shouldUseNext = nextScreen?.navigatorKey === current?.navigatorKey;
765
+ return shouldUseNext ? nextScreen : void 0;
701
766
  },
702
- width: dimensions.width,
703
- height: dimensions.height,
704
- goBack: navigation.goBack
767
+ [key]
768
+ )
769
+ );
770
+ const getAnimationValuesForScreen = useCallback2(
771
+ (screenId) => ({
772
+ progress: animationValues.screenProgress[screenId] || progressFallback,
773
+ gesture: {
774
+ isDragging: animationValues.gestureDragging[screenId] || gestureDraggingFallback,
775
+ x: animationValues.gestureX[screenId] || gestureXFallback,
776
+ y: animationValues.gestureY[screenId] || gestureYFallback,
777
+ normalizedX: animationValues.normalizedGestureX[screenId] || normalizedGestureXFallback,
778
+ normalizedY: animationValues.normalizedGestureY[screenId] || normalizedGestureYFallback,
779
+ isDismissing: animationValues.isDismissing[screenId] || isDismissingFallback
780
+ }
705
781
  }),
706
- [key, currentRoute, dimensions.width, dimensions.height, navigation.goBack]
782
+ [
783
+ progressFallback,
784
+ gestureDraggingFallback,
785
+ gestureXFallback,
786
+ gestureYFallback,
787
+ normalizedGestureXFallback,
788
+ normalizedGestureYFallback,
789
+ isDismissingFallback
790
+ ]
707
791
  );
708
- const progressFallback = useSharedValue(0);
709
- const gestureDraggingFallback = useSharedValue(0);
710
- const gestureXFallback = useSharedValue(0);
711
- const gestureYFallback = useSharedValue(0);
712
- const normalizedGestureXFallback = useSharedValue(0);
713
- const normalizedGestureYFallback = useSharedValue(0);
714
- return useMemo(() => {
792
+ return useMemo3(() => {
715
793
  return {
716
- current: {
717
- progress: animationValues.screenProgress[key] || progressFallback,
718
- gesture: {
719
- isDragging: animationValues.gestureDragging[key] || gestureDraggingFallback,
720
- x: animationValues.gestureX[key] || gestureXFallback,
721
- y: animationValues.gestureY[key] || gestureYFallback,
722
- normalizedX: animationValues.normalizedGestureX[key] || normalizedGestureXFallback,
723
- normalizedY: animationValues.normalizedGestureY[key] || normalizedGestureYFallback
724
- }
725
- },
726
- next: nextRoute && animationValues.screenProgress[nextRoute.id] ? {
727
- progress: animationValues.screenProgress[nextRoute.id],
728
- gesture: {
729
- isDragging: animationValues.gestureDragging[nextRoute.id] || gestureDraggingFallback,
730
- x: animationValues.gestureX[nextRoute.id] || gestureXFallback,
731
- y: animationValues.gestureY[nextRoute.id] || gestureYFallback,
732
- normalizedX: animationValues.normalizedGestureX[nextRoute.id] || normalizedGestureXFallback,
733
- normalizedY: animationValues.normalizedGestureY[nextRoute.id] || normalizedGestureYFallback
734
- }
735
- } : void 0,
794
+ current: getAnimationValuesForScreen(key),
795
+ next: actualNextScreen ? getAnimationValuesForScreen(actualNextScreen.id) : void 0,
736
796
  layouts: { screen: dimensions },
737
797
  insets,
738
- closing: currentRoute?.closing || false,
739
- screenStyleInterpolator: nextRoute?.screenStyleInterpolator || currentRoute?.screenStyleInterpolator || noopinterpolator,
740
- gestureDetector: panGesture
798
+ closing: currentScreen?.closing || false,
799
+ screenStyleInterpolator: actualNextScreen?.screenStyleInterpolator || currentScreen?.screenStyleInterpolator || noopinterpolator
741
800
  };
742
801
  }, [
743
802
  key,
744
- currentRoute,
745
- nextRoute,
803
+ currentScreen,
804
+ actualNextScreen,
746
805
  dimensions,
747
806
  insets,
748
- panGesture,
749
- progressFallback,
750
- gestureDraggingFallback,
751
- gestureXFallback,
752
- gestureYFallback,
753
- normalizedGestureXFallback,
754
- normalizedGestureYFallback
807
+ getAnimationValuesForScreen
755
808
  ]);
756
809
  };
757
810
  var _useScreenAnimation = () => {
758
811
  return useAnimationBuilder();
759
812
  };
760
813
  var useScreenAnimation = () => {
761
- const {
762
- screenStyleInterpolator: _,
763
- gestureDetector: __,
764
- ...animationProps
765
- } = useAnimationBuilder();
814
+ const { screenStyleInterpolator: _, ...animationProps } = useAnimationBuilder();
766
815
  return animationProps;
767
816
  };
768
817
 
769
- // src/utils/create-config.ts
770
- var createConfig = ({
771
- navigation: reactNavigation,
772
- route,
773
- ...config
774
- }) => {
775
- return {
776
- focus: (e) => {
777
- const navigatorKey = reactNavigation.getState().key;
778
- RouteStore.updateRoute(e.target, {
779
- id: e.target,
780
- name: route.name,
781
- status: 1,
782
- closing: false,
783
- navigatorKey,
784
- ...config
785
- });
786
- },
787
- beforeRemove: (e) => {
788
- const navigatorState = reactNavigation.getState();
789
- const isLastScreenInStack = navigatorState.routes.length === 1 && navigatorState.routes[0].key === e.target;
790
- if (isLastScreenInStack) {
791
- RouteStore.removeRoute(e.target);
792
- return;
793
- }
794
- e.preventDefault();
795
- const handleFinish = (finished) => {
796
- if (!finished) return;
797
- if (reactNavigation.canGoBack()) {
798
- reactNavigation.dispatch(e.data?.action);
799
- RouteStore.removeRoute(e.target);
800
- }
801
- };
802
- RouteStore.updateRoute(e.target, {
803
- status: 0,
804
- closing: true,
805
- onAnimationFinish: handleFinish
806
- });
807
- }
808
- };
809
- };
810
-
811
- // src/utils/create-transition-component.tsx
812
- import { forwardRef, memo } from "react";
813
- import { StyleSheet } from "react-native";
814
- import { GestureDetector } from "react-native-gesture-handler";
815
- import Animated, {
816
- useAnimatedStyle as useAnimatedStyle2
817
- } from "react-native-reanimated";
818
-
819
818
  // src/hooks/use-skip-first-frame.tsx
820
819
  import { useEffect } from "react";
821
- import { useAnimatedStyle, useSharedValue as useSharedValue2 } from "react-native-reanimated";
820
+ import { useAnimatedStyle, useSharedValue as useSharedValue5 } from "react-native-reanimated";
822
821
  var useSkipFirstFrame = () => {
823
- const opacity = useSharedValue2(0);
822
+ const opacity = useSharedValue5(0);
824
823
  const style = useAnimatedStyle(() => {
825
824
  "worklet";
826
825
  return {
@@ -835,18 +834,14 @@ var useSkipFirstFrame = () => {
835
834
  return { style };
836
835
  };
837
836
 
838
- // src/utils/create-transition-component.tsx
839
- import { jsx, jsxs } from "react/jsx-runtime";
840
- function createTransitionComponent(Wrapped) {
837
+ // src/components/create-transition-aware-component.tsx
838
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
839
+ function createTransitionAwareComponent(Wrapped) {
841
840
  const AnimatedComponent = Animated.createAnimatedComponent(Wrapped);
842
841
  const Inner = forwardRef(
843
842
  (props, ref) => {
844
843
  const { children, style, ...rest } = props;
845
- const {
846
- screenStyleInterpolator,
847
- gestureDetector,
848
- ...screenInterpolationProps
849
- } = _useScreenAnimation();
844
+ const { screenStyleInterpolator, ...screenInterpolationProps } = _useScreenAnimation();
850
845
  const screenContainerStyle = useAnimatedStyle2(() => {
851
846
  "worklet";
852
847
  return screenStyleInterpolator(screenInterpolationProps).contentStyle || {};
@@ -856,8 +851,15 @@ function createTransitionComponent(Wrapped) {
856
851
  return screenStyleInterpolator(screenInterpolationProps).overlayStyle || {};
857
852
  });
858
853
  const { style: flickerFixStyle } = useSkipFirstFrame();
859
- return /* @__PURE__ */ jsxs(Animated.View, { style: [{ flex: 1 }, flickerFixStyle], children: [
860
- /* @__PURE__ */ jsx(GestureDetector, { gesture: gestureDetector, children: /* @__PURE__ */ jsx(
854
+ return /* @__PURE__ */ jsx2(TransitionGestureHandlerProvider, { children: /* @__PURE__ */ jsxs(Animated.View, { style: [{ flex: 1 }, flickerFixStyle], children: [
855
+ /* @__PURE__ */ jsx2(
856
+ Animated.View,
857
+ {
858
+ style: [StyleSheet.absoluteFillObject, overlayStyle],
859
+ pointerEvents: "none"
860
+ }
861
+ ),
862
+ /* @__PURE__ */ jsx2(
861
863
  AnimatedComponent,
862
864
  {
863
865
  ...rest,
@@ -869,41 +871,286 @@ function createTransitionComponent(Wrapped) {
869
871
  ],
870
872
  children
871
873
  }
872
- ) }),
873
- /* @__PURE__ */ jsx(
874
- Animated.View,
875
- {
876
- style: [
877
- StyleSheet.absoluteFillObject,
878
- overlayStyle,
879
- { zIndex: 1e4 }
880
- ],
881
- pointerEvents: "none"
882
- }
883
874
  )
884
- ] });
875
+ ] }) });
885
876
  }
886
877
  );
887
878
  Inner.displayName = `Transition(${Wrapped.displayName || Wrapped.name || "Component"})`;
888
879
  return memo(Inner);
889
880
  }
890
881
 
891
- // src/utils/default-screen-options.ts
892
- var defaultScreenOptions = () => ({
893
- presentation: "containedTransparentModal",
894
- headerShown: false,
895
- animation: "none"
882
+ // src/components/create-transition-aware-scrollable.tsx
883
+ import { forwardRef as forwardRef2, memo as memo2 } from "react";
884
+ import { View } from "react-native";
885
+ import { GestureDetector as GestureDetector2 } from "react-native-gesture-handler";
886
+ import Animated2 from "react-native-reanimated";
887
+
888
+ // src/hooks/use-scroll-progress.tsx
889
+ import { useCallback as useCallback3 } from "react";
890
+ import { useAnimatedScrollHandler } from "react-native-reanimated";
891
+ var useScrollProgress = (props) => {
892
+ const { scrollProgress } = useGestureContext();
893
+ const scrollHandler = useAnimatedScrollHandler({
894
+ onScroll: (event) => {
895
+ scrollProgress.modify((value) => {
896
+ "worklet";
897
+ return {
898
+ ...value,
899
+ x: event.contentOffset.x,
900
+ y: event.contentOffset.y,
901
+ layoutHeight: event.layoutMeasurement.height,
902
+ layoutWidth: event.layoutMeasurement.width,
903
+ contentHeight: event.contentSize.height,
904
+ contentWidth: event.contentSize.width
905
+ };
906
+ });
907
+ }
908
+ });
909
+ const onContentSizeChange = useCallback3(
910
+ (width, height) => {
911
+ props.onContentSizeChange?.(width, height);
912
+ scrollProgress.modify((value) => {
913
+ "worklet";
914
+ return {
915
+ ...value,
916
+ contentWidth: width,
917
+ contentHeight: height
918
+ };
919
+ });
920
+ },
921
+ [scrollProgress, props.onContentSizeChange]
922
+ );
923
+ return {
924
+ scrollHandler,
925
+ onContentSizeChange
926
+ };
927
+ };
928
+
929
+ // src/components/create-transition-aware-scrollable.tsx
930
+ import { jsx as jsx3 } from "react/jsx-runtime";
931
+ function createTransitionAwareScrollable(ScrollableComponent) {
932
+ const WithTransitionAwareness = createTransitionAwareComponent(View);
933
+ const AnimatedScrollableComponent = Animated2.createAnimatedComponent(ScrollableComponent);
934
+ const WithScrollAwareness = forwardRef2((props, ref) => {
935
+ const { nativeGesture } = useGestureContext();
936
+ const { scrollHandler, onContentSizeChange } = useScrollProgress({
937
+ onScroll: props.onScroll,
938
+ onContentSizeChange: props.onContentSizeChange
939
+ });
940
+ return /* @__PURE__ */ jsx3(GestureDetector2, { gesture: nativeGesture, children: /* @__PURE__ */ jsx3(
941
+ AnimatedScrollableComponent,
942
+ {
943
+ ...props,
944
+ ref,
945
+ onScroll: scrollHandler,
946
+ onContentSizeChange,
947
+ scrollEventThrottle: props.scrollEventThrottle || 16
948
+ }
949
+ ) });
950
+ });
951
+ const Wrapped = forwardRef2((props, ref) => {
952
+ const { isPlaceholder } = useGestureContext();
953
+ if (isPlaceholder) {
954
+ return /* @__PURE__ */ jsx3(WithTransitionAwareness, { children: /* @__PURE__ */ jsx3(WithScrollAwareness, { ...props, ref }) });
955
+ }
956
+ return /* @__PURE__ */ jsx3(WithScrollAwareness, { ...props, ref });
957
+ });
958
+ WithScrollAwareness.displayName = `Transition(${ScrollableComponent.displayName || ScrollableComponent.name || "Component"})`;
959
+ return memo2(Wrapped);
960
+ }
961
+
962
+ // src/configs/presets.ts
963
+ var presets_exports = {};
964
+ __export(presets_exports, {
965
+ DraggableCard: () => DraggableCard,
966
+ ElasticCard: () => ElasticCard,
967
+ SlideFromBottom: () => SlideFromBottom,
968
+ SlideFromTop: () => SlideFromTop,
969
+ ZoomIn: () => ZoomIn
896
970
  });
971
+ import {
972
+ Extrapolation,
973
+ interpolate as interpolate2,
974
+ interpolateColor
975
+ } from "react-native-reanimated";
976
+ var SlideFromTop = (config = {}) => {
977
+ return {
978
+ gestureEnabled: true,
979
+ gestureDirection: "vertical-inverted",
980
+ screenStyleInterpolator: ({
981
+ current,
982
+ next,
983
+ layouts: {
984
+ screen: { height }
985
+ }
986
+ }) => {
987
+ "worklet";
988
+ const progress = current.progress.value + (next?.progress.value ?? 0);
989
+ const y = interpolate2(progress, [0, 1, 2], [-height, 0, height]);
990
+ return {
991
+ contentStyle: {
992
+ transform: [{ translateY: y }]
993
+ }
994
+ };
995
+ },
996
+ transitionSpec: {
997
+ open: DefaultSpec,
998
+ close: DefaultSpec
999
+ },
1000
+ ...config
1001
+ };
1002
+ };
1003
+ var ZoomIn = (config = {}) => {
1004
+ return {
1005
+ gestureEnabled: false,
1006
+ screenStyleInterpolator: ({ current, next }) => {
1007
+ "worklet";
1008
+ const progress = current.progress.value + (next?.progress.value ?? 0);
1009
+ const scale = interpolate2(
1010
+ progress,
1011
+ [0, 1, 2],
1012
+ [0.5, 1, 0.5],
1013
+ Extrapolation.CLAMP
1014
+ );
1015
+ const opacity = interpolate2(
1016
+ progress,
1017
+ [0, 1, 2],
1018
+ [0, 1, 0],
1019
+ Extrapolation.CLAMP
1020
+ );
1021
+ return {
1022
+ contentStyle: {
1023
+ transform: [{ scale }],
1024
+ opacity
1025
+ }
1026
+ };
1027
+ },
1028
+ transitionSpec: {
1029
+ open: DefaultSpec,
1030
+ close: DefaultSpec
1031
+ },
1032
+ ...config
1033
+ };
1034
+ };
1035
+ var SlideFromBottom = (config = {}) => {
1036
+ return {
1037
+ gestureEnabled: true,
1038
+ gestureDirection: "vertical",
1039
+ screenStyleInterpolator: ({
1040
+ current,
1041
+ next,
1042
+ layouts: {
1043
+ screen: { height }
1044
+ }
1045
+ }) => {
1046
+ "worklet";
1047
+ const progress = current.progress.value + (next?.progress.value ?? 0);
1048
+ const y = interpolate2(progress, [0, 1, 2], [height, 0, -height]);
1049
+ return {
1050
+ contentStyle: {
1051
+ transform: [{ translateY: y }]
1052
+ }
1053
+ };
1054
+ },
1055
+ transitionSpec: {
1056
+ open: DefaultSpec,
1057
+ close: DefaultSpec
1058
+ },
1059
+ ...config
1060
+ };
1061
+ };
1062
+ var DraggableCard = (config = {}) => {
1063
+ return {
1064
+ gestureEnabled: true,
1065
+ gestureDirection: ["horizontal", "vertical"],
1066
+ screenStyleInterpolator: ({ current, next, layouts: { screen } }) => {
1067
+ "worklet";
1068
+ const progress = current.progress.value + (next?.progress.value ?? 0);
1069
+ const scale = interpolate2(progress, [0, 1, 2], [0, 1, 0.75]);
1070
+ const translateY = interpolate2(
1071
+ current.gesture.normalizedY.value,
1072
+ [-1, 1],
1073
+ [-screen.height * 0.5, screen.height * 0.5],
1074
+ "clamp"
1075
+ );
1076
+ const translateX = interpolate2(
1077
+ current.gesture.normalizedX.value,
1078
+ [-1, 1],
1079
+ [-screen.width * 0.5, screen.width * 0.5],
1080
+ "clamp"
1081
+ );
1082
+ return {
1083
+ contentStyle: {
1084
+ transform: [{ scale }, { translateY }, { translateX }]
1085
+ }
1086
+ };
1087
+ },
1088
+ transitionSpec: {
1089
+ open: DefaultSpec,
1090
+ close: DefaultSpec
1091
+ },
1092
+ ...config
1093
+ };
1094
+ };
1095
+ var ElasticCard = (config = { elasticFactor: 0.5 }) => {
1096
+ return {
1097
+ gestureEnabled: true,
1098
+ gestureDirection: "bidirectional",
1099
+ screenStyleInterpolator: ({ current, next, layouts: { screen } }) => {
1100
+ "worklet";
1101
+ const progress = current.progress.value + (next?.progress.value ?? 0);
1102
+ const scale = interpolate2(progress, [0, 1, 2], [0, 1, 0.8]);
1103
+ const maxElasticityX = screen.width * (config.elasticFactor ?? 0.5);
1104
+ const maxElasticityY = screen.height * (config.elasticFactor ?? 0.5);
1105
+ const translateX = interpolate2(
1106
+ current.gesture.normalizedX.value,
1107
+ [-1, 0, 1],
1108
+ [-maxElasticityX, 0, maxElasticityX],
1109
+ "clamp"
1110
+ );
1111
+ const translateY = interpolate2(
1112
+ current.gesture.normalizedY.value,
1113
+ [-1, 0, 1],
1114
+ [-maxElasticityY, 0, maxElasticityY],
1115
+ "clamp"
1116
+ );
1117
+ const overlayColor = interpolateColor(
1118
+ next?.progress.value || 0,
1119
+ [0, 1],
1120
+ ["rgba(0,0,0,0)", "rgba(0,0,0,0.5)"]
1121
+ );
1122
+ return {
1123
+ contentStyle: {
1124
+ transform: [{ scale }, { translateX }, { translateY }]
1125
+ },
1126
+ overlayStyle: {
1127
+ backgroundColor: overlayColor
1128
+ }
1129
+ };
1130
+ },
1131
+ ...config
1132
+ };
1133
+ };
897
1134
 
898
1135
  // src/index.ts
899
1136
  var index_default = {
900
- createTransitionComponent,
901
- View: createTransitionComponent(View),
902
- Pressable: createTransitionComponent(Pressable),
1137
+ View: createTransitionAwareComponent(View2),
1138
+ Pressable: createTransitionAwareComponent(Pressable),
1139
+ ScrollView: createTransitionAwareScrollable(ScrollView),
1140
+ FlatList: createTransitionAwareScrollable(FlatList),
903
1141
  createConfig,
1142
+ createScreenConfig,
904
1143
  defaultScreenOptions,
905
1144
  presets: presets_exports,
906
- specs: specs_exports
1145
+ specs: specs_exports,
1146
+ /**
1147
+ * Create a transition aware component
1148
+ */
1149
+ createTransitionAwareComponent,
1150
+ /**
1151
+ * Create a transition aware scrollable component
1152
+ */
1153
+ createTransitionAwareScrollable
907
1154
  };
908
1155
  export {
909
1156
  index_default as default,