react-native-screen-transitions 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -197,82 +197,11 @@ var ElasticCard = (config = { elasticFactor: 0.5 }) => {
197
197
 
198
198
  // src/hooks/use-screen-animation.tsx
199
199
  import { useNavigation } from "@react-navigation/native";
200
- import { useMemo } from "react";
200
+ import { useCallback, useMemo } from "react";
201
201
  import { useWindowDimensions } from "react-native";
202
202
  import { useSharedValue } from "react-native-reanimated";
203
203
  import { useSafeAreaInsets } from "react-native-safe-area-context";
204
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
205
  // src/animation-engine.ts
277
206
  import {
278
207
  cancelAnimation,
@@ -323,54 +252,93 @@ function createVanillaStore(initialState) {
323
252
  return useStore;
324
253
  }
325
254
 
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
- }
255
+ // src/store/utils/handle-screen-dismiss.tsx
256
+ import { StackActions } from "@react-navigation/native";
257
+ var handleScreenDismiss = (screenBeingDismissed, navigation) => {
258
+ const { screens } = ScreenStore.use.getState();
259
+ const dismissedScreen = screens[screenBeingDismissed];
260
+ if (!dismissedScreen) {
261
+ navigation.goBack();
262
+ return;
263
+ }
264
+ const childScreens = Object.values(screens).filter(
265
+ (screen) => screen.parentNavigatorKey === dismissedScreen.navigatorKey
266
+ );
267
+ if (childScreens.length > 0) {
268
+ ScreenStore.updateScreen(dismissedScreen.id, {
269
+ closing: true
365
270
  });
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;
271
+ navigation.dispatch(StackActions.pop(childScreens.length));
272
+ } else {
273
+ navigation.goBack();
371
274
  }
372
275
  };
373
276
 
277
+ // src/store/utils/remove-screen.tsx
278
+ var removeScreen = (key) => {
279
+ if (!key) return;
280
+ ScreenStore.use.setState(({ screens, screenKeys }) => {
281
+ delete screens[key];
282
+ const indexToRemove = screenKeys.indexOf(key);
283
+ if (indexToRemove > -1) {
284
+ screenKeys.splice(indexToRemove, 1);
285
+ }
286
+ });
287
+ };
288
+
289
+ // src/store/utils/should-skip-prevent-default.tsx
290
+ var shouldSkipPreventDefault = (key, navigatorState) => {
291
+ if (!key) return false;
292
+ const { screens } = ScreenStore.use.getState();
293
+ const currentScreen = screens[key];
294
+ const isLastScreenInStack = navigatorState.routes.length === 1 && navigatorState.routes[0].key === key;
295
+ const isParentNavigatorExiting = Boolean(
296
+ currentScreen?.parentNavigatorKey && Object.values(screens).some(
297
+ (screen) => screen.navigatorKey === currentScreen.parentNavigatorKey && screen.closing
298
+ )
299
+ );
300
+ return isLastScreenInStack || isParentNavigatorExiting;
301
+ };
302
+
303
+ // src/store/utils/update-screen.tsx
304
+ var updateScreen = (key, value) => {
305
+ if (!key) return;
306
+ ScreenStore.use.setState(({ screenKeys, screens }) => {
307
+ const currentScreen = screens[key];
308
+ if (currentScreen) {
309
+ screens[key] = {
310
+ ...currentScreen,
311
+ ...value
312
+ };
313
+ } else {
314
+ const { name = "", status = 0, closing = false, ...rest } = value;
315
+ const newIndex = screenKeys.length;
316
+ screens[key] = {
317
+ id: key,
318
+ index: newIndex,
319
+ name,
320
+ status,
321
+ closing,
322
+ ...rest
323
+ };
324
+ screenKeys.push(key);
325
+ }
326
+ });
327
+ };
328
+
329
+ // src/store/index.ts
330
+ var useScreenStore = createVanillaStore({
331
+ screens: {},
332
+ screenKeys: []
333
+ });
334
+ var ScreenStore = {
335
+ use: useScreenStore,
336
+ updateScreen,
337
+ removeScreen,
338
+ handleScreenDismiss,
339
+ shouldSkipPreventDefault
340
+ };
341
+
374
342
  // src/utils/animate.ts
375
343
  import {
376
344
  withSpring,
@@ -394,12 +362,12 @@ var animationValues = {
394
362
  normalizedGestureY: {},
395
363
  gestureDragging: {}
396
364
  };
397
- var triggerAnimation = (route) => {
365
+ var triggerAnimation = (screen) => {
398
366
  "worklet";
399
- const { id, closing, status, transitionSpec, onAnimationFinish } = route;
367
+ const { id, closing, status, transitionSpec, onAnimationFinish } = screen;
400
368
  const progressValue = animationValues.screenProgress[id];
401
369
  if (!progressValue && __DEV__) {
402
- console.warn(`Animation values not found for route: ${id}`);
370
+ console.warn(`Animation values not found for screen: ${id}`);
403
371
  return;
404
372
  }
405
373
  const animationConfig = closing ? transitionSpec?.close : transitionSpec?.open;
@@ -411,14 +379,16 @@ var triggerAnimation = (route) => {
411
379
  }
412
380
  });
413
381
  };
414
- RouteStore.use.subscribeWithSelector(
415
- (state) => state.routes,
416
- (currRoutes, prevRoutes) => {
417
- const currKeys = Object.keys(currRoutes);
418
- const prevKeys = Object.keys(prevRoutes);
382
+ ScreenStore.use.subscribeWithSelector(
383
+ (state) => state.screens,
384
+ (currScreens, prevScreens) => {
385
+ const currKeys = Object.keys(currScreens);
386
+ const prevKeys = Object.keys(prevScreens);
419
387
  const incomingKeys = currKeys.filter((k) => !prevKeys.includes(k));
420
388
  const removedKeys = prevKeys.filter((k) => !currKeys.includes(k));
421
- const changedKeys = currKeys.filter((k) => currRoutes[k] !== prevRoutes[k]);
389
+ const changedKeys = currKeys.filter(
390
+ (k) => currScreens[k] !== prevScreens[k]
391
+ );
422
392
  const animatableValues = Object.values(animationValues);
423
393
  for (const incomingKey of incomingKeys) {
424
394
  for (const value of animatableValues) {
@@ -432,9 +402,9 @@ RouteStore.use.subscribeWithSelector(
432
402
  }
433
403
  }
434
404
  for (const changedKey of changedKeys) {
435
- const currentRoute = currRoutes[changedKey];
436
- if (currentRoute) {
437
- triggerAnimation(currentRoute);
405
+ const currentScreen = currScreens[changedKey];
406
+ if (currentScreen) {
407
+ triggerAnimation(currentScreen);
438
408
  }
439
409
  }
440
410
  }
@@ -447,10 +417,11 @@ import {
447
417
  runOnJS as runOnJS2
448
418
  } from "react-native-reanimated";
449
419
 
450
- // src/utils/gesture/create-gesture-activation-criteria.ts
451
- var createGestureActivationCriteria = ({
420
+ // src/utils/gesture/apply-gesture-activation-criteria.ts
421
+ var applyGestureActivationCriteria = ({
452
422
  gestureDirection,
453
- gestureResponseDistance
423
+ gestureResponseDistance,
424
+ panGesture
454
425
  }) => {
455
426
  const directions = Array.isArray(gestureDirection) ? gestureDirection : [gestureDirection];
456
427
  if (directions.includes("bidirectional")) {
@@ -507,7 +478,20 @@ var createGestureActivationCriteria = ({
507
478
  } else {
508
479
  result.failOffsetY = [-toleranceY, toleranceY];
509
480
  }
510
- return result;
481
+ if (result?.activeOffsetX) {
482
+ panGesture.activeOffsetX(result.activeOffsetX);
483
+ }
484
+ if (result?.activeOffsetY) {
485
+ panGesture.activeOffsetY(result.activeOffsetY);
486
+ }
487
+ if (result?.failOffsetX) {
488
+ panGesture.failOffsetX(result.failOffsetX);
489
+ }
490
+ if (result?.failOffsetY) {
491
+ panGesture.failOffsetY(result.failOffsetY);
492
+ }
493
+ panGesture.enableTrackpadTwoFingerGesture(true);
494
+ return panGesture;
511
495
  };
512
496
 
513
497
  // src/utils/gesture/map-gesture-to-progress.ts
@@ -523,10 +507,10 @@ var DEFAULT_GESTURE_RESPONSE_DISTANCE = 50;
523
507
  var buildGestureDetector = ({
524
508
  key,
525
509
  progress,
526
- config,
510
+ screenState,
527
511
  width,
528
512
  height,
529
- goBack
513
+ handleDismiss
530
514
  }) => {
531
515
  const _translateX = animationValues.gestureX[key];
532
516
  const _translateY = animationValues.gestureY[key];
@@ -539,7 +523,7 @@ var buildGestureDetector = ({
539
523
  transitionSpec,
540
524
  gestureVelocityImpact = GESTURE_VELOCITY_IMPACT,
541
525
  gestureResponseDistance = DEFAULT_GESTURE_RESPONSE_DISTANCE
542
- } = config;
526
+ } = screenState;
543
527
  const directions = Array.isArray(gestureDirection) ? gestureDirection : [gestureDirection];
544
528
  const panGesture = Gesture.Pan().enabled(gestureEnabled).onStart(() => {
545
529
  "worklet";
@@ -618,7 +602,7 @@ var buildGestureDetector = ({
618
602
  const spec = shouldDismiss ? transitionSpec?.close : transitionSpec?.open;
619
603
  const onFinish = shouldDismiss ? (isFinished) => {
620
604
  "worklet";
621
- if (isFinished) runOnJS2(goBack)();
605
+ if (isFinished) runOnJS2(handleDismiss)(screenState.id);
622
606
  } : void 0;
623
607
  progress.value = animate(finalProgress, spec, onFinish);
624
608
  _translateX.value = animate(0, spec);
@@ -626,25 +610,12 @@ var buildGestureDetector = ({
626
610
  _normalizedGestureX.value = animate(0, spec);
627
611
  _normalizedGestureY.value = animate(0, spec);
628
612
  });
629
- const criteria = createGestureActivationCriteria({
613
+ applyGestureActivationCriteria({
630
614
  gestureDirection,
631
- gestureResponseDistance
615
+ gestureResponseDistance,
616
+ panGesture
632
617
  });
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);
618
+ return panGesture;
648
619
  };
649
620
 
650
621
  // src/utils/noop-interpolator.ts
@@ -666,33 +637,33 @@ var useAnimationBuilder = () => {
666
637
  const dimensions = useWindowDimensions();
667
638
  const insets = useSafeAreaInsets();
668
639
  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
- })
640
+ const progressFallback = useSharedValue(0);
641
+ const gestureDraggingFallback = useSharedValue(0);
642
+ const gestureXFallback = useSharedValue(0);
643
+ const gestureYFallback = useSharedValue(0);
644
+ const normalizedGestureXFallback = useSharedValue(0);
645
+ const normalizedGestureYFallback = useSharedValue(0);
646
+ const currentScreen = ScreenStore.use(
647
+ useCallback((state) => state.screens[key], [key])
648
+ );
649
+ const actualNextScreen = ScreenStore.use(
650
+ useCallback(
651
+ (state) => {
652
+ const current = state.screens[key];
653
+ if (!current) return void 0;
654
+ const nextKey = state.screenKeys[current.index + 1];
655
+ const nextScreen = nextKey ? state.screens[nextKey] : void 0;
656
+ const shouldUseNext = nextScreen?.navigatorKey === current?.navigatorKey;
657
+ return shouldUseNext ? nextScreen : void 0;
658
+ },
659
+ [key]
660
+ )
690
661
  );
691
662
  const panGesture = useMemo(
692
663
  () => buildGestureDetector({
693
664
  key,
694
665
  progress: animationValues.screenProgress[key],
695
- config: currentRoute || {
666
+ screenState: currentScreen || {
696
667
  id: key,
697
668
  name: key,
698
669
  index: 0,
@@ -701,57 +672,50 @@ var useAnimationBuilder = () => {
701
672
  },
702
673
  width: dimensions.width,
703
674
  height: dimensions.height,
704
- goBack: navigation.goBack
675
+ handleDismiss: (screenBeingDismissed) => {
676
+ ScreenStore.handleScreenDismiss(screenBeingDismissed, navigation);
677
+ }
705
678
  }),
706
- [key, currentRoute, dimensions.width, dimensions.height, navigation.goBack]
679
+ [key, currentScreen, dimensions, navigation]
680
+ );
681
+ const getAnimationValuesForScreen = useCallback(
682
+ (screenId) => ({
683
+ progress: animationValues.screenProgress[screenId] || progressFallback,
684
+ gesture: {
685
+ isDragging: animationValues.gestureDragging[screenId] || gestureDraggingFallback,
686
+ x: animationValues.gestureX[screenId] || gestureXFallback,
687
+ y: animationValues.gestureY[screenId] || gestureYFallback,
688
+ normalizedX: animationValues.normalizedGestureX[screenId] || normalizedGestureXFallback,
689
+ normalizedY: animationValues.normalizedGestureY[screenId] || normalizedGestureYFallback
690
+ }
691
+ }),
692
+ [
693
+ progressFallback,
694
+ gestureDraggingFallback,
695
+ gestureXFallback,
696
+ gestureYFallback,
697
+ normalizedGestureXFallback,
698
+ normalizedGestureYFallback
699
+ ]
707
700
  );
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
701
  return useMemo(() => {
715
702
  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,
703
+ current: getAnimationValuesForScreen(key),
704
+ next: actualNextScreen ? getAnimationValuesForScreen(actualNextScreen.id) : void 0,
736
705
  layouts: { screen: dimensions },
737
706
  insets,
738
- closing: currentRoute?.closing || false,
739
- screenStyleInterpolator: nextRoute?.screenStyleInterpolator || currentRoute?.screenStyleInterpolator || noopinterpolator,
707
+ closing: currentScreen?.closing || false,
708
+ screenStyleInterpolator: actualNextScreen?.screenStyleInterpolator || currentScreen?.screenStyleInterpolator || noopinterpolator,
740
709
  gestureDetector: panGesture
741
710
  };
742
711
  }, [
743
712
  key,
744
- currentRoute,
745
- nextRoute,
713
+ currentScreen,
714
+ actualNextScreen,
746
715
  dimensions,
747
716
  insets,
748
717
  panGesture,
749
- progressFallback,
750
- gestureDraggingFallback,
751
- gestureXFallback,
752
- gestureYFallback,
753
- normalizedGestureXFallback,
754
- normalizedGestureYFallback
718
+ getAnimationValuesForScreen
755
719
  ]);
756
720
  };
757
721
  var _useScreenAnimation = () => {
@@ -774,21 +738,25 @@ var createConfig = ({
774
738
  }) => {
775
739
  return {
776
740
  focus: (e) => {
741
+ const parentNavigatorKey = reactNavigation.getParent()?.getState?.()?.key;
777
742
  const navigatorKey = reactNavigation.getState().key;
778
- RouteStore.updateRoute(e.target, {
743
+ ScreenStore.updateScreen(e.target, {
779
744
  id: e.target,
780
745
  name: route.name,
781
746
  status: 1,
782
747
  closing: false,
783
748
  navigatorKey,
749
+ parentNavigatorKey,
784
750
  ...config
785
751
  });
786
752
  },
787
753
  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);
754
+ const shouldSkipPreventDefault2 = ScreenStore.shouldSkipPreventDefault(
755
+ e.target,
756
+ reactNavigation.getState()
757
+ );
758
+ if (shouldSkipPreventDefault2) {
759
+ ScreenStore.removeScreen(e.target);
792
760
  return;
793
761
  }
794
762
  e.preventDefault();
@@ -796,10 +764,10 @@ var createConfig = ({
796
764
  if (!finished) return;
797
765
  if (reactNavigation.canGoBack()) {
798
766
  reactNavigation.dispatch(e.data?.action);
799
- RouteStore.removeRoute(e.target);
767
+ ScreenStore.removeScreen(e.target);
800
768
  }
801
769
  };
802
- RouteStore.updateRoute(e.target, {
770
+ ScreenStore.updateScreen(e.target, {
803
771
  status: 0,
804
772
  closing: true,
805
773
  onAnimationFinish: handleFinish