@niibase/bottom-sheet-manager 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +414 -69
  2. package/lib/commonjs/events.js +100 -15
  3. package/lib/commonjs/events.js.map +1 -1
  4. package/lib/commonjs/index.js +14 -0
  5. package/lib/commonjs/index.js.map +1 -1
  6. package/lib/commonjs/manager.js +153 -35
  7. package/lib/commonjs/manager.js.map +1 -1
  8. package/lib/commonjs/provider.js +92 -54
  9. package/lib/commonjs/provider.js.map +1 -1
  10. package/lib/commonjs/router/index.js +80 -21
  11. package/lib/commonjs/router/index.js.map +1 -1
  12. package/lib/commonjs/router/router.js +137 -12
  13. package/lib/commonjs/router/router.js.map +1 -1
  14. package/lib/commonjs/router/view.js +93 -126
  15. package/lib/commonjs/router/view.js.map +1 -1
  16. package/lib/commonjs/sheet.js +122 -98
  17. package/lib/commonjs/sheet.js.map +1 -1
  18. package/lib/module/events.js +100 -15
  19. package/lib/module/events.js.map +1 -1
  20. package/lib/module/index.js +2 -2
  21. package/lib/module/index.js.map +1 -1
  22. package/lib/module/manager.js +154 -35
  23. package/lib/module/manager.js.map +1 -1
  24. package/lib/module/provider.js +87 -50
  25. package/lib/module/provider.js.map +1 -1
  26. package/lib/module/router/index.js +66 -19
  27. package/lib/module/router/index.js.map +1 -1
  28. package/lib/module/router/router.js +135 -11
  29. package/lib/module/router/router.js.map +1 -1
  30. package/lib/module/router/view.js +92 -126
  31. package/lib/module/router/view.js.map +1 -1
  32. package/lib/module/sheet.js +124 -100
  33. package/lib/module/sheet.js.map +1 -1
  34. package/lib/typescript/events.d.ts +46 -12
  35. package/lib/typescript/events.d.ts.map +1 -1
  36. package/lib/typescript/index.d.ts +2 -2
  37. package/lib/typescript/index.d.ts.map +1 -1
  38. package/lib/typescript/manager.d.ts +73 -7
  39. package/lib/typescript/manager.d.ts.map +1 -1
  40. package/lib/typescript/provider.d.ts +22 -16
  41. package/lib/typescript/provider.d.ts.map +1 -1
  42. package/lib/typescript/router/index.d.ts +47 -17
  43. package/lib/typescript/router/index.d.ts.map +1 -1
  44. package/lib/typescript/router/router.d.ts +44 -5
  45. package/lib/typescript/router/router.d.ts.map +1 -1
  46. package/lib/typescript/router/types.d.ts +142 -32
  47. package/lib/typescript/router/types.d.ts.map +1 -1
  48. package/lib/typescript/router/view.d.ts +3 -3
  49. package/lib/typescript/router/view.d.ts.map +1 -1
  50. package/lib/typescript/sheet.d.ts +1 -1
  51. package/lib/typescript/sheet.d.ts.map +1 -1
  52. package/lib/typescript/types.d.ts +52 -21
  53. package/lib/typescript/types.d.ts.map +1 -1
  54. package/package.json +14 -15
  55. package/src/events.ts +118 -27
  56. package/src/index.ts +2 -1
  57. package/src/manager.ts +209 -42
  58. package/src/provider.tsx +144 -71
  59. package/src/router/index.tsx +77 -33
  60. package/src/router/router.ts +188 -15
  61. package/src/router/types.ts +172 -57
  62. package/src/router/view.tsx +111 -213
  63. package/src/sheet.tsx +192 -124
  64. package/src/types.ts +51 -24
@@ -1,265 +1,163 @@
1
- import {
2
- BottomSheetBackdrop,
3
- BottomSheetModal,
4
- BottomSheetModalProps,
5
- BottomSheetModalProvider,
6
- } from "@gorhom/bottom-sheet";
1
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
7
2
  import { ParamListBase, useTheme } from "@react-navigation/native";
3
+ import { SNAP_POINT_TYPE } from "@gorhom/bottom-sheet";
8
4
  import * as React from "react";
9
- import { StatusBar } from "react-native";
10
- import Animated, {
11
- Easing,
12
- interpolate,
13
- interpolateColor,
14
- runOnJS,
15
- useAnimatedReaction,
16
- useAnimatedStyle,
17
- useSharedValue,
18
- withSpring,
19
- } from "react-native-reanimated";
20
- import { useSafeAreaInsets } from "react-native-safe-area-context";
21
5
 
22
6
  import type {
23
7
  BottomSheetDescriptorMap,
24
- BottomSheetNavigationConfig,
25
8
  BottomSheetNavigationHelpers,
26
- BottomSheetNavigationProp,
27
9
  BottomSheetNavigationState,
10
+ BottomSheetRoute,
28
11
  } from "./types";
12
+ import { BottomSheetInstance, BottomSheetProps } from "../types";
13
+ import { BottomSheetActions } from "./router";
14
+ import BottomSheet from "../sheet";
29
15
 
30
- type BottomSheetModalScreenProps = BottomSheetModalProps & {
31
- navigation: BottomSheetNavigationProp<ParamListBase>;
32
- /**
33
- * When `true`, tapping on the backdrop will not dismiss the modal.
34
- * @default false
35
- */
36
- clickThrough?: boolean;
37
-
38
- /**
39
- * Opacity of the sheet's overlay.
40
- * @default 0.45
41
- */
42
- opacity?: number;
43
-
44
- /**
45
- * IOS modal sheet type of animation
46
- * @default false
47
- */
48
- iosModalSheetTypeOfAnimation?: boolean;
16
+ type Props = {
17
+ state: BottomSheetNavigationState<ParamListBase>;
18
+ navigation: BottomSheetNavigationHelpers;
19
+ descriptors: BottomSheetDescriptorMap;
49
20
  };
50
21
 
51
- function BottomSheetModalScreen({
52
- index,
53
- navigation,
54
- clickThrough,
55
- iosModalSheetTypeOfAnimation,
56
- opacity,
22
+ function BottomSheetScreen({
57
23
  children,
24
+ navigation,
25
+ route,
58
26
  ...props
59
- }: BottomSheetModalScreenProps) {
60
- const ref = React.useRef<BottomSheetModal>(null);
61
- const lastIndexRef = React.useRef(index);
62
-
63
- // Present on mount.
64
- React.useEffect(() => {
65
- ref.current?.present();
66
- }, []);
27
+ }: BottomSheetProps & {
28
+ route: BottomSheetRoute<ParamListBase>;
29
+ navigation: BottomSheetNavigationHelpers;
30
+ }) {
31
+ const ref = React.useRef<BottomSheetInstance>(null);
32
+ const lastSnapIndexRef = React.useRef(route.snapToIndex ?? props.index ?? 0);
67
33
 
68
- const isMounted = React.useRef(true);
34
+ // Handle route closing state
69
35
  React.useEffect(() => {
70
- return () => {
71
- isMounted.current = false;
72
- };
73
- }, []);
36
+ if (route.closing) {
37
+ ref.current?.close();
38
+ }
39
+ }, [route.closing]);
74
40
 
41
+ // Handle snap point changes from navigation actions
75
42
  React.useEffect(() => {
76
- if (index != null && lastIndexRef.current !== index) {
77
- ref.current?.snapToIndex(index);
43
+ if (route.snapToIndex != null && route.snapToIndex !== lastSnapIndexRef.current) {
44
+ ref.current?.snapToIndex(route.snapToIndex);
45
+ lastSnapIndexRef.current = route.snapToIndex;
78
46
  }
79
- }, [index]);
47
+ }, [route.snapToIndex, route.snapToKey]);
48
+
49
+ const handleChange = React.useCallback(
50
+ (newIndex: number, position: number, type: SNAP_POINT_TYPE) => {
51
+ navigation.emit({
52
+ type: "sheetOnChange",
53
+ target: route.key,
54
+ data: { index: newIndex, position, type },
55
+ });
56
+
57
+ const currentIndex = lastSnapIndexRef.current;
58
+ lastSnapIndexRef.current = newIndex;
80
59
 
81
- const onChange = React.useCallback(
82
- (newIndex: number) => {
83
- const currentIndex = lastIndexRef.current;
84
- lastIndexRef.current = newIndex;
85
60
  if (newIndex >= 0 && newIndex !== currentIndex) {
86
- navigation.snapTo(newIndex);
61
+ navigation.dispatch(BottomSheetActions.snapTo(newIndex));
87
62
  }
88
63
  },
89
64
  [navigation],
90
65
  );
91
66
 
92
- const onDismiss = React.useCallback(() => {
93
- // BottomSheetModal will call onDismiss on unmount, be we do not want that since
94
- // we already popped the screen.
95
- if (isMounted.current) {
96
- navigation.goBack();
97
- }
98
- }, [navigation]);
99
-
100
67
  return (
101
- <BottomSheetModal
102
- ref={ref}
103
- onDismiss={onDismiss}
104
- onChange={onChange}
105
- index={index}
106
- backdropComponent={(props) => (
107
- <BottomSheetBackdrop
108
- {...props}
109
- appearsOnIndex={0}
110
- disappearsOnIndex={-1}
111
- enableTouchThrough={!!clickThrough}
112
- opacity={opacity || 0.45}
113
- />
114
- )}
115
- {...props}
116
- >
68
+ <BottomSheet ref={ref} onChange={handleChange} {...props}>
117
69
  {children}
118
- </BottomSheetModal>
70
+ </BottomSheet>
119
71
  );
120
72
  }
121
73
 
122
- const DEFAULT_SNAP_POINTS = ["66%"];
123
-
124
- type Props = BottomSheetNavigationConfig & {
125
- state: BottomSheetNavigationState<ParamListBase>;
126
- navigation: BottomSheetNavigationHelpers;
127
- descriptors: BottomSheetDescriptorMap;
128
- };
129
-
130
- export function BottomSheetView({ state, descriptors }: Props) {
74
+ export function BottomSheetView({ state, navigation, descriptors }: Props) {
131
75
  const { colors } = useTheme();
132
- const { top } = useSafeAreaInsets();
76
+ const { bottom, left, right } = useSafeAreaInsets();
77
+
133
78
  const themeBackgroundStyle = React.useMemo(
134
79
  () => ({
80
+ borderCurve: "continuous" as unknown as undefined,
135
81
  backgroundColor: colors.card,
136
82
  }),
137
83
  [colors.card],
138
84
  );
85
+
139
86
  const themeHandleIndicatorStyle = React.useMemo(
140
87
  () => ({
88
+ borderCurve: "continuous" as unknown as undefined,
141
89
  backgroundColor: colors.border,
142
- height: 5,
143
- width: 50,
144
90
  }),
145
91
  [colors.border],
146
92
  );
147
93
 
148
- // IOS modal sheet type of animation
149
- const isFullScreen = useSharedValue(0);
150
- const colorStyle = useAnimatedStyle(() => ({
151
- flex: 1,
152
- backgroundColor: interpolateColor(
153
- isFullScreen.value,
154
- [0, 1],
155
- ["transparent", "#000"],
156
- ),
157
- }));
158
- const animatedStyle = useAnimatedStyle(() => ({
159
- flex: 1,
160
- transform: [
161
- {
162
- scaleX: withSpring(interpolate(isFullScreen.value, [0, 1], [1, 0.92]), {
163
- damping: 15,
164
- stiffness: 100,
165
- }),
166
- },
167
- {
168
- translateY: withSpring(interpolate(isFullScreen.value, [0, 1], [0, top + 5]), {
169
- damping: 15,
170
- stiffness: 100,
171
- }),
172
- },
173
- ],
174
- }));
175
-
176
- // Since background color is white, we need to set status bar to light
177
- const setStatusBar = StatusBar.setBarStyle;
178
- useAnimatedReaction(
179
- () => isFullScreen.value,
180
- (currentValue) => {
181
- "worklet";
182
- if (currentValue > -1) {
183
- runOnJS(setStatusBar)(currentValue >= 0.5 ? "light-content" : "default");
184
- }
185
- },
186
- [],
94
+ const defaultStyle = React.useMemo(
95
+ () => ({ paddingBottom: bottom, paddingLeft: left, paddingRight: right }),
96
+ [bottom, left, right],
187
97
  );
188
98
 
189
- // Avoid rendering provider if we only have one screen.
190
- const shouldRenderProvider = React.useRef(false);
191
- shouldRenderProvider.current = shouldRenderProvider.current || state.routes.length > 1;
192
-
193
- const firstRoute = state.routes[0];
194
- if (!firstRoute) {
195
- // no routes at all, probably shouldn't happen, but let's be defensive
196
- return null;
197
- }
198
-
199
- const firstDescriptor = descriptors[firstRoute.key];
200
- if (!firstDescriptor) {
201
- // if we don't have a descriptor for the first route, bail out
202
- return null;
203
- }
99
+ const [baseRoute, ...sheetRoutes] = state.routes;
100
+ const baseDescriptor = baseRoute ? descriptors[baseRoute.key] : null;
204
101
 
205
102
  return (
206
103
  <>
207
- <Animated.View style={colorStyle}>
208
- <Animated.View style={animatedStyle}>{firstDescriptor.render?.()}</Animated.View>
209
- </Animated.View>
210
- {shouldRenderProvider.current && (
211
- <BottomSheetModalProvider>
212
- {state.routes.slice(1).map((route) => {
213
- const descriptor = descriptors[route.key];
214
- if (!descriptor) return null;
215
-
216
- const { options, navigation, render } = descriptor;
217
- const {
218
- index,
219
- snapPoints,
220
- handleStyle,
221
- backgroundStyle,
222
- handleIndicatorStyle,
223
- enableDynamicSizing,
224
- ...sheetProps
225
- } = options;
226
-
227
- return (
228
- <BottomSheetModalScreen
229
- key={route.key}
230
- // Make sure index is in range, it could be out if snapToIndex is persisted
231
- // and snapPoints is changed.
232
- index={Math.min(
233
- route.snapToIndex ?? index ?? 0,
234
- !!snapPoints ? snapPoints.length - 1 : 0,
235
- )}
236
- snapPoints={
237
- !snapPoints && !enableDynamicSizing ? DEFAULT_SNAP_POINTS : snapPoints
238
- }
239
- onAnimate={(_, to) => {
240
- // @ts-ignore TODO: Fix types
241
- isFullScreen.value = ["%100", "100%"].includes(snapPoints?.[to])
242
- ? 1
243
- : 0;
244
- }}
245
- animationConfigs={{
246
- duration: 300,
247
- easing: Easing.bezier(0.25, 0.1, 0.25, 1),
248
- }}
249
- topInset={top + 18}
250
- navigation={navigation}
251
- enableDynamicSizing={enableDynamicSizing}
252
- backgroundStyle={[themeBackgroundStyle, backgroundStyle]}
253
- handleIndicatorStyle={[themeHandleIndicatorStyle, handleIndicatorStyle]}
254
- handleStyle={[themeBackgroundStyle, { borderRadius: 20 }, handleStyle]}
255
- {...sheetProps}
256
- >
257
- {render?.()}
258
- </BottomSheetModalScreen>
259
- );
260
- })}
261
- </BottomSheetModalProvider>
262
- )}
104
+ {baseDescriptor?.render()}
105
+ {sheetRoutes.map((route) => {
106
+ const descriptor = descriptors[route.key];
107
+ if (!descriptor) return null;
108
+
109
+ const { options, render } = descriptor;
110
+ const {
111
+ index = 0,
112
+ style,
113
+ backgroundStyle,
114
+ handleIndicatorStyle,
115
+ handleStyle,
116
+ ...props
117
+ } = options;
118
+
119
+ return (
120
+ <BottomSheetScreen
121
+ key={route.key}
122
+ id={route.key}
123
+ route={route}
124
+ index={index}
125
+ navigation={navigation}
126
+ style={[defaultStyle, style]}
127
+ backgroundStyle={[themeBackgroundStyle, backgroundStyle]}
128
+ handleIndicatorStyle={[themeHandleIndicatorStyle, handleIndicatorStyle]}
129
+ handleStyle={[themeBackgroundStyle, { borderRadius: 24 }, handleStyle]}
130
+ onClose={(data) => {
131
+ navigation.dispatch({
132
+ ...BottomSheetActions.remove(),
133
+ source: route.key,
134
+ });
135
+ navigation.emit({
136
+ type: "sheetDismiss",
137
+ target: route.key,
138
+ data,
139
+ });
140
+ }}
141
+ onBeforeShow={(data) => {
142
+ navigation.emit({
143
+ type: "sheetPresent",
144
+ target: route.key,
145
+ data,
146
+ });
147
+ }}
148
+ onAnimate={(fromIndex, toIndex, fromPosition, toPosition) => {
149
+ navigation.emit({
150
+ type: "sheetOnAnimate",
151
+ target: route.key,
152
+ data: { fromIndex, toIndex, fromPosition, toPosition },
153
+ });
154
+ }}
155
+ {...props}
156
+ >
157
+ {render()}
158
+ </BottomSheetScreen>
159
+ );
160
+ })}
263
161
  </>
264
162
  );
265
163
  }