@honeypathkar/react-native-predictive-back-gesture 1.0.4 → 1.0.5
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.
|
@@ -9,4 +9,4 @@ const syncBackMode = React.useCallback(() => {
|
|
|
9
9
|
: PredictiveBack_1.BACK_MODE_SYSTEM);
|
|
10
10
|
}, []);
|
|
11
11
|
// On the NavigationContainer:
|
|
12
|
-
|
|
12
|
+
React.createElement(NavigationContainer, { ref: navigationRef, onReady: syncBackMode, onStateChange: syncBackMode });
|
|
@@ -38,7 +38,6 @@ const react_native_1 = require("react-native");
|
|
|
38
38
|
const react_native_gesture_handler_1 = require("react-native-gesture-handler");
|
|
39
39
|
const react_native_reanimated_1 = __importStar(require("react-native-reanimated"));
|
|
40
40
|
const native_1 = require("@react-navigation/native");
|
|
41
|
-
const react_native_paper_1 = require("react-native-paper");
|
|
42
41
|
const PredectiveBack_1 = require("./PredectiveBack");
|
|
43
42
|
const { width, height } = react_native_1.Dimensions.get("window");
|
|
44
43
|
// Material predictive-back peek: the card shrinks and drifts a little way to the right
|
|
@@ -63,9 +62,14 @@ const DRAG_RANGE = width * 0.6;
|
|
|
63
62
|
const PEEK_THRESHOLD = 0.35;
|
|
64
63
|
const VELOCITY_THRESHOLD = 900;
|
|
65
64
|
const MIN_VELOCITY_DISTANCE = 50;
|
|
66
|
-
const SwipeableScreen = ({ children, enabled = true, style, onHaptic }) => {
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
const SwipeableScreen = ({ children, enabled = true, style, onHaptic, backgroundColor = '#000000', onGoBack }) => {
|
|
66
|
+
let navigation = null;
|
|
67
|
+
try {
|
|
68
|
+
navigation = (0, native_1.useNavigation)();
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
// Component rendered outside React Navigation container
|
|
72
|
+
}
|
|
69
73
|
// Gesture progress (0…1) — drives the peek: shrink, drift right, rounded corners.
|
|
70
74
|
const peek = (0, react_native_reanimated_1.useSharedValue)(0);
|
|
71
75
|
// Dismissal progress (0…1) — slides the card clear to the right. Also runs in
|
|
@@ -97,18 +101,22 @@ const SwipeableScreen = ({ children, enabled = true, style, onHaptic }) => {
|
|
|
97
101
|
exit.value = (0, react_native_reanimated_1.withTiming)(0, { duration: 200, easing: EASING });
|
|
98
102
|
}, [peek, exit, nativeActive]);
|
|
99
103
|
const goBack = (0, react_1.useCallback)(() => {
|
|
100
|
-
if (
|
|
104
|
+
if (onGoBack) {
|
|
105
|
+
onGoBack();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (!navigation || !navigation.canGoBack()) {
|
|
101
109
|
settle();
|
|
102
110
|
return;
|
|
103
111
|
}
|
|
104
112
|
navigation.goBack();
|
|
105
113
|
// goBack dispatches through `beforeRemove`, so it may not actually pop.
|
|
106
114
|
requestAnimationFrame(() => {
|
|
107
|
-
if (navigation.isFocused()) {
|
|
115
|
+
if (navigation && navigation.isFocused && navigation.isFocused()) {
|
|
108
116
|
settle();
|
|
109
117
|
}
|
|
110
118
|
});
|
|
111
|
-
}, [navigation, settle]);
|
|
119
|
+
}, [navigation, settle, onGoBack]);
|
|
112
120
|
// ─── Commit / cancel ─────────────────────────────────────────────────────
|
|
113
121
|
const commit = (0, react_1.useCallback)((duration = EXIT_DURATION) => {
|
|
114
122
|
if (isDismissing.current) {
|
|
@@ -131,8 +139,9 @@ const SwipeableScreen = ({ children, enabled = true, style, onHaptic }) => {
|
|
|
131
139
|
}, [peek, nativeActive]);
|
|
132
140
|
// ─── System back gesture (Android) ───────────────────────────────────────
|
|
133
141
|
const token = (0, react_1.useRef)({}).current;
|
|
134
|
-
const
|
|
135
|
-
|
|
142
|
+
const canGoBackInNav = navigation && navigation.canGoBack ? navigation.canGoBack() : false;
|
|
143
|
+
const isActive = enabled && (canGoBackInNav || !!onGoBack);
|
|
144
|
+
const focusEffectCallback = (0, react_1.useCallback)(() => {
|
|
136
145
|
if (!PredectiveBack_1.PREDICTIVE_BACK_SUPPORTED || !isActive) {
|
|
137
146
|
return undefined;
|
|
138
147
|
}
|
|
@@ -160,7 +169,15 @@ const SwipeableScreen = ({ children, enabled = true, style, onHaptic }) => {
|
|
|
160
169
|
onCommit: () => commit(PredectiveBack_1.PREDICTIVE_BACK_HAS_PROGRESS ? EXIT_DURATION : ENTER_DURATION),
|
|
161
170
|
});
|
|
162
171
|
return () => (0, PredectiveBack_1.releasePredictiveBack)(token);
|
|
163
|
-
}, [token, isActive, peek, pivot, nativeActive, cancel, commit])
|
|
172
|
+
}, [token, isActive, peek, pivot, nativeActive, cancel, commit]);
|
|
173
|
+
if (navigation) {
|
|
174
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
175
|
+
(0, native_1.useFocusEffect)(focusEffectCallback);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
179
|
+
(0, react_1.useEffect)(focusEffectCallback, [focusEffectCallback]);
|
|
180
|
+
}
|
|
164
181
|
// ─── Drag-to-close from the left edge ────────────────────────────────────
|
|
165
182
|
// On Android this covers the strip just inside the system gesture zone; on iOS it
|
|
166
183
|
// is the only way back. Both drive the same peek, so the card looks identical
|
|
@@ -222,20 +239,15 @@ const SwipeableScreen = ({ children, enabled = true, style, onHaptic }) => {
|
|
|
222
239
|
opacity: (0, react_native_reanimated_1.interpolate)(revealed, [0, 1], [MAX_DIM, 0], react_native_reanimated_1.Extrapolation.CLAMP),
|
|
223
240
|
};
|
|
224
241
|
});
|
|
225
|
-
return (
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
]}>
|
|
235
|
-
{children}
|
|
236
|
-
</react_native_reanimated_1.default.View>
|
|
237
|
-
</react_native_gesture_handler_1.GestureDetector>
|
|
238
|
-
</react_native_1.View>);
|
|
242
|
+
return (react_1.default.createElement(react_native_1.View, { style: styles.outerWrapper },
|
|
243
|
+
react_1.default.createElement(react_native_reanimated_1.default.View, { pointerEvents: "none", style: [styles.backdrop, backdropStyle] }),
|
|
244
|
+
react_1.default.createElement(react_native_gesture_handler_1.GestureDetector, { gesture: panGesture },
|
|
245
|
+
react_1.default.createElement(react_native_reanimated_1.default.View, { style: [
|
|
246
|
+
styles.screen,
|
|
247
|
+
{ backgroundColor },
|
|
248
|
+
screenStyle,
|
|
249
|
+
style,
|
|
250
|
+
] }, children))));
|
|
239
251
|
};
|
|
240
252
|
exports.default = SwipeableScreen;
|
|
241
253
|
const styles = react_native_1.StyleSheet.create({
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["../src/appnavigator.jsx","../src/predectiveback.js","../src/swipablescreen.jsx","../src/index.js"],"version":"5.9.3"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export default SwipeableScreen;
|
|
2
|
-
declare function SwipeableScreen({ children, enabled, style, onHaptic }: {
|
|
2
|
+
declare function SwipeableScreen({ children, enabled, style, onHaptic, backgroundColor, onGoBack }: {
|
|
3
3
|
children: any;
|
|
4
4
|
enabled?: boolean;
|
|
5
5
|
style: any;
|
|
6
6
|
onHaptic: any;
|
|
7
|
+
backgroundColor?: string;
|
|
8
|
+
onGoBack: any;
|
|
7
9
|
}): React.JSX.Element;
|
|
8
10
|
import React from "react";
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@honeypathkar/react-native-predictive-back-gesture",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Android 14+ Predictive Back gesture animations and swipeable screen component for React Native.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
|
-
"main": "
|
|
9
|
-
"module": "
|
|
8
|
+
"main": "lib/commonjs/index.js",
|
|
9
|
+
"module": "lib/module/index.js",
|
|
10
10
|
"types": "lib/typescript/index.d.ts",
|
|
11
11
|
"files": [
|
|
12
12
|
"src",
|
|
@@ -34,13 +34,12 @@
|
|
|
34
34
|
"react": "*",
|
|
35
35
|
"react-native": "*",
|
|
36
36
|
"react-native-gesture-handler": "*",
|
|
37
|
-
"react-native-reanimated": "*"
|
|
38
|
-
"react-native-paper": "*"
|
|
37
|
+
"react-native-reanimated": "*"
|
|
39
38
|
},
|
|
40
39
|
"devDependencies": {
|
|
41
40
|
"@types/react": "^18.3.31",
|
|
42
41
|
"react": "^18.2.0",
|
|
43
|
-
"react-native": "^0.72.
|
|
42
|
+
"react-native": "^0.72.17",
|
|
44
43
|
"react-native-gesture-handler": "^2.12.0",
|
|
45
44
|
"react-native-reanimated": "^3.3.0",
|
|
46
45
|
"typescript": "^5.9.3"
|
package/src/SwipableScreen.jsx
CHANGED
|
@@ -13,7 +13,6 @@ import Animated, {
|
|
|
13
13
|
cancelAnimation,
|
|
14
14
|
} from "react-native-reanimated";
|
|
15
15
|
import { useNavigation, useFocusEffect } from "@react-navigation/native";
|
|
16
|
-
import { useTheme } from "react-native-paper";
|
|
17
16
|
import {
|
|
18
17
|
PREDICTIVE_BACK_HAS_PROGRESS,
|
|
19
18
|
PREDICTIVE_BACK_SUPPORTED,
|
|
@@ -48,9 +47,13 @@ const PEEK_THRESHOLD = 0.35;
|
|
|
48
47
|
const VELOCITY_THRESHOLD = 900;
|
|
49
48
|
const MIN_VELOCITY_DISTANCE = 50;
|
|
50
49
|
|
|
51
|
-
const SwipeableScreen = ({ children, enabled = true, style, onHaptic }) => {
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
const SwipeableScreen = ({ children, enabled = true, style, onHaptic, backgroundColor = '#000000', onGoBack }) => {
|
|
51
|
+
let navigation = null;
|
|
52
|
+
try {
|
|
53
|
+
navigation = useNavigation();
|
|
54
|
+
} catch (e) {
|
|
55
|
+
// Component rendered outside React Navigation container
|
|
56
|
+
}
|
|
54
57
|
|
|
55
58
|
// Gesture progress (0…1) — drives the peek: shrink, drift right, rounded corners.
|
|
56
59
|
const peek = useSharedValue(0);
|
|
@@ -86,18 +89,22 @@ const SwipeableScreen = ({ children, enabled = true, style, onHaptic }) => {
|
|
|
86
89
|
}, [peek, exit, nativeActive]);
|
|
87
90
|
|
|
88
91
|
const goBack = useCallback(() => {
|
|
89
|
-
if (
|
|
92
|
+
if (onGoBack) {
|
|
93
|
+
onGoBack();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (!navigation || !navigation.canGoBack()) {
|
|
90
97
|
settle();
|
|
91
98
|
return;
|
|
92
99
|
}
|
|
93
100
|
navigation.goBack();
|
|
94
101
|
// goBack dispatches through `beforeRemove`, so it may not actually pop.
|
|
95
102
|
requestAnimationFrame(() => {
|
|
96
|
-
if (navigation.isFocused()) {
|
|
103
|
+
if (navigation && navigation.isFocused && navigation.isFocused()) {
|
|
97
104
|
settle();
|
|
98
105
|
}
|
|
99
106
|
});
|
|
100
|
-
}, [navigation, settle]);
|
|
107
|
+
}, [navigation, settle, onGoBack]);
|
|
101
108
|
|
|
102
109
|
// ─── Commit / cancel ─────────────────────────────────────────────────────
|
|
103
110
|
const commit = useCallback(
|
|
@@ -126,43 +133,50 @@ const SwipeableScreen = ({ children, enabled = true, style, onHaptic }) => {
|
|
|
126
133
|
|
|
127
134
|
// ─── System back gesture (Android) ───────────────────────────────────────
|
|
128
135
|
const token = useRef({}).current;
|
|
129
|
-
const
|
|
136
|
+
const canGoBackInNav = navigation && navigation.canGoBack ? navigation.canGoBack() : false;
|
|
137
|
+
const isActive = enabled && (canGoBackInNav || !!onGoBack);
|
|
130
138
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
139
|
+
const focusEffectCallback = useCallback(() => {
|
|
140
|
+
if (!PREDICTIVE_BACK_SUPPORTED || !isActive) {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
136
143
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
144
|
+
const track = (event) => {
|
|
145
|
+
peek.value = event.progress;
|
|
146
|
+
pivot.value = (event.touchY / height) * 2 - 1;
|
|
147
|
+
};
|
|
141
148
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
+
acquirePredictiveBack(token, {
|
|
150
|
+
onStart: (event) => {
|
|
151
|
+
if (isDismissing.current) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
nativeActive.value = 1;
|
|
155
|
+
cancelAnimation(peek);
|
|
156
|
+
track(event);
|
|
157
|
+
},
|
|
158
|
+
onProgress: (event) => {
|
|
159
|
+
if (!isDismissing.current) {
|
|
149
160
|
track(event);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
// has not moved yet, so give the slide-out a little more room to breathe.
|
|
159
|
-
onCommit: () =>
|
|
160
|
-
commit(PREDICTIVE_BACK_HAS_PROGRESS ? EXIT_DURATION : ENTER_DURATION),
|
|
161
|
-
});
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
onCancel: cancel,
|
|
164
|
+
// Without live progress (pre-Android 14, or a 3-button back press) the card
|
|
165
|
+
// has not moved yet, so give the slide-out a little more room to breathe.
|
|
166
|
+
onCommit: () =>
|
|
167
|
+
commit(PREDICTIVE_BACK_HAS_PROGRESS ? EXIT_DURATION : ENTER_DURATION),
|
|
168
|
+
});
|
|
162
169
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
170
|
+
return () => releasePredictiveBack(token);
|
|
171
|
+
}, [token, isActive, peek, pivot, nativeActive, cancel, commit]);
|
|
172
|
+
|
|
173
|
+
if (navigation) {
|
|
174
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
175
|
+
useFocusEffect(focusEffectCallback);
|
|
176
|
+
} else {
|
|
177
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
178
|
+
useEffect(focusEffectCallback, [focusEffectCallback]);
|
|
179
|
+
}
|
|
166
180
|
|
|
167
181
|
// ─── Drag-to-close from the left edge ────────────────────────────────────
|
|
168
182
|
// On Android this covers the strip just inside the system gesture zone; on iOS it
|
|
@@ -246,7 +260,7 @@ const SwipeableScreen = ({ children, enabled = true, style, onHaptic }) => {
|
|
|
246
260
|
<Animated.View
|
|
247
261
|
style={[
|
|
248
262
|
styles.screen,
|
|
249
|
-
{ backgroundColor
|
|
263
|
+
{ backgroundColor },
|
|
250
264
|
screenStyle,
|
|
251
265
|
style,
|
|
252
266
|
]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"root":["../../src/appnavigator.jsx","../../src/predectiveback.js","../../src/swipablescreen.jsx","../../src/index.js"],"version":"5.9.3"}
|