react-native-screen-transitions 1.0.1 → 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,49 +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
  });
271
+ navigation.dispatch(StackActions.pop(childScreens.length));
272
+ } else {
273
+ navigation.goBack();
366
274
  }
367
275
  };
368
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
+
369
342
  // src/utils/animate.ts
370
343
  import {
371
344
  withSpring,
@@ -389,12 +362,12 @@ var animationValues = {
389
362
  normalizedGestureY: {},
390
363
  gestureDragging: {}
391
364
  };
392
- var triggerAnimation = (route) => {
365
+ var triggerAnimation = (screen) => {
393
366
  "worklet";
394
- const { id, closing, status, transitionSpec, onAnimationFinish } = route;
367
+ const { id, closing, status, transitionSpec, onAnimationFinish } = screen;
395
368
  const progressValue = animationValues.screenProgress[id];
396
369
  if (!progressValue && __DEV__) {
397
- console.warn(`Animation values not found for route: ${id}`);
370
+ console.warn(`Animation values not found for screen: ${id}`);
398
371
  return;
399
372
  }
400
373
  const animationConfig = closing ? transitionSpec?.close : transitionSpec?.open;
@@ -406,14 +379,16 @@ var triggerAnimation = (route) => {
406
379
  }
407
380
  });
408
381
  };
409
- RouteStore.use.subscribeWithSelector(
410
- (state) => state.routes,
411
- (currRoutes, prevRoutes) => {
412
- const currKeys = Object.keys(currRoutes);
413
- 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);
414
387
  const incomingKeys = currKeys.filter((k) => !prevKeys.includes(k));
415
388
  const removedKeys = prevKeys.filter((k) => !currKeys.includes(k));
416
- const changedKeys = currKeys.filter((k) => currRoutes[k] !== prevRoutes[k]);
389
+ const changedKeys = currKeys.filter(
390
+ (k) => currScreens[k] !== prevScreens[k]
391
+ );
417
392
  const animatableValues = Object.values(animationValues);
418
393
  for (const incomingKey of incomingKeys) {
419
394
  for (const value of animatableValues) {
@@ -427,9 +402,9 @@ RouteStore.use.subscribeWithSelector(
427
402
  }
428
403
  }
429
404
  for (const changedKey of changedKeys) {
430
- const currentRoute = currRoutes[changedKey];
431
- if (currentRoute) {
432
- triggerAnimation(currentRoute);
405
+ const currentScreen = currScreens[changedKey];
406
+ if (currentScreen) {
407
+ triggerAnimation(currentScreen);
433
408
  }
434
409
  }
435
410
  }
@@ -442,10 +417,11 @@ import {
442
417
  runOnJS as runOnJS2
443
418
  } from "react-native-reanimated";
444
419
 
445
- // src/utils/gesture/create-gesture-activation-criteria.ts
446
- var createGestureActivationCriteria = ({
420
+ // src/utils/gesture/apply-gesture-activation-criteria.ts
421
+ var applyGestureActivationCriteria = ({
447
422
  gestureDirection,
448
- gestureResponseDistance
423
+ gestureResponseDistance,
424
+ panGesture
449
425
  }) => {
450
426
  const directions = Array.isArray(gestureDirection) ? gestureDirection : [gestureDirection];
451
427
  if (directions.includes("bidirectional")) {
@@ -502,7 +478,20 @@ var createGestureActivationCriteria = ({
502
478
  } else {
503
479
  result.failOffsetY = [-toleranceY, toleranceY];
504
480
  }
505
- 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;
506
495
  };
507
496
 
508
497
  // src/utils/gesture/map-gesture-to-progress.ts
@@ -518,10 +507,10 @@ var DEFAULT_GESTURE_RESPONSE_DISTANCE = 50;
518
507
  var buildGestureDetector = ({
519
508
  key,
520
509
  progress,
521
- config,
510
+ screenState,
522
511
  width,
523
512
  height,
524
- goBack
513
+ handleDismiss
525
514
  }) => {
526
515
  const _translateX = animationValues.gestureX[key];
527
516
  const _translateY = animationValues.gestureY[key];
@@ -534,7 +523,7 @@ var buildGestureDetector = ({
534
523
  transitionSpec,
535
524
  gestureVelocityImpact = GESTURE_VELOCITY_IMPACT,
536
525
  gestureResponseDistance = DEFAULT_GESTURE_RESPONSE_DISTANCE
537
- } = config;
526
+ } = screenState;
538
527
  const directions = Array.isArray(gestureDirection) ? gestureDirection : [gestureDirection];
539
528
  const panGesture = Gesture.Pan().enabled(gestureEnabled).onStart(() => {
540
529
  "worklet";
@@ -613,7 +602,7 @@ var buildGestureDetector = ({
613
602
  const spec = shouldDismiss ? transitionSpec?.close : transitionSpec?.open;
614
603
  const onFinish = shouldDismiss ? (isFinished) => {
615
604
  "worklet";
616
- if (isFinished) runOnJS2(goBack)();
605
+ if (isFinished) runOnJS2(handleDismiss)(screenState.id);
617
606
  } : void 0;
618
607
  progress.value = animate(finalProgress, spec, onFinish);
619
608
  _translateX.value = animate(0, spec);
@@ -621,25 +610,12 @@ var buildGestureDetector = ({
621
610
  _normalizedGestureX.value = animate(0, spec);
622
611
  _normalizedGestureY.value = animate(0, spec);
623
612
  });
624
- const criteria = createGestureActivationCriteria({
613
+ applyGestureActivationCriteria({
625
614
  gestureDirection,
626
- gestureResponseDistance
615
+ gestureResponseDistance,
616
+ panGesture
627
617
  });
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);
618
+ return panGesture;
643
619
  };
644
620
 
645
621
  // src/utils/noop-interpolator.ts
@@ -661,25 +637,33 @@ var useAnimationBuilder = () => {
661
637
  const dimensions = useWindowDimensions();
662
638
  const insets = useSafeAreaInsets();
663
639
  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
- })
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
+ )
677
661
  );
678
662
  const panGesture = useMemo(
679
663
  () => buildGestureDetector({
680
664
  key,
681
665
  progress: animationValues.screenProgress[key],
682
- config: currentRoute || {
666
+ screenState: currentScreen || {
683
667
  id: key,
684
668
  name: key,
685
669
  index: 0,
@@ -688,57 +672,50 @@ var useAnimationBuilder = () => {
688
672
  },
689
673
  width: dimensions.width,
690
674
  height: dimensions.height,
691
- goBack: navigation.goBack
675
+ handleDismiss: (screenBeingDismissed) => {
676
+ ScreenStore.handleScreenDismiss(screenBeingDismissed, navigation);
677
+ }
692
678
  }),
693
- [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
+ ]
694
700
  );
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
701
  return useMemo(() => {
702
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,
703
+ current: getAnimationValuesForScreen(key),
704
+ next: actualNextScreen ? getAnimationValuesForScreen(actualNextScreen.id) : void 0,
723
705
  layouts: { screen: dimensions },
724
706
  insets,
725
- closing: currentRoute?.closing || false,
726
- screenStyleInterpolator: nextRoute?.screenStyleInterpolator || currentRoute?.screenStyleInterpolator || noopinterpolator,
707
+ closing: currentScreen?.closing || false,
708
+ screenStyleInterpolator: actualNextScreen?.screenStyleInterpolator || currentScreen?.screenStyleInterpolator || noopinterpolator,
727
709
  gestureDetector: panGesture
728
710
  };
729
711
  }, [
730
712
  key,
731
- currentRoute,
732
- nextRoute,
713
+ currentScreen,
714
+ actualNextScreen,
733
715
  dimensions,
734
716
  insets,
735
717
  panGesture,
736
- progressFallback,
737
- gestureDraggingFallback,
738
- gestureXFallback,
739
- gestureYFallback,
740
- normalizedGestureXFallback,
741
- normalizedGestureYFallback
718
+ getAnimationValuesForScreen
742
719
  ]);
743
720
  };
744
721
  var _useScreenAnimation = () => {
@@ -761,24 +738,36 @@ var createConfig = ({
761
738
  }) => {
762
739
  return {
763
740
  focus: (e) => {
764
- RouteStore.updateRoute(e.target, {
741
+ const parentNavigatorKey = reactNavigation.getParent()?.getState?.()?.key;
742
+ const navigatorKey = reactNavigation.getState().key;
743
+ ScreenStore.updateScreen(e.target, {
765
744
  id: e.target,
766
745
  name: route.name,
767
746
  status: 1,
768
747
  closing: false,
748
+ navigatorKey,
749
+ parentNavigatorKey,
769
750
  ...config
770
751
  });
771
752
  },
772
753
  beforeRemove: (e) => {
754
+ const shouldSkipPreventDefault2 = ScreenStore.shouldSkipPreventDefault(
755
+ e.target,
756
+ reactNavigation.getState()
757
+ );
758
+ if (shouldSkipPreventDefault2) {
759
+ ScreenStore.removeScreen(e.target);
760
+ return;
761
+ }
773
762
  e.preventDefault();
774
763
  const handleFinish = (finished) => {
775
764
  if (!finished) return;
776
765
  if (reactNavigation.canGoBack()) {
777
766
  reactNavigation.dispatch(e.data?.action);
778
- RouteStore.removeRoute(e.target);
767
+ ScreenStore.removeScreen(e.target);
779
768
  }
780
769
  };
781
- RouteStore.updateRoute(e.target, {
770
+ ScreenStore.updateScreen(e.target, {
782
771
  status: 0,
783
772
  closing: true,
784
773
  onAnimationFinish: handleFinish