react-native-bottom-sheet-stack 1.6.1 → 1.7.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.
Files changed (135) hide show
  1. package/README.md +9 -7
  2. package/lib/commonjs/BottomSheet.context.js +1 -2
  3. package/lib/commonjs/BottomSheet.context.js.map +1 -1
  4. package/lib/commonjs/BottomSheetBackdrop.js +23 -32
  5. package/lib/commonjs/BottomSheetBackdrop.js.map +1 -1
  6. package/lib/commonjs/BottomSheetHost.js +17 -254
  7. package/lib/commonjs/BottomSheetHost.js.map +1 -1
  8. package/lib/commonjs/BottomSheetManaged.js +87 -54
  9. package/lib/commonjs/BottomSheetManaged.js.map +1 -1
  10. package/lib/commonjs/BottomSheetPersistent.js +113 -0
  11. package/lib/commonjs/BottomSheetPersistent.js.map +1 -0
  12. package/lib/commonjs/BottomSheetPortal.js +4 -3
  13. package/lib/commonjs/BottomSheetPortal.js.map +1 -1
  14. package/lib/commonjs/BottomSheetRef.context.js +17 -0
  15. package/lib/commonjs/BottomSheetRef.context.js.map +1 -0
  16. package/lib/commonjs/QueueItem.js +167 -0
  17. package/lib/commonjs/QueueItem.js.map +1 -0
  18. package/lib/commonjs/animatedRegistry.js +9 -0
  19. package/lib/commonjs/animatedRegistry.js.map +1 -1
  20. package/lib/commonjs/bottomSheet.store.js +11 -133
  21. package/lib/commonjs/bottomSheet.store.js.map +1 -1
  22. package/lib/commonjs/bottomSheetCoordinator.js +9 -10
  23. package/lib/commonjs/bottomSheetCoordinator.js.map +1 -1
  24. package/lib/commonjs/index.js +28 -0
  25. package/lib/commonjs/index.js.map +1 -1
  26. package/lib/commonjs/portalSessionRegistry.js +32 -0
  27. package/lib/commonjs/portalSessionRegistry.js.map +1 -0
  28. package/lib/commonjs/refsMap.js +9 -0
  29. package/lib/commonjs/refsMap.js.map +1 -1
  30. package/lib/commonjs/store/helpers.js +59 -0
  31. package/lib/commonjs/store/helpers.js.map +1 -0
  32. package/lib/commonjs/store/hooks.js +176 -0
  33. package/lib/commonjs/store/hooks.js.map +1 -0
  34. package/lib/commonjs/store/index.js +52 -0
  35. package/lib/commonjs/store/index.js.map +1 -0
  36. package/lib/commonjs/store/store.js +140 -0
  37. package/lib/commonjs/store/store.js.map +1 -0
  38. package/lib/commonjs/store/types.js +6 -0
  39. package/lib/commonjs/store/types.js.map +1 -0
  40. package/lib/commonjs/useBottomSheetContext.js +24 -42
  41. package/lib/commonjs/useBottomSheetContext.js.map +1 -1
  42. package/lib/commonjs/useBottomSheetControl.js +8 -14
  43. package/lib/commonjs/useBottomSheetControl.js.map +1 -1
  44. package/lib/commonjs/useBottomSheetManager.js +3 -13
  45. package/lib/commonjs/useBottomSheetManager.js.map +1 -1
  46. package/lib/commonjs/useBottomSheetStatus.js +9 -17
  47. package/lib/commonjs/useBottomSheetStatus.js.map +1 -1
  48. package/lib/commonjs/useEvent.js +39 -0
  49. package/lib/commonjs/useEvent.js.map +1 -0
  50. package/lib/commonjs/useScaleAnimation.js +38 -24
  51. package/lib/commonjs/useScaleAnimation.js.map +1 -1
  52. package/lib/commonjs/useSheetRenderData.js +62 -0
  53. package/lib/commonjs/useSheetRenderData.js.map +1 -0
  54. package/lib/typescript/example/src/App.d.ts.map +1 -1
  55. package/lib/typescript/example/src/screens/HomeScreen.d.ts.map +1 -1
  56. package/lib/typescript/example/src/sheets/NavigationSheets.d.ts.map +1 -1
  57. package/lib/typescript/example/src/sheets/ScannerNestedSheets.d.ts +4 -0
  58. package/lib/typescript/example/src/sheets/ScannerNestedSheets.d.ts.map +1 -0
  59. package/lib/typescript/example/src/sheets/ScannerSheet.d.ts +3 -0
  60. package/lib/typescript/example/src/sheets/ScannerSheet.d.ts.map +1 -0
  61. package/lib/typescript/example/src/sheets/index.d.ts +1 -0
  62. package/lib/typescript/example/src/sheets/index.d.ts.map +1 -1
  63. package/lib/typescript/src/BottomSheet.context.d.ts.map +1 -1
  64. package/lib/typescript/src/BottomSheetBackdrop.d.ts +0 -5
  65. package/lib/typescript/src/BottomSheetBackdrop.d.ts.map +1 -1
  66. package/lib/typescript/src/BottomSheetHost.d.ts +1 -3
  67. package/lib/typescript/src/BottomSheetHost.d.ts.map +1 -1
  68. package/lib/typescript/src/BottomSheetManaged.d.ts.map +1 -1
  69. package/lib/typescript/src/BottomSheetPersistent.d.ts +9 -0
  70. package/lib/typescript/src/BottomSheetPersistent.d.ts.map +1 -0
  71. package/lib/typescript/src/BottomSheetPortal.d.ts.map +1 -1
  72. package/lib/typescript/src/BottomSheetRef.context.d.ts +11 -0
  73. package/lib/typescript/src/BottomSheetRef.context.d.ts.map +1 -0
  74. package/lib/typescript/src/QueueItem.d.ts +8 -0
  75. package/lib/typescript/src/QueueItem.d.ts.map +1 -0
  76. package/lib/typescript/src/animatedRegistry.d.ts +5 -0
  77. package/lib/typescript/src/animatedRegistry.d.ts.map +1 -1
  78. package/lib/typescript/src/bottomSheet.store.d.ts +1 -37
  79. package/lib/typescript/src/bottomSheet.store.d.ts.map +1 -1
  80. package/lib/typescript/src/bottomSheetCoordinator.d.ts +2 -1
  81. package/lib/typescript/src/bottomSheetCoordinator.d.ts.map +1 -1
  82. package/lib/typescript/src/index.d.ts +4 -0
  83. package/lib/typescript/src/index.d.ts.map +1 -1
  84. package/lib/typescript/src/portalSessionRegistry.d.ts +8 -0
  85. package/lib/typescript/src/portalSessionRegistry.d.ts.map +1 -0
  86. package/lib/typescript/src/refsMap.d.ts +5 -0
  87. package/lib/typescript/src/refsMap.d.ts.map +1 -1
  88. package/lib/typescript/src/store/helpers.d.ts +11 -0
  89. package/lib/typescript/src/store/helpers.d.ts.map +1 -0
  90. package/lib/typescript/src/store/hooks.d.ts +16 -0
  91. package/lib/typescript/src/store/hooks.d.ts.map +1 -0
  92. package/lib/typescript/src/store/index.d.ts +5 -0
  93. package/lib/typescript/src/store/index.d.ts.map +1 -0
  94. package/lib/typescript/src/store/store.d.ts +11 -0
  95. package/lib/typescript/src/store/store.d.ts.map +1 -0
  96. package/lib/typescript/src/store/types.d.ts +37 -0
  97. package/lib/typescript/src/store/types.d.ts.map +1 -0
  98. package/lib/typescript/src/useBottomSheetContext.d.ts.map +1 -1
  99. package/lib/typescript/src/useBottomSheetControl.d.ts.map +1 -1
  100. package/lib/typescript/src/useBottomSheetManager.d.ts.map +1 -1
  101. package/lib/typescript/src/useBottomSheetStatus.d.ts +1 -2
  102. package/lib/typescript/src/useBottomSheetStatus.d.ts.map +1 -1
  103. package/lib/typescript/src/useEvent.d.ts +4 -0
  104. package/lib/typescript/src/useEvent.d.ts.map +1 -0
  105. package/lib/typescript/src/useScaleAnimation.d.ts +1 -12
  106. package/lib/typescript/src/useScaleAnimation.d.ts.map +1 -1
  107. package/lib/typescript/src/useSheetRenderData.d.ts +17 -0
  108. package/lib/typescript/src/useSheetRenderData.d.ts.map +1 -0
  109. package/package.json +1 -1
  110. package/src/BottomSheet.context.ts +1 -3
  111. package/src/BottomSheetBackdrop.tsx +10 -19
  112. package/src/BottomSheetHost.tsx +13 -99
  113. package/src/BottomSheetManaged.tsx +24 -2
  114. package/src/BottomSheetPersistent.tsx +57 -0
  115. package/src/BottomSheetPortal.tsx +5 -7
  116. package/src/BottomSheetRef.context.ts +14 -0
  117. package/src/QueueItem.tsx +83 -0
  118. package/src/animatedRegistry.ts +8 -0
  119. package/src/bottomSheet.store.ts +1 -173
  120. package/src/bottomSheetCoordinator.ts +10 -8
  121. package/src/index.tsx +6 -0
  122. package/src/portalSessionRegistry.ts +25 -0
  123. package/src/refsMap.ts +8 -0
  124. package/src/store/helpers.ts +65 -0
  125. package/src/store/hooks.ts +50 -0
  126. package/src/store/index.ts +4 -0
  127. package/src/store/store.ts +168 -0
  128. package/src/store/types.ts +42 -0
  129. package/src/useBottomSheetContext.ts +6 -15
  130. package/src/useBottomSheetControl.ts +16 -7
  131. package/src/useBottomSheetManager.tsx +9 -10
  132. package/src/useBottomSheetStatus.ts +4 -14
  133. package/src/useEvent.ts +17 -0
  134. package/src/useScaleAnimation.ts +46 -30
  135. package/src/useSheetRenderData.ts +74 -0
@@ -5,30 +5,17 @@ import Animated, {
5
5
  useAnimatedStyle,
6
6
  } from 'react-native-reanimated';
7
7
  import { getAnimatedIndex } from './animatedRegistry';
8
- import { useBottomSheetStore } from './bottomSheet.store';
9
8
 
10
9
  interface BottomSheetBackdropProps {
11
10
  sheetId: string;
12
11
  onPress?: () => void;
13
12
  }
14
13
 
15
- const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
16
-
17
- /**
18
- * Custom backdrop component rendered separately from the scaled sheet content.
19
- * This ensures the backdrop doesn't scale with the sheet.
20
- * Opacity is interpolated from the bottom sheet's animatedIndex for smooth animation.
21
- */
22
14
  export function BottomSheetBackdrop({
23
15
  sheetId,
24
16
  onPress,
25
17
  }: BottomSheetBackdropProps) {
26
- const status = useBottomSheetStore(
27
- (state) => state.sheetsById[sheetId]?.status
28
- );
29
-
30
18
  const animatedIndex = getAnimatedIndex(sheetId);
31
- const isVisible = status === 'opening' || status === 'open';
32
19
 
33
20
  const animatedStyle = useAnimatedStyle(() => {
34
21
  const opacity = interpolate(
@@ -42,17 +29,21 @@ export function BottomSheetBackdrop({
42
29
  });
43
30
 
44
31
  return (
45
- <AnimatedPressable
46
- style={[styles.backdrop, animatedStyle]}
47
- onPress={onPress}
48
- pointerEvents={isVisible ? 'auto' : 'none'}
49
- />
32
+ <Pressable
33
+ style={StyleSheet.absoluteFillObject}
34
+ onPress={() => {
35
+ onPress?.();
36
+ }}
37
+ >
38
+ <Animated.View
39
+ style={[StyleSheet.absoluteFillObject, animatedStyle, styles.backdrop]}
40
+ />
41
+ </Pressable>
50
42
  );
51
43
  }
52
44
 
53
45
  const styles = StyleSheet.create({
54
46
  backdrop: {
55
- ...StyleSheet.absoluteFillObject,
56
47
  backgroundColor: 'rgba(0, 0, 0, 0.5)',
57
48
  },
58
49
  });
@@ -1,35 +1,14 @@
1
1
  import { useEffect } from 'react';
2
- import { StyleSheet, View } from 'react-native';
3
- import Animated from 'react-native-reanimated';
4
- import { useSafeAreaFrame } from 'react-native-safe-area-context';
5
- import { PortalHost } from 'react-native-teleport';
6
2
 
7
- import { shallow } from 'zustand/shallow';
8
- import { cleanupAnimatedIndex } from './animatedRegistry';
9
- import { BottomSheetContext } from './BottomSheet.context';
10
- import { useBottomSheetStore } from './bottomSheet.store';
11
- import { BottomSheetBackdrop } from './BottomSheetBackdrop';
3
+ import { useClearGroup } from './bottomSheet.store';
12
4
  import { initBottomSheetCoordinator } from './bottomSheetCoordinator';
13
5
  import { useBottomSheetManagerContext } from './BottomSheetManager.provider';
14
- import { cleanupSheetRef } from './refsMap';
15
- import { useScaleAnimatedStyle } from './useScaleAnimation';
16
-
17
- function PortalHostWrapper({
18
- id,
19
- width,
20
- height,
21
- }: {
22
- id: string;
23
- width: number;
24
- height: number;
25
- }) {
26
- return <PortalHost name={`bottomsheet-${id}`} style={{ width, height }} />;
27
- }
28
-
29
- function BottomSheetHostComp() {
30
- const queueIds = useQueueIds();
31
- const clearGroup = useBottomSheetStore((store) => store.clearGroup);
6
+ import { QueueItem } from './QueueItem';
7
+ import { useSheetRenderData } from './useSheetRenderData';
32
8
 
9
+ export function BottomSheetHost() {
10
+ const sheetRenderData = useSheetRenderData();
11
+ const clearGroup = useClearGroup();
33
12
  const { groupId } = useBottomSheetManagerContext();
34
13
 
35
14
  useEffect(() => {
@@ -47,79 +26,14 @@ function BottomSheetHostComp() {
47
26
 
48
27
  return (
49
28
  <>
50
- {queueIds.map((id, index) => (
51
- <QueueItem key={id} id={id} stackIndex={index} />
29
+ {sheetRenderData.map(({ id, stackIndex, isActive }) => (
30
+ <QueueItem
31
+ key={id}
32
+ id={id}
33
+ stackIndex={stackIndex}
34
+ isActive={isActive}
35
+ />
52
36
  ))}
53
37
  </>
54
38
  );
55
39
  }
56
-
57
- function QueueItem({ id, stackIndex }: { id: string; stackIndex: number }) {
58
- const { content, usePortal, startClosing } = useBottomSheetStore(
59
- (state) => ({
60
- content: state.sheetsById[id]?.content,
61
- usePortal: state.sheetsById[id]?.usePortal,
62
- startClosing: state.startClosing,
63
- }),
64
- shallow
65
- );
66
-
67
- const { width, height } = useSafeAreaFrame();
68
- const value = { id };
69
-
70
- const scaleStyle = useScaleAnimatedStyle({ id });
71
-
72
- useEffect(() => {
73
- return () => {
74
- cleanupSheetRef(id);
75
- cleanupAnimatedIndex(id);
76
- };
77
- }, [id]);
78
-
79
- const backdropZIndex = stackIndex * 2;
80
- const contentZIndex = stackIndex * 2 + 1;
81
-
82
- return (
83
- <BottomSheetContext.Provider value={value}>
84
- <View style={[StyleSheet.absoluteFillObject, { zIndex: backdropZIndex }]}>
85
- <BottomSheetBackdrop sheetId={id} onPress={() => startClosing(id)} />
86
- </View>
87
-
88
- {/* Sheet content - rendered with scaling */}
89
- <Animated.View
90
- style={[
91
- StyleSheet.absoluteFillObject,
92
- styles.container,
93
- { width, height, zIndex: contentZIndex },
94
- scaleStyle,
95
- ]}
96
- >
97
- {usePortal ? (
98
- <PortalHostWrapper id={id} width={width} height={height} />
99
- ) : (
100
- content
101
- )}
102
- </Animated.View>
103
- </BottomSheetContext.Provider>
104
- );
105
- }
106
-
107
- const useQueueIds = () => {
108
- const { groupId } = useBottomSheetManagerContext();
109
-
110
- return useBottomSheetStore(
111
- (state) =>
112
- state.stackOrder.filter(
113
- (sheetId) => state.sheetsById[sheetId]?.groupId === groupId
114
- ),
115
- shallow
116
- );
117
- };
118
-
119
- export const BottomSheetHost = BottomSheetHostComp;
120
-
121
- const styles = StyleSheet.create({
122
- container: {
123
- pointerEvents: 'box-none',
124
- },
125
- });
@@ -6,7 +6,9 @@ import { type BottomSheetMethods } from '@gorhom/bottom-sheet/lib/typescript/typ
6
6
  import React from 'react';
7
7
 
8
8
  import { getAnimatedIndex } from './animatedRegistry';
9
+ import { useSheetStatus } from './bottomSheet.store';
9
10
  import { createSheetEventHandlers } from './bottomSheetCoordinator';
11
+ import { useBottomSheetRefContext } from './BottomSheetRef.context';
10
12
  import { useBottomSheetContext } from './useBottomSheetContext';
11
13
 
12
14
  export interface BottomSheetRef extends BottomSheetMethods {}
@@ -23,18 +25,27 @@ export const BottomSheetManaged = React.forwardRef<
23
25
  {
24
26
  children,
25
27
  onAnimate,
28
+ onChange,
26
29
  onClose,
27
30
  enablePanDownToClose = true,
28
31
  backdropComponent = nullBackdrop,
29
32
  animatedIndex: externalAnimatedIndex,
33
+ index: externalIndex,
30
34
  ...props
31
35
  },
32
- ref
36
+ forwardedRef
33
37
  ) => {
34
38
  const { id } = useBottomSheetContext();
39
+ const contextRef = useBottomSheetRefContext();
40
+ const ref = contextRef ?? forwardedRef;
41
+
42
+ const status = useSheetStatus(id);
43
+ const shouldBeClosed = status === 'hidden' || status === 'closing';
44
+ const index = externalIndex ?? (shouldBeClosed ? -1 : 0);
35
45
 
36
46
  const animatedIndex = externalAnimatedIndex ?? getAnimatedIndex(id);
37
- const { handleAnimate, handleClose } = createSheetEventHandlers(id);
47
+ const { handleAnimate, handleChange, handleClose } =
48
+ createSheetEventHandlers(id);
38
49
 
39
50
  const wrappedOnAnimate: BottomSheetProps['onAnimate'] = (
40
51
  fromIndex: number,
@@ -46,6 +57,15 @@ export const BottomSheetManaged = React.forwardRef<
46
57
  onAnimate?.(fromIndex, toIndex, fromPosition, toPosition);
47
58
  };
48
59
 
60
+ const wrappedOnChange: BottomSheetProps['onChange'] = (
61
+ index: number,
62
+ position: number,
63
+ type
64
+ ) => {
65
+ handleChange(index);
66
+ onChange?.(index, position, type);
67
+ };
68
+
49
69
  const wrappedOnClose = () => {
50
70
  onClose?.();
51
71
  handleClose();
@@ -62,7 +82,9 @@ export const BottomSheetManaged = React.forwardRef<
62
82
  animationConfigs={config}
63
83
  ref={ref}
64
84
  {...props}
85
+ index={index}
65
86
  animatedIndex={animatedIndex}
87
+ onChange={wrappedOnChange}
66
88
  onClose={wrappedOnClose}
67
89
  onAnimate={wrappedOnAnimate}
68
90
  backdropComponent={backdropComponent}
@@ -0,0 +1,57 @@
1
+ import type { BottomSheetMethods } from '@gorhom/bottom-sheet/lib/typescript/types';
2
+ import React, { useEffect, useRef } from 'react';
3
+
4
+ import { useMount, useSheetExists, useUnmount } from './bottomSheet.store';
5
+ import { useMaybeBottomSheetManagerContext } from './BottomSheetManager.provider';
6
+ import { BottomSheetPortal } from './BottomSheetPortal';
7
+ import { BottomSheetRefContext } from './BottomSheetRef.context';
8
+ import type { BottomSheetPortalId } from './portal.types';
9
+ import { useEvent } from './useEvent';
10
+ import { setSheetRef } from './refsMap';
11
+
12
+ interface BottomSheetPersistentProps {
13
+ id: BottomSheetPortalId;
14
+ children: React.ReactElement;
15
+ }
16
+
17
+ export function BottomSheetPersistent({
18
+ id,
19
+ children,
20
+ }: BottomSheetPersistentProps) {
21
+ const bottomSheetManagerContext = useMaybeBottomSheetManagerContext();
22
+ const mount = useMount();
23
+ const unmount = useUnmount();
24
+ const sheetExists = useSheetExists(id);
25
+
26
+ const sheetRef = useRef<BottomSheetMethods>(null);
27
+ const groupId = bottomSheetManagerContext?.groupId || 'default';
28
+
29
+ const mountSheet = useEvent(() => {
30
+ mount({ id, groupId, content: null, usePortal: true, keepMounted: true });
31
+ });
32
+
33
+ useEffect(() => {
34
+ if (!sheetExists) {
35
+ setSheetRef(id, sheetRef);
36
+ mountSheet();
37
+ }
38
+ }, [id, sheetExists, mountSheet]);
39
+
40
+ useEffect(() => {
41
+ return () => {
42
+ unmount(id);
43
+ };
44
+ }, [id, unmount]);
45
+
46
+ if (!sheetExists) {
47
+ return null;
48
+ }
49
+
50
+ return (
51
+ <BottomSheetPortal id={id}>
52
+ <BottomSheetRefContext.Provider value={sheetRef}>
53
+ {children}
54
+ </BottomSheetRefContext.Provider>
55
+ </BottomSheetPortal>
56
+ );
57
+ }
@@ -4,7 +4,7 @@ import React from 'react';
4
4
  import { Portal } from 'react-native-teleport';
5
5
 
6
6
  import { BottomSheetContext } from './BottomSheet.context';
7
- import { useBottomSheetStore } from './bottomSheet.store';
7
+ import { useSheetUsePortal, useSheetPortalSession } from './bottomSheet.store';
8
8
  import type { BottomSheetPortalId } from './portal.types';
9
9
  import { getSheetRef } from './refsMap';
10
10
 
@@ -14,22 +14,20 @@ interface BottomSheetPortalProps {
14
14
  }
15
15
 
16
16
  export function BottomSheetPortal({ id, children }: BottomSheetPortalProps) {
17
- const usePortal = useBottomSheetStore(
18
- (state) => state.sheetsById[id]?.usePortal
19
- );
17
+ const usePortal = useSheetUsePortal(id);
18
+ const portalSession = useSheetPortalSession(id);
20
19
 
21
- if (!usePortal) {
20
+ if (!usePortal || portalSession === undefined) {
22
21
  return null;
23
22
  }
24
23
 
25
24
  const ref = getSheetRef(id);
26
-
27
25
  const childWithRef = React.cloneElement(children, {
28
26
  ref,
29
27
  } as { ref: typeof ref });
30
28
 
31
29
  return (
32
- <Portal hostName={`bottomsheet-${id}`}>
30
+ <Portal hostName={`bottomsheet-${id}-${portalSession}`}>
33
31
  <BottomSheetContext.Provider value={{ id }}>
34
32
  {childWithRef}
35
33
  </BottomSheetContext.Provider>
@@ -0,0 +1,14 @@
1
+ import { createContext, useContext, type RefObject } from 'react';
2
+ import type { BottomSheetMethods } from '@gorhom/bottom-sheet/lib/typescript/types';
3
+
4
+ type SheetRef = RefObject<BottomSheetMethods | null>;
5
+
6
+ /**
7
+ * Context for passing sheet ref from BottomSheetPersistent/BottomSheetPortal
8
+ * to BottomSheetManaged. This allows automatic ref binding without user intervention.
9
+ */
10
+ export const BottomSheetRefContext = createContext<SheetRef | null>(null);
11
+
12
+ export function useBottomSheetRefContext(): SheetRef | null {
13
+ return useContext(BottomSheetRefContext);
14
+ }
@@ -0,0 +1,83 @@
1
+ import { useEffect } from 'react';
2
+ import { StyleSheet, View } from 'react-native';
3
+ import Animated from 'react-native-reanimated';
4
+ import { useSafeAreaFrame } from 'react-native-safe-area-context';
5
+ import { PortalHost } from 'react-native-teleport';
6
+
7
+ import { cleanupAnimatedIndex } from './animatedRegistry';
8
+ import { BottomSheetContext } from './BottomSheet.context';
9
+ import {
10
+ useSheetContent,
11
+ useSheetUsePortal,
12
+ useSheetKeepMounted,
13
+ useSheetPortalSession,
14
+ useStartClosing,
15
+ } from './bottomSheet.store';
16
+ import { BottomSheetBackdrop } from './BottomSheetBackdrop';
17
+ import { cleanupSheetRef } from './refsMap';
18
+ import { useScaleAnimatedStyle } from './useScaleAnimation';
19
+
20
+ interface QueueItemProps {
21
+ id: string;
22
+ stackIndex: number;
23
+ isActive: boolean;
24
+ }
25
+
26
+ export function QueueItem({ id, stackIndex, isActive }: QueueItemProps) {
27
+ const content = useSheetContent(id);
28
+ const usePortal = useSheetUsePortal(id);
29
+ const keepMounted = useSheetKeepMounted(id);
30
+ const portalSession = useSheetPortalSession(id);
31
+ const startClosing = useStartClosing();
32
+
33
+ const { width, height } = useSafeAreaFrame();
34
+ const scaleStyle = useScaleAnimatedStyle({ id });
35
+
36
+ useEffect(() => {
37
+ return () => {
38
+ cleanupSheetRef(id);
39
+ cleanupAnimatedIndex(id);
40
+ };
41
+ }, [id, keepMounted]);
42
+
43
+ const backdropZIndex = stackIndex * 2;
44
+ const contentZIndex = stackIndex * 2 + 1;
45
+
46
+ return (
47
+ <>
48
+ {isActive && (
49
+ <View
50
+ style={[StyleSheet.absoluteFillObject, { zIndex: backdropZIndex }]}
51
+ pointerEvents="box-none"
52
+ >
53
+ <BottomSheetBackdrop sheetId={id} onPress={() => startClosing(id)} />
54
+ </View>
55
+ )}
56
+
57
+ <Animated.View
58
+ pointerEvents="box-none"
59
+ style={[
60
+ StyleSheet.absoluteFillObject,
61
+ styles.container,
62
+ { width, height, zIndex: contentZIndex },
63
+ scaleStyle,
64
+ ]}
65
+ >
66
+ {usePortal ? (
67
+ <PortalHost
68
+ name={`bottomsheet-${id}-${portalSession}`}
69
+ style={{ width, height }}
70
+ />
71
+ ) : (
72
+ <BottomSheetContext.Provider value={{ id }}>
73
+ {content}
74
+ </BottomSheetContext.Provider>
75
+ )}
76
+ </Animated.View>
77
+ </>
78
+ );
79
+ }
80
+
81
+ const styles = StyleSheet.create({
82
+ container: {},
83
+ });
@@ -20,3 +20,11 @@ export function getAnimatedIndex(sheetId: string): SharedValue<number> {
20
20
  export function cleanupAnimatedIndex(sheetId: string): void {
21
21
  animatedIndexRegistry.delete(sheetId);
22
22
  }
23
+
24
+ /**
25
+ * Reset all animated indexes. Useful for testing.
26
+ * @internal
27
+ */
28
+ export function __resetAnimatedIndexes(): void {
29
+ animatedIndexRegistry.clear();
30
+ }
@@ -1,173 +1 @@
1
- import { type ReactNode } from 'react';
2
- import { subscribeWithSelector } from 'zustand/middleware';
3
- import { createWithEqualityFn as create } from 'zustand/traditional';
4
-
5
- export type BottomSheetStatus = 'opening' | 'open' | 'closing' | 'hidden';
6
- export type OpenMode = 'push' | 'switch' | 'replace';
7
-
8
- export interface BottomSheetState {
9
- groupId: string;
10
- id: string;
11
- content: ReactNode;
12
- status: BottomSheetStatus;
13
- scaleBackground?: boolean;
14
- usePortal?: boolean;
15
- params?: Record<string, unknown>;
16
- }
17
-
18
- type TriggerState = Omit<BottomSheetState, 'status'>;
19
-
20
- interface BottomSheetStoreState {
21
- sheetsById: Record<string, BottomSheetState>;
22
- stackOrder: string[];
23
- }
24
-
25
- interface BottomSheetStoreActions {
26
- open(sheet: TriggerState, mode?: OpenMode): void;
27
- markOpen(id: string): void;
28
- startClosing(id: string): void;
29
- finishClosing(id: string): void;
30
- updateParams(id: string, params: Record<string, unknown> | undefined): void;
31
- clearGroup(groupId: string): void;
32
- clearAll(): void;
33
- }
34
-
35
- export type BottomSheetStore = BottomSheetStoreState & BottomSheetStoreActions;
36
-
37
- export const useBottomSheetStore = create(
38
- subscribeWithSelector<BottomSheetStore>((set) => ({
39
- sheetsById: {},
40
- stackOrder: [],
41
-
42
- open: (sheet, mode = 'push') =>
43
- set((state) => {
44
- if (state.sheetsById[sheet.id]) {
45
- return state;
46
- }
47
-
48
- const newSheetsById = { ...state.sheetsById };
49
- const topId = state.stackOrder[state.stackOrder.length - 1];
50
-
51
- if (mode === 'switch' && topId && newSheetsById[topId]) {
52
- newSheetsById[topId] = {
53
- ...newSheetsById[topId],
54
- status: 'hidden',
55
- };
56
- }
57
-
58
- if (mode === 'replace' && topId && newSheetsById[topId]) {
59
- newSheetsById[topId] = {
60
- ...newSheetsById[topId],
61
- status: 'closing',
62
- };
63
- }
64
-
65
- newSheetsById[sheet.id] = { ...sheet, status: 'opening' };
66
-
67
- return {
68
- sheetsById: newSheetsById,
69
- stackOrder: [...state.stackOrder, sheet.id],
70
- };
71
- }),
72
-
73
- markOpen: (id) =>
74
- set((state) => {
75
- const sheet = state.sheetsById[id];
76
- if (!sheet) {
77
- return state;
78
- }
79
- return {
80
- sheetsById: {
81
- ...state.sheetsById,
82
- [id]: { ...sheet, status: 'open' },
83
- },
84
- };
85
- }),
86
-
87
- startClosing: (id) =>
88
- set((state) => {
89
- const sheet = state.sheetsById[id];
90
- if (!sheet || sheet.status === 'hidden') {
91
- return state;
92
- }
93
-
94
- const newSheetsById = { ...state.sheetsById };
95
- newSheetsById[id] = { ...sheet, status: 'closing' };
96
-
97
- const index = state.stackOrder.indexOf(id);
98
- const belowId = state.stackOrder[index - 1];
99
- const belowSheet = belowId ? newSheetsById[belowId] : undefined;
100
-
101
- if (belowId && belowSheet && belowSheet.status === 'hidden') {
102
- newSheetsById[belowId] = { ...belowSheet, status: 'opening' };
103
- }
104
-
105
- return { sheetsById: newSheetsById };
106
- }),
107
-
108
- finishClosing: (id) =>
109
- set((state) => {
110
- if (!state.sheetsById[id]) {
111
- return state;
112
- }
113
-
114
- const newSheetsById = { ...state.sheetsById };
115
- delete newSheetsById[id];
116
-
117
- const newStackOrder = state.stackOrder.filter(
118
- (sheetId) => sheetId !== id
119
- );
120
-
121
- const topId = newStackOrder[newStackOrder.length - 1];
122
- const topSheet = topId ? newSheetsById[topId] : undefined;
123
-
124
- if (topId && topSheet && topSheet.status === 'hidden') {
125
- newSheetsById[topId] = { ...topSheet, status: 'opening' };
126
- }
127
-
128
- return {
129
- sheetsById: newSheetsById,
130
- stackOrder: newStackOrder,
131
- };
132
- }),
133
-
134
- updateParams: (id, params) =>
135
- set((state) => {
136
- const sheet = state.sheetsById[id];
137
- if (!sheet) {
138
- return state;
139
- }
140
- return {
141
- sheetsById: {
142
- ...state.sheetsById,
143
- [id]: { ...sheet, params },
144
- },
145
- };
146
- }),
147
-
148
- clearGroup: (groupId) =>
149
- set((state) => {
150
- const idsToRemove = new Set(
151
- state.stackOrder.filter(
152
- (id) => state.sheetsById[id]?.groupId === groupId
153
- )
154
- );
155
-
156
- if (idsToRemove.size === 0) {
157
- return state;
158
- }
159
-
160
- const newSheetsById = { ...state.sheetsById };
161
- idsToRemove.forEach((id) => {
162
- delete newSheetsById[id];
163
- });
164
-
165
- return {
166
- sheetsById: newSheetsById,
167
- stackOrder: state.stackOrder.filter((id) => !idsToRemove.has(id)),
168
- };
169
- }),
170
-
171
- clearAll: () => set(() => ({ sheetsById: {}, stackOrder: [] })),
172
- }))
173
- );
1
+ export * from './store';
@@ -43,19 +43,20 @@ export function createSheetEventHandlers(sheetId: string) {
43
43
  const { startClosing, finishClosing, markOpen } =
44
44
  useBottomSheetStore.getState();
45
45
 
46
- const handleAnimate = (fromIndex: number, toIndex: number) => {
46
+ const handleAnimate = (_fromIndex: number, toIndex: number) => {
47
47
  const state = useBottomSheetStore.getState();
48
48
  const currentStatus = state.sheetsById[sheetId]?.status;
49
49
 
50
- // Sheet is closing (animating to -1)
51
- if (toIndex === -1) {
52
- if (currentStatus === 'open' || currentStatus === 'opening') {
53
- startClosing(sheetId);
54
- }
50
+ if (toIndex === -1 && currentStatus === 'open') {
51
+ startClosing(sheetId);
55
52
  }
53
+ };
54
+
55
+ const handleChange = (index: number) => {
56
+ const state = useBottomSheetStore.getState();
57
+ const currentStatus = state.sheetsById[sheetId]?.status;
56
58
 
57
- // Sheet finished opening (animating from -1 to visible)
58
- if (fromIndex === -1 && toIndex >= 0) {
59
+ if (index >= 0 && currentStatus === 'opening') {
59
60
  markOpen(sheetId);
60
61
  }
61
62
  };
@@ -71,6 +72,7 @@ export function createSheetEventHandlers(sheetId: string) {
71
72
 
72
73
  return {
73
74
  handleAnimate,
75
+ handleChange,
74
76
  handleClose,
75
77
  };
76
78
  }
package/src/index.tsx CHANGED
@@ -4,6 +4,7 @@ export { BottomSheetHost } from './BottomSheetHost';
4
4
  export { BottomSheetScaleView } from './BottomSheetScaleView';
5
5
  export { BottomSheetManaged, type BottomSheetRef } from './BottomSheetManaged';
6
6
  export { BottomSheetPortal } from './BottomSheetPortal';
7
+ export { BottomSheetPersistent } from './BottomSheetPersistent';
7
8
 
8
9
  // Hooks
9
10
  export { useBottomSheetManager } from './useBottomSheetManager';
@@ -33,3 +34,8 @@ export type {
33
34
  BottomSheetPortalId,
34
35
  BottomSheetPortalParams,
35
36
  } from './portal.types';
37
+
38
+ // Testing utilities (internal use)
39
+ export { __resetSheetRefs } from './refsMap';
40
+ export { __resetAnimatedIndexes } from './animatedRegistry';
41
+ export { __resetPortalSessions } from './portalSessionRegistry';