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.
- package/README.md +9 -7
- package/lib/commonjs/BottomSheet.context.js +1 -2
- package/lib/commonjs/BottomSheet.context.js.map +1 -1
- package/lib/commonjs/BottomSheetBackdrop.js +23 -32
- package/lib/commonjs/BottomSheetBackdrop.js.map +1 -1
- package/lib/commonjs/BottomSheetHost.js +17 -254
- package/lib/commonjs/BottomSheetHost.js.map +1 -1
- package/lib/commonjs/BottomSheetManaged.js +87 -54
- package/lib/commonjs/BottomSheetManaged.js.map +1 -1
- package/lib/commonjs/BottomSheetPersistent.js +113 -0
- package/lib/commonjs/BottomSheetPersistent.js.map +1 -0
- package/lib/commonjs/BottomSheetPortal.js +4 -3
- package/lib/commonjs/BottomSheetPortal.js.map +1 -1
- package/lib/commonjs/BottomSheetRef.context.js +17 -0
- package/lib/commonjs/BottomSheetRef.context.js.map +1 -0
- package/lib/commonjs/QueueItem.js +167 -0
- package/lib/commonjs/QueueItem.js.map +1 -0
- package/lib/commonjs/animatedRegistry.js +9 -0
- package/lib/commonjs/animatedRegistry.js.map +1 -1
- package/lib/commonjs/bottomSheet.store.js +11 -133
- package/lib/commonjs/bottomSheet.store.js.map +1 -1
- package/lib/commonjs/bottomSheetCoordinator.js +9 -10
- package/lib/commonjs/bottomSheetCoordinator.js.map +1 -1
- package/lib/commonjs/index.js +28 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/portalSessionRegistry.js +32 -0
- package/lib/commonjs/portalSessionRegistry.js.map +1 -0
- package/lib/commonjs/refsMap.js +9 -0
- package/lib/commonjs/refsMap.js.map +1 -1
- package/lib/commonjs/store/helpers.js +59 -0
- package/lib/commonjs/store/helpers.js.map +1 -0
- package/lib/commonjs/store/hooks.js +176 -0
- package/lib/commonjs/store/hooks.js.map +1 -0
- package/lib/commonjs/store/index.js +52 -0
- package/lib/commonjs/store/index.js.map +1 -0
- package/lib/commonjs/store/store.js +140 -0
- package/lib/commonjs/store/store.js.map +1 -0
- package/lib/commonjs/store/types.js +6 -0
- package/lib/commonjs/store/types.js.map +1 -0
- package/lib/commonjs/useBottomSheetContext.js +24 -42
- package/lib/commonjs/useBottomSheetContext.js.map +1 -1
- package/lib/commonjs/useBottomSheetControl.js +8 -14
- package/lib/commonjs/useBottomSheetControl.js.map +1 -1
- package/lib/commonjs/useBottomSheetManager.js +3 -13
- package/lib/commonjs/useBottomSheetManager.js.map +1 -1
- package/lib/commonjs/useBottomSheetStatus.js +9 -17
- package/lib/commonjs/useBottomSheetStatus.js.map +1 -1
- package/lib/commonjs/useEvent.js +39 -0
- package/lib/commonjs/useEvent.js.map +1 -0
- package/lib/commonjs/useScaleAnimation.js +38 -24
- package/lib/commonjs/useScaleAnimation.js.map +1 -1
- package/lib/commonjs/useSheetRenderData.js +62 -0
- package/lib/commonjs/useSheetRenderData.js.map +1 -0
- package/lib/typescript/example/src/App.d.ts.map +1 -1
- package/lib/typescript/example/src/screens/HomeScreen.d.ts.map +1 -1
- package/lib/typescript/example/src/sheets/NavigationSheets.d.ts.map +1 -1
- package/lib/typescript/example/src/sheets/ScannerNestedSheets.d.ts +4 -0
- package/lib/typescript/example/src/sheets/ScannerNestedSheets.d.ts.map +1 -0
- package/lib/typescript/example/src/sheets/ScannerSheet.d.ts +3 -0
- package/lib/typescript/example/src/sheets/ScannerSheet.d.ts.map +1 -0
- package/lib/typescript/example/src/sheets/index.d.ts +1 -0
- package/lib/typescript/example/src/sheets/index.d.ts.map +1 -1
- package/lib/typescript/src/BottomSheet.context.d.ts.map +1 -1
- package/lib/typescript/src/BottomSheetBackdrop.d.ts +0 -5
- package/lib/typescript/src/BottomSheetBackdrop.d.ts.map +1 -1
- package/lib/typescript/src/BottomSheetHost.d.ts +1 -3
- package/lib/typescript/src/BottomSheetHost.d.ts.map +1 -1
- package/lib/typescript/src/BottomSheetManaged.d.ts.map +1 -1
- package/lib/typescript/src/BottomSheetPersistent.d.ts +9 -0
- package/lib/typescript/src/BottomSheetPersistent.d.ts.map +1 -0
- package/lib/typescript/src/BottomSheetPortal.d.ts.map +1 -1
- package/lib/typescript/src/BottomSheetRef.context.d.ts +11 -0
- package/lib/typescript/src/BottomSheetRef.context.d.ts.map +1 -0
- package/lib/typescript/src/QueueItem.d.ts +8 -0
- package/lib/typescript/src/QueueItem.d.ts.map +1 -0
- package/lib/typescript/src/animatedRegistry.d.ts +5 -0
- package/lib/typescript/src/animatedRegistry.d.ts.map +1 -1
- package/lib/typescript/src/bottomSheet.store.d.ts +1 -37
- package/lib/typescript/src/bottomSheet.store.d.ts.map +1 -1
- package/lib/typescript/src/bottomSheetCoordinator.d.ts +2 -1
- package/lib/typescript/src/bottomSheetCoordinator.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +4 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/portalSessionRegistry.d.ts +8 -0
- package/lib/typescript/src/portalSessionRegistry.d.ts.map +1 -0
- package/lib/typescript/src/refsMap.d.ts +5 -0
- package/lib/typescript/src/refsMap.d.ts.map +1 -1
- package/lib/typescript/src/store/helpers.d.ts +11 -0
- package/lib/typescript/src/store/helpers.d.ts.map +1 -0
- package/lib/typescript/src/store/hooks.d.ts +16 -0
- package/lib/typescript/src/store/hooks.d.ts.map +1 -0
- package/lib/typescript/src/store/index.d.ts +5 -0
- package/lib/typescript/src/store/index.d.ts.map +1 -0
- package/lib/typescript/src/store/store.d.ts +11 -0
- package/lib/typescript/src/store/store.d.ts.map +1 -0
- package/lib/typescript/src/store/types.d.ts +37 -0
- package/lib/typescript/src/store/types.d.ts.map +1 -0
- package/lib/typescript/src/useBottomSheetContext.d.ts.map +1 -1
- package/lib/typescript/src/useBottomSheetControl.d.ts.map +1 -1
- package/lib/typescript/src/useBottomSheetManager.d.ts.map +1 -1
- package/lib/typescript/src/useBottomSheetStatus.d.ts +1 -2
- package/lib/typescript/src/useBottomSheetStatus.d.ts.map +1 -1
- package/lib/typescript/src/useEvent.d.ts +4 -0
- package/lib/typescript/src/useEvent.d.ts.map +1 -0
- package/lib/typescript/src/useScaleAnimation.d.ts +1 -12
- package/lib/typescript/src/useScaleAnimation.d.ts.map +1 -1
- package/lib/typescript/src/useSheetRenderData.d.ts +17 -0
- package/lib/typescript/src/useSheetRenderData.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/BottomSheet.context.ts +1 -3
- package/src/BottomSheetBackdrop.tsx +10 -19
- package/src/BottomSheetHost.tsx +13 -99
- package/src/BottomSheetManaged.tsx +24 -2
- package/src/BottomSheetPersistent.tsx +57 -0
- package/src/BottomSheetPortal.tsx +5 -7
- package/src/BottomSheetRef.context.ts +14 -0
- package/src/QueueItem.tsx +83 -0
- package/src/animatedRegistry.ts +8 -0
- package/src/bottomSheet.store.ts +1 -173
- package/src/bottomSheetCoordinator.ts +10 -8
- package/src/index.tsx +6 -0
- package/src/portalSessionRegistry.ts +25 -0
- package/src/refsMap.ts +8 -0
- package/src/store/helpers.ts +65 -0
- package/src/store/hooks.ts +50 -0
- package/src/store/index.ts +4 -0
- package/src/store/store.ts +168 -0
- package/src/store/types.ts +42 -0
- package/src/useBottomSheetContext.ts +6 -15
- package/src/useBottomSheetControl.ts +16 -7
- package/src/useBottomSheetManager.tsx +9 -10
- package/src/useBottomSheetStatus.ts +4 -14
- package/src/useEvent.ts +17 -0
- package/src/useScaleAnimation.ts +46 -30
- 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
|
-
<
|
|
46
|
-
style={
|
|
47
|
-
onPress={
|
|
48
|
-
|
|
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
|
});
|
package/src/BottomSheetHost.tsx
CHANGED
|
@@ -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 {
|
|
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 {
|
|
15
|
-
import {
|
|
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
|
-
{
|
|
51
|
-
<QueueItem
|
|
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
|
-
|
|
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 } =
|
|
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 {
|
|
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 =
|
|
18
|
-
|
|
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
|
+
});
|
package/src/animatedRegistry.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/bottomSheet.store.ts
CHANGED
|
@@ -1,173 +1 @@
|
|
|
1
|
-
|
|
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 = (
|
|
46
|
+
const handleAnimate = (_fromIndex: number, toIndex: number) => {
|
|
47
47
|
const state = useBottomSheetStore.getState();
|
|
48
48
|
const currentStatus = state.sheetsById[sheetId]?.status;
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
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';
|