@honeypathkar/react-native-predictive-back-gesture 1.0.7 → 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 honeypathkar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ANIMATION_SLIDE = exports.ANIMATION_CARD = void 0;
36
37
  const react_1 = __importStar(require("react"));
37
38
  const react_native_1 = require("react-native");
38
39
  const react_native_gesture_handler_1 = require("react-native-gesture-handler");
@@ -40,6 +41,19 @@ const react_native_reanimated_1 = __importStar(require("react-native-reanimated"
40
41
  const native_1 = require("@react-navigation/native");
41
42
  const PredectiveBack_1 = require("./PredectiveBack");
42
43
  const { width, height } = react_native_1.Dimensions.get("window");
44
+ /**
45
+ * How the screen moves while the gesture is in flight.
46
+ *
47
+ * `card` is Material's predictive back: the screen shrinks and drifts a little,
48
+ * staying on screen so the user can see what they are about to go back to, and
49
+ * only leaves once they commit. `slide` is the older, flatter idea — the screen
50
+ * tracks the finger across the full width and walks off the edge, which is what
51
+ * most stock apps still do.
52
+ *
53
+ * The default stays `card`, so nothing changes for anything already using this.
54
+ */
55
+ exports.ANIMATION_CARD = "card";
56
+ exports.ANIMATION_SLIDE = "slide";
43
57
  // Material predictive-back peek: the card shrinks and drifts a little way to the right
44
58
  // while the gesture is in flight, revealing the screen underneath. It only leaves the
45
59
  // screen once the user commits.
@@ -48,6 +62,17 @@ const MAX_PEEK_X = width * 0.12;
48
62
  const MAX_PEEK_Y = height * 0.03;
49
63
  const CORNER_RADIUS = 32;
50
64
  const MAX_DIM = 0.5;
65
+ /**
66
+ * How far a full drag carries a sliding screen, as a fraction of the width.
67
+ *
68
+ * Not 1. Dragging the screen all the way off under the finger leaves nothing on
69
+ * screen before the gesture is even released, so a cancel has to haul the whole
70
+ * width back and a commit has nothing left to animate — the transition is over
71
+ * before the decision is made. Stopping the drag short keeps a piece of the
72
+ * outgoing screen in view the whole time and leaves the commit something to do,
73
+ * which is what makes the release read as a release.
74
+ */
75
+ const SLIDE_PEEK_TRAVEL = 0.6;
51
76
  // Screens enter from the right and leave back to the right — the exit always mirrors
52
77
  // the entry, whichever edge the gesture came from.
53
78
  const ENTER_DURATION = 280;
@@ -62,7 +87,15 @@ const DRAG_RANGE = width * 0.6;
62
87
  const PEEK_THRESHOLD = 0.35;
63
88
  const VELOCITY_THRESHOLD = 900;
64
89
  const MIN_VELOCITY_DISTANCE = 50;
65
- const SwipeableScreen = ({ children, enabled = true, style, onHaptic, backgroundColor = '#000000', onGoBack }) => {
90
+ const SwipeableScreen = ({ children, enabled = true, style, onHaptic, backgroundColor = '#000000', onGoBack, animation = exports.ANIMATION_CARD, peekTravel = SLIDE_PEEK_TRAVEL, }) => {
91
+ const isSlide = animation === exports.ANIMATION_SLIDE;
92
+ // Clamped rather than trusted: a value above 1 would push the screen past the
93
+ // edge mid-drag and then have the commit animate it back inwards.
94
+ const slideTravel = Math.min(Math.max(peekTravel, 0), 1);
95
+ // A peek only has to travel far enough to read as a peek, so the card
96
+ // completes in well under a screen width. A slide is the screen itself under
97
+ // the finger, and anything other than one-to-one feels like lag.
98
+ const dragRange = isSlide ? width : DRAG_RANGE;
66
99
  let navigation = null;
67
100
  try {
68
101
  navigation = (0, native_1.useNavigation)();
@@ -199,7 +232,7 @@ const SwipeableScreen = ({ children, enabled = true, style, onHaptic, background
199
232
  if (nativeActive.value || event.translationX <= 0) {
200
233
  return;
201
234
  }
202
- peek.value = Math.min(event.translationX / DRAG_RANGE, 1);
235
+ peek.value = Math.min(event.translationX / dragRange, 1);
203
236
  pivot.value = (event.y / height) * 2 - 1;
204
237
  })
205
238
  .onEnd((event) => {
@@ -221,9 +254,22 @@ const SwipeableScreen = ({ children, enabled = true, style, onHaptic, background
221
254
  // ─── Card ────────────────────────────────────────────────────────────────
222
255
  const screenStyle = (0, react_native_reanimated_1.useAnimatedStyle)(() => {
223
256
  const p = peek.value;
257
+ const e = exit.value;
258
+ if (isSlide) {
259
+ // One expression for both halves of the journey. The gesture carries the
260
+ // screen as far as slideTravel allows; the commit takes it from wherever
261
+ // that left off out to the full width. Written as a single blend so there
262
+ // is no jump at the hand-off — a release at 40% of the drag continues
263
+ // from exactly where the finger left it, rather than snapping to a fresh
264
+ // starting point.
265
+ const travelled = p * slideTravel * width;
266
+ return {
267
+ transform: [{ translateX: travelled + (width - travelled) * e }],
268
+ };
269
+ }
224
270
  return {
225
271
  transform: [
226
- { translateX: p * MAX_PEEK_X + exit.value * width },
272
+ { translateX: p * MAX_PEEK_X + e * width },
227
273
  { translateY: pivot.value * p * MAX_PEEK_Y },
228
274
  { scale: 1 - MAX_SCALE_DOWN * p },
229
275
  ],
@@ -1,11 +1,43 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
5
35
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.releasePredictiveBack = exports.acquirePredictiveBack = exports.setFallbackBackMode = exports.BACK_MODE_SYSTEM = exports.BACK_MODE_DEFAULT = exports.EDGE_RIGHT = exports.EDGE_LEFT = exports.PREDICTIVE_BACK_HAS_PROGRESS = exports.PREDICTIVE_BACK_SUPPORTED = exports.SwipeableScreen = void 0;
7
- const SwipableScreen_1 = __importDefault(require("./SwipableScreen"));
36
+ exports.releasePredictiveBack = exports.acquirePredictiveBack = exports.setFallbackBackMode = exports.BACK_MODE_SYSTEM = exports.BACK_MODE_DEFAULT = exports.EDGE_RIGHT = exports.EDGE_LEFT = exports.PREDICTIVE_BACK_HAS_PROGRESS = exports.PREDICTIVE_BACK_SUPPORTED = exports.ANIMATION_SLIDE = exports.ANIMATION_CARD = exports.SwipeableScreen = void 0;
37
+ const SwipableScreen_1 = __importStar(require("./SwipableScreen"));
8
38
  exports.SwipeableScreen = SwipableScreen_1.default;
39
+ Object.defineProperty(exports, "ANIMATION_CARD", { enumerable: true, get: function () { return SwipableScreen_1.ANIMATION_CARD; } });
40
+ Object.defineProperty(exports, "ANIMATION_SLIDE", { enumerable: true, get: function () { return SwipableScreen_1.ANIMATION_SLIDE; } });
9
41
  const PredectiveBack_1 = require("./PredectiveBack");
10
42
  Object.defineProperty(exports, "PREDICTIVE_BACK_SUPPORTED", { enumerable: true, get: function () { return PredectiveBack_1.PREDICTIVE_BACK_SUPPORTED; } });
11
43
  Object.defineProperty(exports, "PREDICTIVE_BACK_HAS_PROGRESS", { enumerable: true, get: function () { return PredectiveBack_1.PREDICTIVE_BACK_HAS_PROGRESS; } });
@@ -1,10 +1,25 @@
1
+ /**
2
+ * How the screen moves while the gesture is in flight.
3
+ *
4
+ * `card` is Material's predictive back: the screen shrinks and drifts a little,
5
+ * staying on screen so the user can see what they are about to go back to, and
6
+ * only leaves once they commit. `slide` is the older, flatter idea — the screen
7
+ * tracks the finger across the full width and walks off the edge, which is what
8
+ * most stock apps still do.
9
+ *
10
+ * The default stays `card`, so nothing changes for anything already using this.
11
+ */
12
+ export const ANIMATION_CARD: "card";
13
+ export const ANIMATION_SLIDE: "slide";
1
14
  export default SwipeableScreen;
2
- declare function SwipeableScreen({ children, enabled, style, onHaptic, backgroundColor, onGoBack }: {
15
+ declare function SwipeableScreen({ children, enabled, style, onHaptic, backgroundColor, onGoBack, animation, peekTravel, }: {
3
16
  children: any;
4
17
  enabled?: boolean;
5
18
  style: any;
6
19
  onHaptic: any;
7
20
  backgroundColor?: string;
8
21
  onGoBack: any;
22
+ animation?: string;
23
+ peekTravel?: number;
9
24
  }): React.JSX.Element;
10
25
  import React from "react";
@@ -1,5 +1,7 @@
1
1
  export default SwipeableScreen;
2
2
  import SwipeableScreen from './SwipableScreen';
3
+ import { ANIMATION_CARD } from './SwipableScreen';
4
+ import { ANIMATION_SLIDE } from './SwipableScreen';
3
5
  import { PREDICTIVE_BACK_SUPPORTED } from './PredectiveBack';
4
6
  import { PREDICTIVE_BACK_HAS_PROGRESS } from './PredectiveBack';
5
7
  import { EDGE_LEFT } from './PredectiveBack';
@@ -9,4 +11,4 @@ import { BACK_MODE_SYSTEM } from './PredectiveBack';
9
11
  import { setFallbackBackMode } from './PredectiveBack';
10
12
  import { acquirePredictiveBack } from './PredectiveBack';
11
13
  import { releasePredictiveBack } from './PredectiveBack';
12
- export { SwipeableScreen, PREDICTIVE_BACK_SUPPORTED, PREDICTIVE_BACK_HAS_PROGRESS, EDGE_LEFT, EDGE_RIGHT, BACK_MODE_DEFAULT, BACK_MODE_SYSTEM, setFallbackBackMode, acquirePredictiveBack, releasePredictiveBack };
14
+ export { SwipeableScreen, ANIMATION_CARD, ANIMATION_SLIDE, PREDICTIVE_BACK_SUPPORTED, PREDICTIVE_BACK_HAS_PROGRESS, EDGE_LEFT, EDGE_RIGHT, BACK_MODE_DEFAULT, BACK_MODE_SYSTEM, setFallbackBackMode, acquirePredictiveBack, releasePredictiveBack };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@honeypathkar/react-native-predictive-back-gesture",
3
- "version": "1.0.7",
3
+ "version": "1.1.0",
4
4
  "description": "Android 14+ Predictive Back gesture animations and swipeable screen component for React Native.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -28,6 +28,15 @@
28
28
  "swipeable-screen",
29
29
  "reanimated"
30
30
  ],
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/honeypathkar/react-native-components.git",
34
+ "directory": "react-native-predective-back-gesture"
35
+ },
36
+ "homepage": "https://github.com/honeypathkar/react-native-components/tree/master/react-native-predective-back-gesture#readme",
37
+ "bugs": {
38
+ "url": "https://github.com/honeypathkar/react-native-components/issues"
39
+ },
31
40
  "author": "honeypathkar",
32
41
  "license": "MIT",
33
42
  "peerDependencies": {
@@ -22,6 +22,20 @@ import {
22
22
 
23
23
  const { width, height } = Dimensions.get("window");
24
24
 
25
+ /**
26
+ * How the screen moves while the gesture is in flight.
27
+ *
28
+ * `card` is Material's predictive back: the screen shrinks and drifts a little,
29
+ * staying on screen so the user can see what they are about to go back to, and
30
+ * only leaves once they commit. `slide` is the older, flatter idea — the screen
31
+ * tracks the finger across the full width and walks off the edge, which is what
32
+ * most stock apps still do.
33
+ *
34
+ * The default stays `card`, so nothing changes for anything already using this.
35
+ */
36
+ export const ANIMATION_CARD = "card";
37
+ export const ANIMATION_SLIDE = "slide";
38
+
25
39
  // Material predictive-back peek: the card shrinks and drifts a little way to the right
26
40
  // while the gesture is in flight, revealing the screen underneath. It only leaves the
27
41
  // screen once the user commits.
@@ -31,6 +45,18 @@ const MAX_PEEK_Y = height * 0.03;
31
45
  const CORNER_RADIUS = 32;
32
46
  const MAX_DIM = 0.5;
33
47
 
48
+ /**
49
+ * How far a full drag carries a sliding screen, as a fraction of the width.
50
+ *
51
+ * Not 1. Dragging the screen all the way off under the finger leaves nothing on
52
+ * screen before the gesture is even released, so a cancel has to haul the whole
53
+ * width back and a commit has nothing left to animate — the transition is over
54
+ * before the decision is made. Stopping the drag short keeps a piece of the
55
+ * outgoing screen in view the whole time and leaves the commit something to do,
56
+ * which is what makes the release read as a release.
57
+ */
58
+ const SLIDE_PEEK_TRAVEL = 0.6;
59
+
34
60
  // Screens enter from the right and leave back to the right — the exit always mirrors
35
61
  // the entry, whichever edge the gesture came from.
36
62
  const ENTER_DURATION = 280;
@@ -47,7 +73,27 @@ const PEEK_THRESHOLD = 0.35;
47
73
  const VELOCITY_THRESHOLD = 900;
48
74
  const MIN_VELOCITY_DISTANCE = 50;
49
75
 
50
- const SwipeableScreen = ({ children, enabled = true, style, onHaptic, backgroundColor = '#000000', onGoBack }) => {
76
+ const SwipeableScreen = ({
77
+ children,
78
+ enabled = true,
79
+ style,
80
+ onHaptic,
81
+ backgroundColor = '#000000',
82
+ onGoBack,
83
+ animation = ANIMATION_CARD,
84
+ peekTravel = SLIDE_PEEK_TRAVEL,
85
+ }) => {
86
+ const isSlide = animation === ANIMATION_SLIDE;
87
+
88
+ // Clamped rather than trusted: a value above 1 would push the screen past the
89
+ // edge mid-drag and then have the commit animate it back inwards.
90
+ const slideTravel = Math.min(Math.max(peekTravel, 0), 1);
91
+
92
+ // A peek only has to travel far enough to read as a peek, so the card
93
+ // completes in well under a screen width. A slide is the screen itself under
94
+ // the finger, and anything other than one-to-one feels like lag.
95
+ const dragRange = isSlide ? width : DRAG_RANGE;
96
+
51
97
  let navigation = null;
52
98
  try {
53
99
  navigation = useNavigation();
@@ -199,7 +245,7 @@ const SwipeableScreen = ({ children, enabled = true, style, onHaptic, background
199
245
  if (nativeActive.value || event.translationX <= 0) {
200
246
  return;
201
247
  }
202
- peek.value = Math.min(event.translationX / DRAG_RANGE, 1);
248
+ peek.value = Math.min(event.translationX / dragRange, 1);
203
249
  pivot.value = (event.y / height) * 2 - 1;
204
250
  })
205
251
  .onEnd((event) => {
@@ -223,10 +269,24 @@ const SwipeableScreen = ({ children, enabled = true, style, onHaptic, background
223
269
  // ─── Card ────────────────────────────────────────────────────────────────
224
270
  const screenStyle = useAnimatedStyle(() => {
225
271
  const p = peek.value;
272
+ const e = exit.value;
273
+
274
+ if (isSlide) {
275
+ // One expression for both halves of the journey. The gesture carries the
276
+ // screen as far as slideTravel allows; the commit takes it from wherever
277
+ // that left off out to the full width. Written as a single blend so there
278
+ // is no jump at the hand-off — a release at 40% of the drag continues
279
+ // from exactly where the finger left it, rather than snapping to a fresh
280
+ // starting point.
281
+ const travelled = p * slideTravel * width;
282
+ return {
283
+ transform: [{ translateX: travelled + (width - travelled) * e }],
284
+ };
285
+ }
226
286
 
227
287
  return {
228
288
  transform: [
229
- { translateX: p * MAX_PEEK_X + exit.value * width },
289
+ { translateX: p * MAX_PEEK_X + e * width },
230
290
  { translateY: pivot.value * p * MAX_PEEK_Y },
231
291
  { scale: 1 - MAX_SCALE_DOWN * p },
232
292
  ],
package/src/index.js CHANGED
@@ -1,4 +1,7 @@
1
- import SwipeableScreen from './SwipableScreen';
1
+ import SwipeableScreen, {
2
+ ANIMATION_CARD,
3
+ ANIMATION_SLIDE,
4
+ } from './SwipableScreen';
2
5
  import {
3
6
  PREDICTIVE_BACK_SUPPORTED,
4
7
  PREDICTIVE_BACK_HAS_PROGRESS,
@@ -13,6 +16,8 @@ import {
13
16
 
14
17
  export {
15
18
  SwipeableScreen,
19
+ ANIMATION_CARD,
20
+ ANIMATION_SLIDE,
16
21
  PREDICTIVE_BACK_SUPPORTED,
17
22
  PREDICTIVE_BACK_HAS_PROGRESS,
18
23
  EDGE_LEFT,