@niibase/bottom-sheet-manager 1.1.0 → 1.3.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 (63) hide show
  1. package/README.md +372 -38
  2. package/lib/commonjs/events.js +100 -15
  3. package/lib/commonjs/events.js.map +1 -1
  4. package/lib/commonjs/index.js +7 -0
  5. package/lib/commonjs/index.js.map +1 -1
  6. package/lib/commonjs/manager.js +107 -29
  7. package/lib/commonjs/manager.js.map +1 -1
  8. package/lib/commonjs/provider.js +69 -28
  9. package/lib/commonjs/provider.js.map +1 -1
  10. package/lib/commonjs/router/index.js +50 -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 +194 -84
  15. package/lib/commonjs/router/view.js.map +1 -1
  16. package/lib/commonjs/sheet.js +125 -76
  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 +1 -1
  21. package/lib/module/index.js.map +1 -1
  22. package/lib/module/manager.js +108 -29
  23. package/lib/module/manager.js.map +1 -1
  24. package/lib/module/provider.js +65 -25
  25. package/lib/module/provider.js.map +1 -1
  26. package/lib/module/router/index.js +34 -18
  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 +194 -84
  31. package/lib/module/router/view.js.map +1 -1
  32. package/lib/module/sheet.js +127 -78
  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 +1 -1
  37. package/lib/typescript/index.d.ts.map +1 -1
  38. package/lib/typescript/manager.d.ts +57 -7
  39. package/lib/typescript/manager.d.ts.map +1 -1
  40. package/lib/typescript/provider.d.ts +22 -3
  41. package/lib/typescript/provider.d.ts.map +1 -1
  42. package/lib/typescript/router/index.d.ts +33 -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 +113 -17
  47. package/lib/typescript/router/types.d.ts.map +1 -1
  48. package/lib/typescript/router/view.d.ts +1 -1
  49. package/lib/typescript/router/view.d.ts.map +1 -1
  50. package/lib/typescript/sheet.d.ts.map +1 -1
  51. package/lib/typescript/types.d.ts +27 -12
  52. package/lib/typescript/types.d.ts.map +1 -1
  53. package/package.json +1 -1
  54. package/src/events.ts +118 -27
  55. package/src/index.ts +6 -5
  56. package/src/manager.ts +156 -33
  57. package/src/provider.tsx +98 -44
  58. package/src/router/index.tsx +38 -31
  59. package/src/router/router.ts +184 -15
  60. package/src/router/types.ts +119 -22
  61. package/src/router/view.tsx +252 -132
  62. package/src/sheet.tsx +176 -95
  63. package/src/types.ts +144 -129
package/src/sheet.tsx CHANGED
@@ -20,9 +20,9 @@ import {
20
20
  } from "react-native";
21
21
  import {
22
22
  Easing,
23
- interpolate,
24
23
  useAnimatedReaction,
25
24
  useSharedValue,
25
+ withTiming,
26
26
  } from "react-native-reanimated";
27
27
  import { useSafeAreaInsets } from "react-native-safe-area-context";
28
28
  import { useTheme } from "@react-navigation/native";
@@ -33,8 +33,9 @@ import {
33
33
  useSheetAnimationContext,
34
34
  useSheetIDContext,
35
35
  useSheetRef,
36
+ useStackBehaviorContext,
36
37
  } from "./provider";
37
- import { BottomSheetInstance, BottomSheetProps, SheetIds } from "./types";
38
+ import { BottomSheetInstance, BottomSheetProps, SheetIds, StackBehavior } from "./types";
38
39
  import { PrivateManager } from "./manager";
39
40
  import { eventManager } from "./events";
40
41
 
@@ -64,34 +65,42 @@ const useSheetManager = ({
64
65
  onContextUpdate,
65
66
  }: {
66
67
  id?: string;
67
- onHide: (data?: any, dismiss?: boolean) => void;
68
- onBeforeShow?: (data?: any) => void;
68
+ onHide: (data?: unknown, dismiss?: boolean, behavior?: StackBehavior) => void;
69
+ onBeforeShow?: (data?: unknown, behavior?: StackBehavior) => void;
69
70
  onContextUpdate: () => void;
70
71
  }) => {
71
- const [visible, setVisible] = React.useState(false);
72
72
  const currentContext = useProviderContext();
73
+ const hasShownRef = React.useRef(false);
73
74
 
74
75
  React.useEffect(() => {
75
76
  if (!id) return undefined;
76
77
 
77
78
  const subscriptions = [
78
- eventManager.subscribe(`show_${id}`, (data: any, context?: string) => {
79
- if (currentContext !== context || visible) return;
80
- onContextUpdate?.();
81
- onBeforeShow?.(data);
82
- setVisible(true);
83
- }),
84
- eventManager.subscribe(`hide_${id}`, (data: any, context, dismiss?: boolean) => {
85
- if (currentContext !== context) return;
86
- onHide?.(data, dismiss);
87
- }),
79
+ eventManager.subscribe(
80
+ `show_${id}`,
81
+ (data: unknown, context?: string, behavior?: StackBehavior) => {
82
+ if (currentContext !== context) return;
83
+ if (!hasShownRef.current) {
84
+ hasShownRef.current = true;
85
+ onContextUpdate?.();
86
+ onBeforeShow?.(data, behavior);
87
+ }
88
+ },
89
+ ),
90
+ eventManager.subscribe(
91
+ `hide_${id}`,
92
+ (data: unknown, context: string, dismiss?: boolean, behavior?: StackBehavior) => {
93
+ if (currentContext !== context) return;
94
+ hasShownRef.current = false;
95
+ onHide?.(data, dismiss, behavior);
96
+ },
97
+ ),
88
98
  ];
89
99
  return () => {
100
+ hasShownRef.current = false;
90
101
  subscriptions.forEach((s) => s?.unsubscribe?.());
91
102
  };
92
103
  }, [id, onHide, onBeforeShow, onContextUpdate, currentContext]);
93
-
94
- return { visible, setVisible };
95
104
  };
96
105
 
97
106
  const BottomSheetComponent = React.forwardRef<BottomSheetInstance, BottomSheetProps>(
@@ -106,10 +115,12 @@ const BottomSheetComponent = React.forwardRef<BottomSheetInstance, BottomSheetPr
106
115
  enableDynamicSizing = false,
107
116
  handleIndicatorStyle,
108
117
  iosModalSheetTypeOfAnimation: pageAnimation,
118
+ animatedIndex: defaultAnimatedIndex,
109
119
  backgroundStyle,
120
+ onAnimate,
110
121
  handleStyle,
122
+ style,
111
123
  clickThrough,
112
- fullScreen,
113
124
  opacity,
114
125
 
115
126
  ...props
@@ -118,14 +129,27 @@ const BottomSheetComponent = React.forwardRef<BottomSheetInstance, BottomSheetPr
118
129
  ) => {
119
130
  const currentSheetRef = useSheetRef();
120
131
  const currentCtx = useProviderContext();
132
+ const stackContext = useStackBehaviorContext();
121
133
 
122
- const { isFullScreen, iosModalSheetTypeOfAnimation } = useSheetAnimationContext();
134
+ const { isFullScreen, iosModalSheetTypeOfAnimation, duration } =
135
+ useSheetAnimationContext();
123
136
  const animatedIndex = useSharedValue(0);
137
+ const previousIndex = useSharedValue(-1);
138
+
139
+ const [currentStackBehavior, setCurrentStackBehavior] =
140
+ React.useState<StackBehavior>(stackBehavior);
141
+ const isPushed = currentStackBehavior === "push";
124
142
 
125
143
  const { colors } = useTheme();
126
- const { top } = useSafeAreaInsets();
144
+ const { bottom, left, right } = useSafeAreaInsets();
145
+
127
146
  const themeBackgroundStyle = React.useMemo(
128
- () => ({ backgroundColor: colors.card }),
147
+ () => ({
148
+ borderCurve: "continuous" as unknown as undefined,
149
+ backgroundColor: colors.card,
150
+ borderTopLeftRadius: 20,
151
+ borderTopRightRadius: 20,
152
+ }),
129
153
  [colors.card],
130
154
  );
131
155
  const themeHandleIndicatorStyle = React.useMemo(
@@ -137,6 +161,15 @@ const BottomSheetComponent = React.forwardRef<BottomSheetInstance, BottomSheetPr
137
161
  [colors.border],
138
162
  );
139
163
 
164
+ const defaultStyle = React.useMemo(
165
+ () => ({
166
+ paddingBottom: bottom,
167
+ paddingLeft: left,
168
+ paddingRight: right,
169
+ }),
170
+ [bottom, left, right],
171
+ );
172
+
140
173
  const valueRef = React.useRef<unknown>(null);
141
174
  const bottomSheetRef = React.useRef<BottomSheetModal>(null);
142
175
  const hardwareBackPressEvent = React.useRef<NativeEventSubscription>(
@@ -145,10 +178,15 @@ const BottomSheetComponent = React.forwardRef<BottomSheetInstance, BottomSheetPr
145
178
 
146
179
  const id = useSheetIDContext();
147
180
  const sheetId = props.id || id;
181
+
148
182
  useSheetManager({
149
183
  id: sheetId,
150
- onHide: (data, dismiss) => hideSheet(data, true, dismiss),
151
- onBeforeShow: (data) => {
184
+ onHide: (data, dismiss, behavior) => {
185
+ if (behavior) setCurrentStackBehavior(behavior);
186
+ hideSheet(data, true, dismiss);
187
+ },
188
+ onBeforeShow: (data, behavior) => {
189
+ if (behavior) setCurrentStackBehavior(behavior);
152
190
  onBeforeShow?.(data);
153
191
  valueRef.current = undefined;
154
192
  currentSheetRef.current = getInstance();
@@ -164,69 +202,111 @@ const BottomSheetComponent = React.forwardRef<BottomSheetInstance, BottomSheetPr
164
202
  });
165
203
 
166
204
  useAnimatedReaction(
167
- () => {
168
- if (!!iosModalSheetTypeOfAnimation || !!pageAnimation) {
169
- isFullScreen.value = 0;
170
- }
171
- return animatedIndex.value;
172
- },
205
+ () => animatedIndex.value,
173
206
  (index) => {
174
- "worklet";
175
- const points: (string | number)[] = ["%100", "100%"];
176
- const checkFullScreen = fullScreen
177
- ? -1
178
- : snapPoints instanceof Array
207
+ ("worklet");
208
+ if (defaultAnimatedIndex) {
209
+ defaultAnimatedIndex.set(index);
210
+ }
211
+
212
+ if (!iosModalSheetTypeOfAnimation && !pageAnimation) {
213
+ if (isFullScreen.value > 0) isFullScreen.set(0);
214
+ previousIndex.set(index);
215
+ return;
216
+ }
217
+
218
+ if (isFullScreen.value < 0) {
219
+ isFullScreen.set(0);
220
+ }
221
+
222
+ const isClosing =
223
+ index < 0 || (previousIndex.value >= 0 && index < previousIndex.value - 0.05);
224
+ previousIndex.set(index);
225
+
226
+ if (isClosing) {
227
+ if (isFullScreen.value > 0.01) {
228
+ isFullScreen.set(
229
+ withTiming(0, {
230
+ duration: duration * 0.85,
231
+ easing: Easing.bezier(0.25, 0.1, 0.25, 1),
232
+ }),
233
+ );
234
+ }
235
+ return;
236
+ }
237
+
238
+ const points: (string | number)[] = ["%90", "90%"];
239
+ const fullScreenIndex =
240
+ snapPoints instanceof Array
179
241
  ? snapPoints.findIndex((p) => points.includes(p))
180
242
  : snapPoints?.value?.findIndex((p) => points.includes(p)) || -1;
181
243
 
182
- if (
183
- -1 !== checkFullScreen &&
184
- (!!iosModalSheetTypeOfAnimation || !!pageAnimation)
185
- ) {
186
- isFullScreen.value = interpolate(
187
- index,
188
- [checkFullScreen - 1, checkFullScreen],
189
- [0, 1],
190
- );
244
+ if (index >= fullScreenIndex - 0.5 && index <= fullScreenIndex + 0.5) {
245
+ isFullScreen.set(1);
246
+ } else if (index >= 0) {
247
+ isFullScreen.set(0);
191
248
  }
192
249
  },
193
- [snapPoints],
250
+ [snapPoints, iosModalSheetTypeOfAnimation, pageAnimation, duration],
194
251
  );
195
252
 
196
253
  const hideSheet = React.useCallback(
197
- (data?: any, isSheetManagerOrRef?: boolean, dismiss?: boolean) => {
198
- const value = data ?? valueRef.current;
254
+ (data?: unknown, fromManager?: boolean, dismiss?: boolean) => {
255
+ let value = data ?? valueRef.current;
256
+
257
+ hardwareBackPressEvent.current?.remove();
199
258
 
200
- if (!dismiss || stackBehavior !== "push") {
201
- hardwareBackPressEvent.current?.remove();
259
+ const closeValue = onClose?.(value);
260
+ if (closeValue !== undefined) value = closeValue;
261
+
262
+ if (dismiss && currentStackBehavior === "push") {
263
+ if (fromManager) valueRef.current = data;
264
+ return;
265
+ }
266
+
267
+ if (currentStackBehavior !== "replace" || !dismiss) {
202
268
  bottomSheetRef.current?.close();
203
- onClose?.(value);
204
269
  }
205
270
 
206
271
  if (sheetId) {
207
- PrivateManager.remove(sheetId, currentCtx);
208
- if (dismiss && stackBehavior === "push") return;
272
+ const hasHistory = PrivateManager.history.length > 0;
273
+ const shouldRestorePrevious = currentStackBehavior !== "replace";
209
274
 
210
- const history = PrivateManager.history.length >= 1;
211
275
  eventManager.publish(
212
276
  `onclose_${sheetId}`,
213
277
  value,
214
278
  currentCtx,
215
- history || dismiss,
279
+ hasHistory || !!dismiss,
280
+ currentStackBehavior,
216
281
  );
217
282
 
218
- if (stackBehavior === "replace") return;
219
- if (dismiss) {
220
- PrivateManager.history.push({ id: sheetId, context: currentCtx });
221
- } else if (history) {
222
- const { id, context } = PrivateManager.history.pop()!;
223
- eventManager.publish(`show_wrap_${id}`, undefined, context, true);
283
+ if (shouldRestorePrevious) {
284
+ if (dismiss) {
285
+ PrivateManager.history.push({
286
+ id: sheetId,
287
+ context: currentCtx,
288
+ behavior: currentStackBehavior,
289
+ });
290
+ } else if (hasHistory) {
291
+ const prev = PrivateManager.history.pop()!;
292
+ eventManager.publish(
293
+ `show_wrap_${prev.id}`,
294
+ undefined,
295
+ prev.context,
296
+ true,
297
+ prev.behavior,
298
+ );
299
+ }
224
300
  }
301
+
302
+ PrivateManager.remove(sheetId, currentCtx);
225
303
  }
226
- if (isSheetManagerOrRef) valueRef.current = data;
304
+
305
+ if (fromManager) valueRef.current = data;
227
306
  },
228
- [sheetId, currentCtx, onClose],
307
+ [sheetId, currentCtx, onClose, currentStackBehavior],
229
308
  );
309
+
230
310
  const getInstance = React.useCallback(
231
311
  (): BottomSheetInstance => ({
232
312
  close(options = {}): void {
@@ -274,40 +354,54 @@ const BottomSheetComponent = React.forwardRef<BottomSheetInstance, BottomSheetPr
274
354
 
275
355
  React.useImperativeHandle(ref, getInstance, [getInstance]);
276
356
 
357
+ const zIndex = React.useMemo(() => {
358
+ if (!sheetId) return 0;
359
+ if (isPushed) {
360
+ return PrivateManager.zIndex(sheetId, currentCtx);
361
+ }
362
+ return 0;
363
+ }, [sheetId, isPushed, currentCtx]);
364
+
365
+ const backdropOpacity = React.useMemo(() => {
366
+ if (isPushed && stackContext.previousSheetId) {
367
+ return (opacity || 0.45) * 0.6;
368
+ }
369
+ return opacity || 0.45;
370
+ }, [isPushed, stackContext.previousSheetId, opacity]);
371
+
277
372
  return (
278
- <View
279
- pointerEvents="box-none"
280
- style={[
281
- StyleSheet.absoluteFill,
282
- {
283
- zIndex:
284
- sheetId && stackBehavior === "push"
285
- ? PrivateManager.zIndex(sheetId, currentCtx)
286
- : 0,
287
- },
288
- ]}
289
- >
373
+ <View pointerEvents="box-none" style={[StyleSheet.absoluteFill, { zIndex }]}>
290
374
  <RNBottomSheet
291
375
  enableDynamicSizing={enableDynamicSizing}
292
- animationConfigs={{ duration: 400, easing: Easing.bezier(0.25, 0.1, 0.25, 1) }}
293
- backdropComponent={(props) => (
376
+ backdropComponent={(backdropProps) => (
294
377
  <BottomSheetBackdrop
295
378
  enableTouchThrough={!!clickThrough}
296
- opacity={opacity || 0.45}
379
+ opacity={backdropOpacity}
297
380
  disappearsOnIndex={-1}
298
381
  appearsOnIndex={0}
299
- {...props}
382
+ {...backdropProps}
300
383
  />
301
384
  )}
385
+ onAnimate={(from, to, ...args) => {
386
+ const snapPointLen = Array.isArray(snapPoints)
387
+ ? snapPoints.length
388
+ : (snapPoints?.value?.length ?? 0);
389
+
390
+ if (to >= isFullScreen.value && to > snapPointLen - 1) {
391
+ isFullScreen.set(0);
392
+ } else if (to > 0 && to === previousIndex.value && isFullScreen.value === 0) {
393
+ isFullScreen.set(1);
394
+ }
395
+
396
+ onAnimate?.(from, to, ...args);
397
+ }}
398
+ topInset={0}
399
+ bottomInset={0}
302
400
  {...props}
303
401
  ref={bottomSheetRef}
304
402
  onClose={hideSheet}
305
403
  animatedIndex={animatedIndex}
306
- topInset={
307
- fullScreen
308
- ? 0
309
- : top + (iosModalSheetTypeOfAnimation || pageAnimation ? 20 : 5)
310
- }
404
+ style={[defaultStyle, style]}
311
405
  snapPoints={enableDynamicSizing ? undefined : (snapPoints ?? ["66%"])}
312
406
  handleIndicatorStyle={[themeHandleIndicatorStyle, handleIndicatorStyle]}
313
407
  backgroundStyle={[themeBackgroundStyle, backgroundStyle]}
@@ -334,17 +428,4 @@ BottomSheet.FooterContainer = BottomSheetFooterContainer;
334
428
  BottomSheet.Backdrop = BottomSheetBackdrop;
335
429
  BottomSheet.TextInput = BottomSheetTextInput;
336
430
 
337
- try {
338
- const { cssInterop } = require("nativewind");
339
- cssInterop(BottomSheet, {
340
- handleIndicatorClassName: "handleIndicatorStyle",
341
- backgroundClassName: "backgroundStyle",
342
- containerClassName: "containerStyle",
343
- handleClassName: "handleStyle",
344
- className: "style",
345
- });
346
- } catch (e) {
347
- // Ignore error
348
- }
349
-
350
431
  export default BottomSheet;