react-native-screen-transitions 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +355 -0
- package/dist/index.d.mts +609 -0
- package/dist/index.d.ts +609 -0
- package/dist/index.js +904 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +891 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +54 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,891 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// src/index.ts
|
|
8
|
+
import { Pressable, View } from "react-native";
|
|
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
|
+
});
|
|
19
|
+
import {
|
|
20
|
+
Extrapolation,
|
|
21
|
+
interpolate,
|
|
22
|
+
interpolateColor
|
|
23
|
+
} from "react-native-reanimated";
|
|
24
|
+
|
|
25
|
+
// src/configs/specs.ts
|
|
26
|
+
var specs_exports = {};
|
|
27
|
+
__export(specs_exports, {
|
|
28
|
+
DefaultSpec: () => DefaultSpec
|
|
29
|
+
});
|
|
30
|
+
var DefaultSpec = {
|
|
31
|
+
stiffness: 1e3,
|
|
32
|
+
damping: 500,
|
|
33
|
+
mass: 3,
|
|
34
|
+
overshootClamping: true,
|
|
35
|
+
restSpeedThreshold: 0.01
|
|
36
|
+
};
|
|
37
|
+
|
|
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
|
+
// src/animation-engine.ts
|
|
277
|
+
import {
|
|
278
|
+
cancelAnimation,
|
|
279
|
+
makeMutable,
|
|
280
|
+
runOnJS
|
|
281
|
+
} from "react-native-reanimated";
|
|
282
|
+
|
|
283
|
+
// src/store/utils/create-vanilla-store.ts
|
|
284
|
+
import { produce } from "immer";
|
|
285
|
+
import { useSyncExternalStore } from "react";
|
|
286
|
+
function createVanillaStore(initialState) {
|
|
287
|
+
let state = initialState;
|
|
288
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
289
|
+
const getState = () => state;
|
|
290
|
+
const setState = (updater) => {
|
|
291
|
+
const nextState = produce(state, updater);
|
|
292
|
+
if (nextState !== state) {
|
|
293
|
+
state = nextState;
|
|
294
|
+
listeners.forEach((listener) => listener());
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
const subscribe = (listener) => {
|
|
298
|
+
listeners.add(listener);
|
|
299
|
+
return () => listeners.delete(listener);
|
|
300
|
+
};
|
|
301
|
+
const subscribeWithSelector = (selector, listener) => {
|
|
302
|
+
let previousSelectedState = selector(state);
|
|
303
|
+
const internalListener = () => {
|
|
304
|
+
const currentSelectedState = selector(getState());
|
|
305
|
+
if (!Object.is(previousSelectedState, currentSelectedState)) {
|
|
306
|
+
listener(currentSelectedState, previousSelectedState);
|
|
307
|
+
previousSelectedState = currentSelectedState;
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
const unsubscribe = subscribe(internalListener);
|
|
311
|
+
listener(previousSelectedState, previousSelectedState);
|
|
312
|
+
return unsubscribe;
|
|
313
|
+
};
|
|
314
|
+
function useStore(selector) {
|
|
315
|
+
return useSyncExternalStore(subscribe, () => selector(getState()));
|
|
316
|
+
}
|
|
317
|
+
Object.assign(useStore, {
|
|
318
|
+
setState,
|
|
319
|
+
getState,
|
|
320
|
+
subscribe,
|
|
321
|
+
subscribeWithSelector
|
|
322
|
+
});
|
|
323
|
+
return useStore;
|
|
324
|
+
}
|
|
325
|
+
|
|
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
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
// src/utils/animate.ts
|
|
370
|
+
import {
|
|
371
|
+
withSpring,
|
|
372
|
+
withTiming
|
|
373
|
+
} from "react-native-reanimated";
|
|
374
|
+
var animate = (toValue, config, callback) => {
|
|
375
|
+
"worklet";
|
|
376
|
+
const isSpring = typeof config === "object" && !("duration" in config) && !("easing" in config);
|
|
377
|
+
if (!isSpring) {
|
|
378
|
+
return withTiming(toValue, config, callback);
|
|
379
|
+
}
|
|
380
|
+
return withSpring(toValue, config, callback);
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
// src/animation-engine.ts
|
|
384
|
+
var animationValues = {
|
|
385
|
+
screenProgress: {},
|
|
386
|
+
gestureX: {},
|
|
387
|
+
gestureY: {},
|
|
388
|
+
normalizedGestureX: {},
|
|
389
|
+
normalizedGestureY: {},
|
|
390
|
+
gestureDragging: {}
|
|
391
|
+
};
|
|
392
|
+
var triggerAnimation = (route) => {
|
|
393
|
+
"worklet";
|
|
394
|
+
const { id, closing, status, transitionSpec, onAnimationFinish } = route;
|
|
395
|
+
const progressValue = animationValues.screenProgress[id];
|
|
396
|
+
if (!progressValue && __DEV__) {
|
|
397
|
+
console.warn(`Animation values not found for route: ${id}`);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const animationConfig = closing ? transitionSpec?.close : transitionSpec?.open;
|
|
401
|
+
const targetValue = status || 0;
|
|
402
|
+
progressValue.value = animate(targetValue, animationConfig, (finished) => {
|
|
403
|
+
"worklet";
|
|
404
|
+
if (finished && onAnimationFinish) {
|
|
405
|
+
runOnJS(onAnimationFinish)(true);
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
};
|
|
409
|
+
RouteStore.use.subscribeWithSelector(
|
|
410
|
+
(state) => state.routes,
|
|
411
|
+
(currRoutes, prevRoutes) => {
|
|
412
|
+
const currKeys = Object.keys(currRoutes);
|
|
413
|
+
const prevKeys = Object.keys(prevRoutes);
|
|
414
|
+
const incomingKeys = currKeys.filter((k) => !prevKeys.includes(k));
|
|
415
|
+
const removedKeys = prevKeys.filter((k) => !currKeys.includes(k));
|
|
416
|
+
const changedKeys = currKeys.filter((k) => currRoutes[k] !== prevRoutes[k]);
|
|
417
|
+
const animatableValues = Object.values(animationValues);
|
|
418
|
+
for (const incomingKey of incomingKeys) {
|
|
419
|
+
for (const value of animatableValues) {
|
|
420
|
+
value[incomingKey] = makeMutable(0);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
for (const removedKey of removedKeys) {
|
|
424
|
+
for (const value of animatableValues) {
|
|
425
|
+
cancelAnimation(value[removedKey]);
|
|
426
|
+
delete value[removedKey];
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
for (const changedKey of changedKeys) {
|
|
430
|
+
const currentRoute = currRoutes[changedKey];
|
|
431
|
+
if (currentRoute) {
|
|
432
|
+
triggerAnimation(currentRoute);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
// src/utils/gesture/build-gesture-detector.ts
|
|
439
|
+
import { Gesture } from "react-native-gesture-handler";
|
|
440
|
+
import {
|
|
441
|
+
interpolate as interpolate2,
|
|
442
|
+
runOnJS as runOnJS2
|
|
443
|
+
} from "react-native-reanimated";
|
|
444
|
+
|
|
445
|
+
// src/utils/gesture/create-gesture-activation-criteria.ts
|
|
446
|
+
var createGestureActivationCriteria = ({
|
|
447
|
+
gestureDirection,
|
|
448
|
+
gestureResponseDistance
|
|
449
|
+
}) => {
|
|
450
|
+
const directions = Array.isArray(gestureDirection) ? gestureDirection : [gestureDirection];
|
|
451
|
+
if (directions.includes("bidirectional")) {
|
|
452
|
+
return {
|
|
453
|
+
activeOffsetX: [
|
|
454
|
+
-gestureResponseDistance,
|
|
455
|
+
gestureResponseDistance
|
|
456
|
+
],
|
|
457
|
+
activeOffsetY: [
|
|
458
|
+
-gestureResponseDistance,
|
|
459
|
+
gestureResponseDistance
|
|
460
|
+
]
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
const allowedDown = directions.includes("vertical");
|
|
464
|
+
const allowedUp = directions.includes("vertical-inverted");
|
|
465
|
+
const allowedRight = directions.includes("horizontal");
|
|
466
|
+
const allowedLeft = directions.includes("horizontal-inverted");
|
|
467
|
+
const toleranceX = 15;
|
|
468
|
+
const toleranceY = 20;
|
|
469
|
+
const dist = gestureResponseDistance;
|
|
470
|
+
const result = {};
|
|
471
|
+
const hasHorizontal = allowedLeft || allowedRight;
|
|
472
|
+
if (hasHorizontal) {
|
|
473
|
+
if (allowedLeft && allowedRight) {
|
|
474
|
+
result.activeOffsetX = [-dist, dist];
|
|
475
|
+
} else if (allowedLeft) {
|
|
476
|
+
result.activeOffsetX = -dist;
|
|
477
|
+
} else if (allowedRight) {
|
|
478
|
+
result.activeOffsetX = dist;
|
|
479
|
+
}
|
|
480
|
+
if (allowedRight && !allowedLeft) {
|
|
481
|
+
result.failOffsetX = -dist;
|
|
482
|
+
} else if (allowedLeft && !allowedRight) {
|
|
483
|
+
result.failOffsetX = dist;
|
|
484
|
+
}
|
|
485
|
+
} else {
|
|
486
|
+
result.failOffsetX = [-toleranceX, toleranceX];
|
|
487
|
+
}
|
|
488
|
+
const hasVertical = allowedUp || allowedDown;
|
|
489
|
+
if (hasVertical) {
|
|
490
|
+
if (allowedUp && allowedDown) {
|
|
491
|
+
result.activeOffsetY = [-dist, dist];
|
|
492
|
+
} else if (allowedUp) {
|
|
493
|
+
result.activeOffsetY = -dist;
|
|
494
|
+
} else if (allowedDown) {
|
|
495
|
+
result.activeOffsetY = dist;
|
|
496
|
+
}
|
|
497
|
+
if (allowedDown && !allowedUp) {
|
|
498
|
+
result.failOffsetY = -dist;
|
|
499
|
+
} else if (allowedUp && !allowedDown) {
|
|
500
|
+
result.failOffsetY = dist;
|
|
501
|
+
}
|
|
502
|
+
} else {
|
|
503
|
+
result.failOffsetY = [-toleranceY, toleranceY];
|
|
504
|
+
}
|
|
505
|
+
return result;
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
// src/utils/gesture/map-gesture-to-progress.ts
|
|
509
|
+
var mapGestureToProgress = (translation, dimension) => {
|
|
510
|
+
"worklet";
|
|
511
|
+
const rawProgress = translation / dimension;
|
|
512
|
+
return Math.max(0, Math.min(1, rawProgress));
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
// src/utils/gesture/build-gesture-detector.ts
|
|
516
|
+
var GESTURE_VELOCITY_IMPACT = 0.3;
|
|
517
|
+
var DEFAULT_GESTURE_RESPONSE_DISTANCE = 50;
|
|
518
|
+
var buildGestureDetector = ({
|
|
519
|
+
key,
|
|
520
|
+
progress,
|
|
521
|
+
config,
|
|
522
|
+
width,
|
|
523
|
+
height,
|
|
524
|
+
goBack
|
|
525
|
+
}) => {
|
|
526
|
+
const _translateX = animationValues.gestureX[key];
|
|
527
|
+
const _translateY = animationValues.gestureY[key];
|
|
528
|
+
const _normalizedGestureX = animationValues.normalizedGestureX[key];
|
|
529
|
+
const _normalizedGestureY = animationValues.normalizedGestureY[key];
|
|
530
|
+
const _isDragging = animationValues.gestureDragging[key];
|
|
531
|
+
const {
|
|
532
|
+
gestureDirection = "horizontal",
|
|
533
|
+
gestureEnabled = false,
|
|
534
|
+
transitionSpec,
|
|
535
|
+
gestureVelocityImpact = GESTURE_VELOCITY_IMPACT,
|
|
536
|
+
gestureResponseDistance = DEFAULT_GESTURE_RESPONSE_DISTANCE
|
|
537
|
+
} = config;
|
|
538
|
+
const directions = Array.isArray(gestureDirection) ? gestureDirection : [gestureDirection];
|
|
539
|
+
const panGesture = Gesture.Pan().enabled(gestureEnabled).onStart(() => {
|
|
540
|
+
"worklet";
|
|
541
|
+
_isDragging.value = 1;
|
|
542
|
+
}).onUpdate((event) => {
|
|
543
|
+
"worklet";
|
|
544
|
+
let gestureProgress = 0;
|
|
545
|
+
_translateX.value = event.translationX;
|
|
546
|
+
_translateY.value = event.translationY;
|
|
547
|
+
_normalizedGestureX.value = interpolate2(
|
|
548
|
+
event.translationX,
|
|
549
|
+
[-width, width],
|
|
550
|
+
[-1, 1],
|
|
551
|
+
"clamp"
|
|
552
|
+
);
|
|
553
|
+
_normalizedGestureY.value = interpolate2(
|
|
554
|
+
event.translationY,
|
|
555
|
+
[-height, height],
|
|
556
|
+
[-1, 1],
|
|
557
|
+
"clamp"
|
|
558
|
+
);
|
|
559
|
+
if (directions.includes("bidirectional")) {
|
|
560
|
+
const distance = Math.sqrt(
|
|
561
|
+
event.translationX ** 2 + event.translationY ** 2
|
|
562
|
+
);
|
|
563
|
+
gestureProgress = mapGestureToProgress(distance, width);
|
|
564
|
+
} else {
|
|
565
|
+
let maxProgress = 0;
|
|
566
|
+
const allowedDown = directions.includes("vertical");
|
|
567
|
+
const allowedUp = directions.includes("vertical-inverted");
|
|
568
|
+
const allowedRight = directions.includes("horizontal");
|
|
569
|
+
const allowedLeft = directions.includes("horizontal-inverted");
|
|
570
|
+
if (allowedRight || allowedLeft) {
|
|
571
|
+
const absX = Math.abs(event.translationX);
|
|
572
|
+
const currentProgress = mapGestureToProgress(absX, width);
|
|
573
|
+
maxProgress = Math.max(maxProgress, currentProgress);
|
|
574
|
+
}
|
|
575
|
+
if (allowedUp || allowedDown) {
|
|
576
|
+
const absY = Math.abs(event.translationY);
|
|
577
|
+
const currentProgress = mapGestureToProgress(absY, height);
|
|
578
|
+
maxProgress = Math.max(maxProgress, currentProgress);
|
|
579
|
+
}
|
|
580
|
+
gestureProgress = maxProgress;
|
|
581
|
+
}
|
|
582
|
+
progress.value = 1 - gestureProgress;
|
|
583
|
+
}).onEnd((event) => {
|
|
584
|
+
"worklet";
|
|
585
|
+
const { translationX, translationY, velocityX, velocityY } = event;
|
|
586
|
+
let shouldDismiss = false;
|
|
587
|
+
const dismissThreshold = 0.5;
|
|
588
|
+
if (directions.includes("bidirectional")) {
|
|
589
|
+
const finalX = Math.abs(
|
|
590
|
+
translationX + velocityX * gestureVelocityImpact
|
|
591
|
+
);
|
|
592
|
+
const finalY = Math.abs(
|
|
593
|
+
translationY + velocityY * gestureVelocityImpact
|
|
594
|
+
);
|
|
595
|
+
const finalDistance = Math.sqrt(finalX ** 2 + finalY ** 2);
|
|
596
|
+
shouldDismiss = finalDistance > width * dismissThreshold;
|
|
597
|
+
} else {
|
|
598
|
+
const allowedDown = directions.includes("vertical");
|
|
599
|
+
const allowedUp = directions.includes("vertical-inverted");
|
|
600
|
+
const allowedRight = directions.includes("horizontal");
|
|
601
|
+
const allowedLeft = directions.includes("horizontal-inverted");
|
|
602
|
+
if (allowedRight && translationX + velocityX * gestureVelocityImpact > width * dismissThreshold) {
|
|
603
|
+
shouldDismiss = true;
|
|
604
|
+
} else if (allowedLeft && -translationX - velocityX * gestureVelocityImpact > width * dismissThreshold) {
|
|
605
|
+
shouldDismiss = true;
|
|
606
|
+
} else if (allowedDown && translationY + velocityY * gestureVelocityImpact > height * dismissThreshold) {
|
|
607
|
+
shouldDismiss = true;
|
|
608
|
+
} else if (allowedUp && -translationY - velocityY * gestureVelocityImpact > height * dismissThreshold) {
|
|
609
|
+
shouldDismiss = true;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
const finalProgress = shouldDismiss ? 0 : 1;
|
|
613
|
+
const spec = shouldDismiss ? transitionSpec?.close : transitionSpec?.open;
|
|
614
|
+
const onFinish = shouldDismiss ? (isFinished) => {
|
|
615
|
+
"worklet";
|
|
616
|
+
if (isFinished) runOnJS2(goBack)();
|
|
617
|
+
} : void 0;
|
|
618
|
+
progress.value = animate(finalProgress, spec, onFinish);
|
|
619
|
+
_translateX.value = animate(0, spec);
|
|
620
|
+
_translateY.value = animate(0, spec);
|
|
621
|
+
_normalizedGestureX.value = animate(0, spec);
|
|
622
|
+
_normalizedGestureY.value = animate(0, spec);
|
|
623
|
+
});
|
|
624
|
+
const criteria = createGestureActivationCriteria({
|
|
625
|
+
gestureDirection,
|
|
626
|
+
gestureResponseDistance
|
|
627
|
+
});
|
|
628
|
+
if (criteria?.activeOffsetX) {
|
|
629
|
+
panGesture.activeOffsetX(criteria.activeOffsetX);
|
|
630
|
+
}
|
|
631
|
+
if (criteria?.activeOffsetY) {
|
|
632
|
+
panGesture.activeOffsetY(criteria.activeOffsetY);
|
|
633
|
+
}
|
|
634
|
+
if (criteria?.failOffsetX) {
|
|
635
|
+
panGesture.failOffsetX(criteria.failOffsetX);
|
|
636
|
+
}
|
|
637
|
+
if (criteria?.failOffsetY) {
|
|
638
|
+
panGesture.failOffsetY(criteria.failOffsetY);
|
|
639
|
+
}
|
|
640
|
+
panGesture.enableTrackpadTwoFingerGesture(true);
|
|
641
|
+
const nativeGesture = Gesture.Native().shouldCancelWhenOutside(false);
|
|
642
|
+
return Gesture.Race(panGesture, nativeGesture);
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
// src/utils/noop-interpolator.ts
|
|
646
|
+
var noopinterpolator = () => {
|
|
647
|
+
"worklet";
|
|
648
|
+
return {
|
|
649
|
+
contentStyle: {},
|
|
650
|
+
overlayStyle: {}
|
|
651
|
+
};
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
// src/hooks/use-key.tsx
|
|
655
|
+
import { useRoute } from "@react-navigation/native";
|
|
656
|
+
var useKey = () => useRoute().key;
|
|
657
|
+
|
|
658
|
+
// src/hooks/use-screen-animation.tsx
|
|
659
|
+
var useAnimationBuilder = () => {
|
|
660
|
+
const key = useKey();
|
|
661
|
+
const dimensions = useWindowDimensions();
|
|
662
|
+
const insets = useSafeAreaInsets();
|
|
663
|
+
const navigation = useNavigation();
|
|
664
|
+
const { currentRoute, nextRoute } = RouteStore.use(
|
|
665
|
+
useShallow(({ routes, routeKeys }) => {
|
|
666
|
+
const current = routes[key];
|
|
667
|
+
if (!current) {
|
|
668
|
+
return { currentRoute: void 0, nextRoute: void 0 };
|
|
669
|
+
}
|
|
670
|
+
const currentScreenIndex = current.index;
|
|
671
|
+
const nextKey = routeKeys[currentScreenIndex + 1];
|
|
672
|
+
return {
|
|
673
|
+
currentRoute: current,
|
|
674
|
+
nextRoute: nextKey ? routes[nextKey] : void 0
|
|
675
|
+
};
|
|
676
|
+
})
|
|
677
|
+
);
|
|
678
|
+
const panGesture = useMemo(
|
|
679
|
+
() => buildGestureDetector({
|
|
680
|
+
key,
|
|
681
|
+
progress: animationValues.screenProgress[key],
|
|
682
|
+
config: currentRoute || {
|
|
683
|
+
id: key,
|
|
684
|
+
name: key,
|
|
685
|
+
index: 0,
|
|
686
|
+
status: 0,
|
|
687
|
+
closing: false
|
|
688
|
+
},
|
|
689
|
+
width: dimensions.width,
|
|
690
|
+
height: dimensions.height,
|
|
691
|
+
goBack: navigation.goBack
|
|
692
|
+
}),
|
|
693
|
+
[key, currentRoute, dimensions.width, dimensions.height, navigation.goBack]
|
|
694
|
+
);
|
|
695
|
+
const progressFallback = useSharedValue(0);
|
|
696
|
+
const gestureDraggingFallback = useSharedValue(0);
|
|
697
|
+
const gestureXFallback = useSharedValue(0);
|
|
698
|
+
const gestureYFallback = useSharedValue(0);
|
|
699
|
+
const normalizedGestureXFallback = useSharedValue(0);
|
|
700
|
+
const normalizedGestureYFallback = useSharedValue(0);
|
|
701
|
+
return useMemo(() => {
|
|
702
|
+
return {
|
|
703
|
+
current: {
|
|
704
|
+
progress: animationValues.screenProgress[key] || progressFallback,
|
|
705
|
+
gesture: {
|
|
706
|
+
isDragging: animationValues.gestureDragging[key] || gestureDraggingFallback,
|
|
707
|
+
x: animationValues.gestureX[key] || gestureXFallback,
|
|
708
|
+
y: animationValues.gestureY[key] || gestureYFallback,
|
|
709
|
+
normalizedX: animationValues.normalizedGestureX[key] || normalizedGestureXFallback,
|
|
710
|
+
normalizedY: animationValues.normalizedGestureY[key] || normalizedGestureYFallback
|
|
711
|
+
}
|
|
712
|
+
},
|
|
713
|
+
next: nextRoute && animationValues.screenProgress[nextRoute.id] ? {
|
|
714
|
+
progress: animationValues.screenProgress[nextRoute.id],
|
|
715
|
+
gesture: {
|
|
716
|
+
isDragging: animationValues.gestureDragging[nextRoute.id] || gestureDraggingFallback,
|
|
717
|
+
x: animationValues.gestureX[nextRoute.id] || gestureXFallback,
|
|
718
|
+
y: animationValues.gestureY[nextRoute.id] || gestureYFallback,
|
|
719
|
+
normalizedX: animationValues.normalizedGestureX[nextRoute.id] || normalizedGestureXFallback,
|
|
720
|
+
normalizedY: animationValues.normalizedGestureY[nextRoute.id] || normalizedGestureYFallback
|
|
721
|
+
}
|
|
722
|
+
} : void 0,
|
|
723
|
+
layouts: { screen: dimensions },
|
|
724
|
+
insets,
|
|
725
|
+
closing: currentRoute?.closing || false,
|
|
726
|
+
screenStyleInterpolator: nextRoute?.screenStyleInterpolator || currentRoute?.screenStyleInterpolator || noopinterpolator,
|
|
727
|
+
gestureDetector: panGesture
|
|
728
|
+
};
|
|
729
|
+
}, [
|
|
730
|
+
key,
|
|
731
|
+
currentRoute,
|
|
732
|
+
nextRoute,
|
|
733
|
+
dimensions,
|
|
734
|
+
insets,
|
|
735
|
+
panGesture,
|
|
736
|
+
progressFallback,
|
|
737
|
+
gestureDraggingFallback,
|
|
738
|
+
gestureXFallback,
|
|
739
|
+
gestureYFallback,
|
|
740
|
+
normalizedGestureXFallback,
|
|
741
|
+
normalizedGestureYFallback
|
|
742
|
+
]);
|
|
743
|
+
};
|
|
744
|
+
var _useScreenAnimation = () => {
|
|
745
|
+
return useAnimationBuilder();
|
|
746
|
+
};
|
|
747
|
+
var useScreenAnimation = () => {
|
|
748
|
+
const {
|
|
749
|
+
screenStyleInterpolator: _,
|
|
750
|
+
gestureDetector: __,
|
|
751
|
+
...animationProps
|
|
752
|
+
} = useAnimationBuilder();
|
|
753
|
+
return animationProps;
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
// src/utils/create-config.ts
|
|
757
|
+
var createConfig = ({
|
|
758
|
+
navigation: reactNavigation,
|
|
759
|
+
route,
|
|
760
|
+
...config
|
|
761
|
+
}) => {
|
|
762
|
+
return {
|
|
763
|
+
focus: (e) => {
|
|
764
|
+
RouteStore.updateRoute(e.target, {
|
|
765
|
+
id: e.target,
|
|
766
|
+
name: route.name,
|
|
767
|
+
status: 1,
|
|
768
|
+
closing: false,
|
|
769
|
+
...config
|
|
770
|
+
});
|
|
771
|
+
},
|
|
772
|
+
beforeRemove: (e) => {
|
|
773
|
+
e.preventDefault();
|
|
774
|
+
const handleFinish = (finished) => {
|
|
775
|
+
if (!finished) return;
|
|
776
|
+
if (reactNavigation.canGoBack()) {
|
|
777
|
+
reactNavigation.dispatch(e.data?.action);
|
|
778
|
+
RouteStore.removeRoute(e.target);
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
RouteStore.updateRoute(e.target, {
|
|
782
|
+
status: 0,
|
|
783
|
+
closing: true,
|
|
784
|
+
onAnimationFinish: handleFinish
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
// src/utils/create-transition-component.tsx
|
|
791
|
+
import { forwardRef, memo } from "react";
|
|
792
|
+
import { StyleSheet } from "react-native";
|
|
793
|
+
import { GestureDetector } from "react-native-gesture-handler";
|
|
794
|
+
import Animated, {
|
|
795
|
+
useAnimatedStyle as useAnimatedStyle2
|
|
796
|
+
} from "react-native-reanimated";
|
|
797
|
+
|
|
798
|
+
// src/hooks/use-skip-first-frame.tsx
|
|
799
|
+
import { useEffect } from "react";
|
|
800
|
+
import { useAnimatedStyle, useSharedValue as useSharedValue2 } from "react-native-reanimated";
|
|
801
|
+
var useSkipFirstFrame = () => {
|
|
802
|
+
const opacity = useSharedValue2(0);
|
|
803
|
+
const style = useAnimatedStyle(() => {
|
|
804
|
+
"worklet";
|
|
805
|
+
return {
|
|
806
|
+
opacity: opacity.value
|
|
807
|
+
};
|
|
808
|
+
});
|
|
809
|
+
useEffect(() => {
|
|
810
|
+
requestAnimationFrame(() => {
|
|
811
|
+
opacity.value = 1;
|
|
812
|
+
});
|
|
813
|
+
}, [opacity]);
|
|
814
|
+
return { style };
|
|
815
|
+
};
|
|
816
|
+
|
|
817
|
+
// src/utils/create-transition-component.tsx
|
|
818
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
819
|
+
function createTransitionComponent(Wrapped) {
|
|
820
|
+
const AnimatedComponent = Animated.createAnimatedComponent(Wrapped);
|
|
821
|
+
const Inner = forwardRef(
|
|
822
|
+
(props, ref) => {
|
|
823
|
+
const { children, style, ...rest } = props;
|
|
824
|
+
const {
|
|
825
|
+
screenStyleInterpolator,
|
|
826
|
+
gestureDetector,
|
|
827
|
+
...screenInterpolationProps
|
|
828
|
+
} = _useScreenAnimation();
|
|
829
|
+
const screenContainerStyle = useAnimatedStyle2(() => {
|
|
830
|
+
"worklet";
|
|
831
|
+
return screenStyleInterpolator(screenInterpolationProps).contentStyle || {};
|
|
832
|
+
});
|
|
833
|
+
const overlayStyle = useAnimatedStyle2(() => {
|
|
834
|
+
"worklet";
|
|
835
|
+
return screenStyleInterpolator(screenInterpolationProps).overlayStyle || {};
|
|
836
|
+
});
|
|
837
|
+
const { style: flickerFixStyle } = useSkipFirstFrame();
|
|
838
|
+
return /* @__PURE__ */ jsxs(Animated.View, { style: [{ flex: 1 }, flickerFixStyle], children: [
|
|
839
|
+
/* @__PURE__ */ jsx(GestureDetector, { gesture: gestureDetector, children: /* @__PURE__ */ jsx(
|
|
840
|
+
AnimatedComponent,
|
|
841
|
+
{
|
|
842
|
+
...rest,
|
|
843
|
+
ref,
|
|
844
|
+
style: [
|
|
845
|
+
{ flex: 1, position: "relative" },
|
|
846
|
+
screenContainerStyle,
|
|
847
|
+
style
|
|
848
|
+
],
|
|
849
|
+
children
|
|
850
|
+
}
|
|
851
|
+
) }),
|
|
852
|
+
/* @__PURE__ */ jsx(
|
|
853
|
+
Animated.View,
|
|
854
|
+
{
|
|
855
|
+
style: [
|
|
856
|
+
StyleSheet.absoluteFillObject,
|
|
857
|
+
overlayStyle,
|
|
858
|
+
{ zIndex: 1e4 }
|
|
859
|
+
],
|
|
860
|
+
pointerEvents: "none"
|
|
861
|
+
}
|
|
862
|
+
)
|
|
863
|
+
] });
|
|
864
|
+
}
|
|
865
|
+
);
|
|
866
|
+
Inner.displayName = `Transition(${Wrapped.displayName || Wrapped.name || "Component"})`;
|
|
867
|
+
return memo(Inner);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// src/utils/default-screen-options.ts
|
|
871
|
+
var defaultScreenOptions = () => ({
|
|
872
|
+
presentation: "containedTransparentModal",
|
|
873
|
+
headerShown: false,
|
|
874
|
+
animation: "none"
|
|
875
|
+
});
|
|
876
|
+
|
|
877
|
+
// src/index.ts
|
|
878
|
+
var index_default = {
|
|
879
|
+
createTransitionComponent,
|
|
880
|
+
View: createTransitionComponent(View),
|
|
881
|
+
Pressable: createTransitionComponent(Pressable),
|
|
882
|
+
createConfig,
|
|
883
|
+
defaultScreenOptions,
|
|
884
|
+
presets: presets_exports,
|
|
885
|
+
specs: specs_exports
|
|
886
|
+
};
|
|
887
|
+
export {
|
|
888
|
+
index_default as default,
|
|
889
|
+
useScreenAnimation
|
|
890
|
+
};
|
|
891
|
+
//# sourceMappingURL=index.mjs.map
|