@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/manager.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { RefObject } from "react";
2
2
 
3
- import { eventManager } from "./events";
3
+ import { BottomSheetInstance, Sheets, StackBehavior } from "./types";
4
4
  import { providerRegistryStack, sheetsRegistry } from "./provider";
5
- import { BottomSheetInstance, Sheets } from "./types";
5
+ import { eventManager } from "./events";
6
6
 
7
7
  // Array of all the ids of Sheets currently rendered in the app.
8
8
  const ids: string[] = [];
@@ -12,14 +12,20 @@ const DEFAULT_Z_INDEX = 999;
12
12
 
13
13
  const makeKey = (id: string, context: string) => `${id}:${context}`;
14
14
 
15
+ interface HistoryEntry {
16
+ id: string;
17
+ context: string;
18
+ behavior: StackBehavior;
19
+ }
20
+
15
21
  export const PrivateManager = {
16
- // Return to the previous sheet when the current sheet is closed.
17
- history: [] as { id: string; context: string }[],
22
+ // Stack of sheet history for restoration when sheets are closed
23
+ history: [] as HistoryEntry[],
18
24
 
19
25
  context(options?: { context?: string; id?: string }) {
20
26
  if (!options) options = {};
21
27
  if (!options?.context) {
22
- // If no context is provided, use to current top most context
28
+ // If no context is provided, use the current top-most context
23
29
  // to render the sheet.
24
30
  for (const context of providerRegistryStack.slice().reverse()) {
25
31
  // We only automatically select nested sheet providers.
@@ -42,11 +48,12 @@ export const PrivateManager = {
42
48
  ) => {
43
49
  const key = makeKey(id, context);
44
50
  refs[key] = instance;
45
- keys.push(key);
51
+ if (!keys.includes(key)) {
52
+ keys.push(key);
53
+ }
46
54
  },
47
55
 
48
56
  /**
49
- *
50
57
  * Get internal ref of a sheet by the given id.
51
58
  *
52
59
  * @param id Id of the sheet
@@ -57,8 +64,8 @@ export const PrivateManager = {
57
64
  context?: string,
58
65
  ): RefObject<BottomSheetInstance<SheetId>> => {
59
66
  if (!context) {
60
- for (let ctx of providerRegistryStack.slice().reverse()) {
61
- for (let _id in sheetsRegistry[ctx]) {
67
+ for (const ctx of providerRegistryStack.slice().reverse()) {
68
+ for (const _id in sheetsRegistry[ctx]) {
62
69
  if (_id === id) {
63
70
  context = ctx;
64
71
  break;
@@ -70,14 +77,17 @@ export const PrivateManager = {
70
77
  },
71
78
 
72
79
  add: (id: string, context: string) => {
73
- if (ids.indexOf(id) < 0) {
74
- ids[ids.length] = makeKey(id, context);
80
+ const key = makeKey(id, context);
81
+ if (!ids.includes(key)) {
82
+ ids.push(key);
75
83
  }
76
84
  },
77
85
 
78
86
  remove: (id: string, context: string) => {
79
- if (ids.indexOf(makeKey(id, context)) > -1) {
80
- ids.splice(ids.indexOf(makeKey(id, context)));
87
+ const key = makeKey(id, context);
88
+ const index = ids.indexOf(key);
89
+ if (index > -1) {
90
+ ids.splice(index, 1);
81
91
  }
82
92
  },
83
93
 
@@ -87,12 +97,39 @@ export const PrivateManager = {
87
97
  },
88
98
 
89
99
  stack: () =>
90
- ids.map((id) => {
91
- return {
92
- id: id.split(":")[0],
93
- context: id.split(":")?.[1] || "global",
94
- };
95
- }),
100
+ ids.map((id) => ({
101
+ id: id.split(":")[0],
102
+ context: id.split(":")?.[1] || "global",
103
+ })),
104
+
105
+ /**
106
+ * Get the top-most sheet in the stack
107
+ */
108
+ topSheet: () => {
109
+ if (ids.length === 0) return null;
110
+ const topId = ids[ids.length - 1];
111
+ return {
112
+ id: topId.split(":")[0],
113
+ context: topId.split(":")?.[1] || "global",
114
+ };
115
+ },
116
+
117
+ /**
118
+ * Check if a sheet is currently visible
119
+ */
120
+ isSheetVisible: (id: string, context?: string): boolean => {
121
+ if (context) {
122
+ return ids.includes(makeKey(id, context));
123
+ }
124
+ return ids.some((key) => key.startsWith(`${id}:`));
125
+ },
126
+
127
+ /**
128
+ * Clear all history entries
129
+ */
130
+ clearHistory: () => {
131
+ PrivateManager.history = [];
132
+ },
96
133
  };
97
134
 
98
135
  class _SheetManager {
@@ -119,36 +156,67 @@ class _SheetManager {
119
156
  * Provide `context` of the `SheetProvider` where you want to show the action sheet.
120
157
  */
121
158
  context?: string;
159
+
160
+ /**
161
+ * Stack behavior for this sheet.
162
+ * - `switch`: (default) Closes current sheet, shows new one
163
+ * - `replace`: Swaps content with crossfade animation
164
+ * - `push`: Stacks new sheet on top of current
165
+ */
166
+ stackBehavior?: StackBehavior;
122
167
  },
123
168
  ): Promise<Sheets[SheetId]["returnValue"]> {
124
169
  return new Promise((resolve) => {
125
170
  const currentContext = PrivateManager.context({ ...options, id: id });
126
- const handler = (data: any, context = "global") => {
171
+ const behavior = options?.stackBehavior ?? "switch";
172
+
173
+ const handler = (
174
+ data: unknown,
175
+ context = "global",
176
+ _reopened?: boolean,
177
+ _behavior?: StackBehavior,
178
+ ) => {
127
179
  if (context !== "global" && currentContext && currentContext !== context)
128
180
  return;
129
- options?.onClose?.(data);
181
+ options?.onClose?.(data as Sheets[SheetId]["returnValue"]);
130
182
  sub?.unsubscribe();
131
- resolve(data);
183
+ resolve(data as Sheets[SheetId]["returnValue"]);
132
184
  };
133
185
 
134
- var sub = eventManager.subscribe(`onclose_${id}`, handler);
135
- PrivateManager.stack().forEach(({ id, context }) => {
136
- eventManager.publish(`hide_${id}`, undefined, context, true);
137
- });
186
+ const sub = eventManager.subscribe(`onclose_${id}`, handler);
187
+
188
+ // Handle existing sheets based on stack behavior
189
+ const currentStack = PrivateManager.stack();
190
+ if (currentStack.length > 0) {
191
+ currentStack.forEach(({ id: sheetId, context }) => {
192
+ eventManager.publish(
193
+ `hide_${sheetId}`,
194
+ undefined,
195
+ context,
196
+ true,
197
+ behavior,
198
+ );
199
+ });
200
+ }
138
201
 
139
202
  // Check if the sheet is registered with any `SheetProviders`.
140
203
  let isRegisteredWithSheetProvider = false;
141
- for (let ctx in sheetsRegistry) {
142
- for (let _id in sheetsRegistry[ctx]) {
204
+ for (const ctx in sheetsRegistry) {
205
+ for (const _id in sheetsRegistry[ctx]) {
143
206
  if (_id === id) {
144
207
  isRegisteredWithSheetProvider = true;
208
+ break;
145
209
  }
146
210
  }
211
+ if (isRegisteredWithSheetProvider) break;
147
212
  }
213
+
148
214
  eventManager.publish(
149
215
  isRegisteredWithSheetProvider ? `show_wrap_${id}` : `show_${id}`,
150
216
  options?.payload,
151
217
  currentContext || "global",
218
+ false,
219
+ behavior,
152
220
  );
153
221
  });
154
222
  }
@@ -157,7 +225,7 @@ class _SheetManager {
157
225
  * An async hide function. This is useful when you want to show one Sheet after closing another.
158
226
  *
159
227
  * @param id id of the Sheet to show
160
- * @param data
228
+ * @param options
161
229
  */
162
230
  async hide<SheetId extends keyof Sheets>(
163
231
  id: SheetId | (string & {}),
@@ -172,10 +240,11 @@ class _SheetManager {
172
240
  context?: string;
173
241
  },
174
242
  ): Promise<Sheets[SheetId]["returnValue"]> {
175
- let currentContext = PrivateManager.context({
243
+ const currentContext = PrivateManager.context({
176
244
  ...options,
177
245
  id: id,
178
246
  });
247
+
179
248
  return new Promise((resolve) => {
180
249
  let isRegisteredWithSheetProvider = false;
181
250
  // Check if the sheet is registered with any `SheetProviders`
@@ -188,14 +257,14 @@ class _SheetManager {
188
257
  }
189
258
  }
190
259
 
191
- const hideHandler = (data: any, context = "global") => {
260
+ const hideHandler = (data: unknown, context = "global") => {
192
261
  if (context !== "global" && currentContext && currentContext !== context)
193
262
  return;
194
263
  sub?.unsubscribe();
195
- resolve(data);
264
+ resolve(data as Sheets[SheetId]["returnValue"]);
196
265
  };
197
266
 
198
- var sub = eventManager.subscribe(`onclose_${id}`, hideHandler);
267
+ const sub = eventManager.subscribe(`onclose_${id}`, hideHandler);
199
268
  eventManager.publish(
200
269
  isRegisteredWithSheetProvider ? `hide_wrap_${id}` : `hide_${id}`,
201
270
  options?.payload,
@@ -210,11 +279,65 @@ class _SheetManager {
210
279
  * @param id Hide all sheets for the specific id.
211
280
  */
212
281
  hideAll<SheetId extends keyof Sheets>(id?: SheetId | (string & {})) {
282
+ // Clear history when hiding all sheets
283
+ PrivateManager.clearHistory();
284
+
213
285
  PrivateManager.stack().forEach(({ id: _id, context }) => {
214
286
  if (id && !_id.startsWith(id)) return;
215
287
  eventManager.publish(`hide_${_id}`, undefined, context);
216
288
  });
217
289
  }
290
+
291
+ /**
292
+ * Replace the current sheet with a new one using crossfade animation.
293
+ * This is a convenience method for show() with stackBehavior: 'replace'.
294
+ */
295
+ async replace<SheetId extends keyof Sheets>(
296
+ id: SheetId | (string & {}),
297
+ options?: {
298
+ payload?: Sheets[SheetId]["payload"];
299
+ onClose?: (data: Sheets[SheetId]["returnValue"] | undefined) => void;
300
+ context?: string;
301
+ },
302
+ ): Promise<Sheets[SheetId]["returnValue"]> {
303
+ return this.show(id, { ...options, stackBehavior: "replace" });
304
+ }
305
+
306
+ /**
307
+ * Push a new sheet on top of the current one, creating a stack.
308
+ * This is a convenience method for show() with stackBehavior: 'push'.
309
+ */
310
+ async push<SheetId extends keyof Sheets>(
311
+ id: SheetId | (string & {}),
312
+ options?: {
313
+ payload?: Sheets[SheetId]["payload"];
314
+ onClose?: (data: Sheets[SheetId]["returnValue"] | undefined) => void;
315
+ context?: string;
316
+ },
317
+ ): Promise<Sheets[SheetId]["returnValue"]> {
318
+ return this.show(id, { ...options, stackBehavior: "push" });
319
+ }
320
+
321
+ /**
322
+ * Pop the top sheet from the stack and restore the previous one.
323
+ * Only works when sheets were opened with stackBehavior: 'push'.
324
+ */
325
+ pop(): void {
326
+ const topSheet = PrivateManager.topSheet();
327
+ if (topSheet) {
328
+ eventManager.publish(`hide_${topSheet.id}`, undefined, topSheet.context);
329
+ }
330
+ }
331
+
332
+ /**
333
+ * Check if a specific sheet is currently visible.
334
+ */
335
+ isVisible<SheetId extends keyof Sheets>(
336
+ id: SheetId | (string & {}),
337
+ context?: string,
338
+ ): boolean {
339
+ return PrivateManager.isSheetVisible(id, context);
340
+ }
218
341
  }
219
342
 
220
343
  /**
package/src/provider.tsx CHANGED
@@ -1,7 +1,3 @@
1
- import { useSafeAreaInsets } from "react-native-safe-area-context";
2
- import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
3
- import { StatusBar } from "react-native";
4
- import React from "react";
5
1
  import Animated, {
6
2
  interpolate,
7
3
  interpolateColor,
@@ -11,10 +7,13 @@ import Animated, {
11
7
  useAnimatedStyle,
12
8
  useSharedValue,
13
9
  withSpring,
14
- withTiming,
15
10
  } from "react-native-reanimated";
11
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
12
+ import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
13
+ import { StatusBar } from "react-native";
14
+ import React from "react";
16
15
 
17
- import { BottomSheetInstance, SheetPayload, Sheets } from "./types";
16
+ import { BottomSheetInstance, SheetPayload, Sheets, StackBehavior } from "./types";
18
17
  import { eventManager } from "./events";
19
18
 
20
19
  export const providerRegistryStack: string[] = [];
@@ -50,6 +49,15 @@ export function registerSheet<SheetId extends keyof Sheets = never>(
50
49
  }
51
50
  }
52
51
 
52
+ /**
53
+ * Animation configuration for iOS modal sheet style animations.
54
+ * @deprecated Use duration prop directly instead
55
+ */
56
+ export interface ModalSheetAnimationConfig {
57
+ /** Duration of the animation in milliseconds */
58
+ duration: number;
59
+ }
60
+
53
61
  /**
54
62
  * The SheetProvider makes available the sheets in a given context. The default context is
55
63
  * `global`. However if you want to render a Sheet within another sheet or if you want to render
@@ -71,7 +79,7 @@ export function registerSheet<SheetId extends keyof Sheets = never>(
71
79
  export function SheetProvider({
72
80
  iosModalSheetTypeOfAnimation = false,
73
81
  context = "global",
74
- duration = 300,
82
+ duration = 150,
75
83
  children,
76
84
  }: React.PropsWithChildren<{
77
85
  context?: string;
@@ -82,17 +90,13 @@ export function SheetProvider({
82
90
  const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
83
91
  const sheetIds = Object.keys(sheetsRegistry[context] || sheetsRegistry["global"] || {});
84
92
 
85
- // Rerender when a new sheet is added.
86
- const onRegister = React.useCallback(forceUpdate, [forceUpdate]);
87
-
88
93
  // IOS modal sheet type of animation
89
94
  const isFullScreen = useSharedValue(-1);
90
95
  const colorStyle = useAnimatedStyle(() => ({
91
96
  flex: 1,
92
- backgroundColor: interpolateColor(
93
- isFullScreen.value,
94
- [0, 1],
95
- ["transparent", "#000"],
97
+ backgroundColor: withSpring(
98
+ interpolateColor(isFullScreen.value, [0, 1], ["transparent", "#000"]),
99
+ { duration },
96
100
  ),
97
101
  }));
98
102
  const animatedStyle = useAnimatedStyle(
@@ -102,14 +106,14 @@ export function SheetProvider({
102
106
  borderRadius: interpolate(isFullScreen.value, [0, 0.8, 1], [0, 20, 24], "clamp"),
103
107
  transform: [
104
108
  {
105
- scaleX: withTiming(
106
- interpolate(isFullScreen.value, [0, 0.98, 1], [1, 1, 0.92], "clamp"),
109
+ scaleX: withSpring(
110
+ interpolate(isFullScreen.value, [0, 0.8], [1, 0.92], "clamp"),
107
111
  { duration },
108
112
  ),
109
113
  },
110
114
  {
111
115
  translateY: withSpring(
112
- interpolate(isFullScreen.value, [0, 0.99, 1], [0, top, top + 5], "clamp"),
116
+ interpolate(isFullScreen.value, [0, 0.8, 1], [0, top, top + 5], "clamp"),
113
117
  { duration, dampingRatio: 1.5 },
114
118
  ),
115
119
  },
@@ -135,23 +139,23 @@ export function SheetProvider({
135
139
  providerRegistryStack.indexOf(context) > -1
136
140
  ? providerRegistryStack.indexOf(context)
137
141
  : providerRegistryStack.push(context) - 1;
138
- const unsub = eventManager.subscribe(`${context}-on-register`, onRegister);
142
+ const unsub = eventManager.subscribe(`${context}-on-register`, forceUpdate);
139
143
  return () => {
140
144
  providerRegistryStack.splice(providerRegistryStack.indexOf(context), 1);
141
145
  unsub?.unsubscribe();
142
146
  };
143
- }, [context, onRegister]);
147
+ }, [context, forceUpdate]);
144
148
 
145
149
  return (
146
150
  <SheetAnimationContext.Provider
147
- value={{ isFullScreen, iosModalSheetTypeOfAnimation }}
151
+ value={{ isFullScreen, iosModalSheetTypeOfAnimation, duration }}
148
152
  >
149
153
  <Animated.View style={colorStyle}>
150
154
  <Animated.View style={animatedStyle}>{children}</Animated.View>
151
155
  </Animated.View>
152
156
  <BottomSheetModalProvider>
153
157
  {sheetIds.map((id) => (
154
- <RenderSheet key={id} id={id} context={context} duration={duration} />
158
+ <RenderSheet key={id} id={id} context={context} />
155
159
  ))}
156
160
  </BottomSheetModalProvider>
157
161
  </SheetAnimationContext.Provider>
@@ -162,13 +166,31 @@ const SheetIDContext = React.createContext<string | undefined>(undefined);
162
166
  const SheetAnimationContext = React.createContext<{
163
167
  iosModalSheetTypeOfAnimation: boolean;
164
168
  isFullScreen: SharedValue<number>;
165
- }>({ isFullScreen: { value: 0 } as any, iosModalSheetTypeOfAnimation: false });
169
+ duration: number;
170
+ }>({
171
+ isFullScreen: { value: 0 } as SharedValue<number>,
172
+ iosModalSheetTypeOfAnimation: false,
173
+ duration: 300,
174
+ });
166
175
 
167
176
  export const SheetRefContext = React.createContext<
168
177
  React.RefObject<BottomSheetInstance | null>
169
- >({} as any);
178
+ >({} as React.RefObject<BottomSheetInstance | null>);
170
179
 
171
- const SheetPayloadContext = React.createContext<any>(undefined);
180
+ const SheetPayloadContext = React.createContext<unknown>(undefined);
181
+
182
+ // Stack behavior context for managing sheet transitions
183
+ interface StackBehaviorContextValue {
184
+ behavior: StackBehavior;
185
+ isTransitioning: boolean;
186
+ previousSheetId: string | null;
187
+ }
188
+
189
+ const StackBehaviorContext = React.createContext<StackBehaviorContextValue>({
190
+ behavior: "switch",
191
+ isTransitioning: false,
192
+ previousSheetId: null,
193
+ });
172
194
 
173
195
  /**
174
196
  * Get id of the current context.
@@ -182,6 +204,10 @@ export const useSheetIDContext = () => React.useContext(SheetIDContext);
182
204
  * Get the current sheet animation context.
183
205
  */
184
206
  export const useSheetAnimationContext = () => React.useContext(SheetAnimationContext);
207
+ /**
208
+ * Get stack behavior context for the current sheet.
209
+ */
210
+ export const useStackBehaviorContext = () => React.useContext(StackBehaviorContext);
185
211
  /**
186
212
  * Get the current Sheet's internal ref.
187
213
  */
@@ -205,25 +231,26 @@ export function useSheetPayload<SheetId extends keyof Sheets = never>() {
205
231
  export function useOnSheet<SheetId extends keyof Sheets = never>(
206
232
  id: SheetId | (string & {}),
207
233
  type: "show" | "hide" | "onclose",
208
- listener: (payload: SheetPayload<SheetId>, context: string, ...args: any[]) => void,
234
+ listener: (payload: SheetPayload<SheetId>, context: string, ...args: unknown[]) => void,
209
235
  ) {
210
236
  React.useEffect(() => {
211
237
  const subscription = eventManager.subscribe(`${type}_${id}`, listener);
212
238
  return () => subscription.unsubscribe();
213
- }, [id, listener]);
239
+ }, [id, listener, type]);
214
240
  }
215
241
 
216
- const RenderSheet = ({
217
- id,
218
- context,
219
- duration,
220
- }: {
242
+ interface RenderSheetProps {
221
243
  id: string;
222
244
  context: string;
223
- duration: number;
224
- }) => {
225
- const [payload, setPayload] = React.useState();
245
+ }
246
+
247
+ const RenderSheet = ({ id, context }: RenderSheetProps) => {
248
+ const [payload, setPayload] = React.useState<unknown>();
226
249
  const [visible, setVisible] = React.useState(false);
250
+ const [stackBehavior, setStackBehavior] = React.useState<StackBehavior>("switch");
251
+ const [isPending, startTransition] = React.useTransition();
252
+ const [previousSheetId, setPreviousSheetId] = React.useState<string | null>(null);
253
+
227
254
  const ref = React.useRef<BottomSheetInstance | null>(null);
228
255
  const Sheet = context.startsWith("$$-auto-")
229
256
  ? sheetsRegistry?.global?.[id]
@@ -232,29 +259,46 @@ const RenderSheet = ({
232
259
  : undefined;
233
260
 
234
261
  const onShow = React.useCallback(
235
- (data: any, ctx = "global", reopened?: boolean) => {
262
+ (data: unknown, ctx = "global", reopened?: boolean, behavior?: StackBehavior) => {
236
263
  if (ctx !== context) return;
237
- if (!reopened) setPayload(data);
238
- setVisible(true);
264
+
265
+ if (behavior) {
266
+ setStackBehavior(behavior);
267
+ }
268
+
269
+ if (!reopened) {
270
+ setPayload(data);
271
+ }
272
+
273
+ // Smooth transition handling using React's useTransition
274
+ startTransition(() => {
275
+ setVisible(true);
276
+ });
239
277
  },
240
278
  [context],
241
279
  );
242
280
 
243
281
  const onClose = React.useCallback(
244
- (_data: any, ctx = "global", reopened?: boolean) => {
282
+ (_data: unknown, ctx = "global", reopened?: boolean, nextSheetId?: string) => {
245
283
  if (context !== ctx) return;
284
+
285
+ if (nextSheetId) {
286
+ setPreviousSheetId(nextSheetId);
287
+ }
288
+
246
289
  if (!reopened) {
247
290
  setPayload(undefined);
248
- setTimeout(() => setVisible(false), Math.max(duration ?? 300, 300));
291
+ setVisible(false);
249
292
  } else {
250
293
  setVisible(false);
294
+ setPreviousSheetId(null);
251
295
  }
252
296
  },
253
297
  [context],
254
298
  );
255
299
 
256
300
  const onHide = React.useCallback(
257
- (data: any, ctx = "global") => {
301
+ (data: unknown, ctx = "global") => {
258
302
  eventManager.publish(`hide_${id}`, data, ctx);
259
303
  },
260
304
  [id],
@@ -267,7 +311,7 @@ const RenderSheet = ({
267
311
  }, [context, id, payload, visible]);
268
312
 
269
313
  React.useEffect(() => {
270
- let subs = [
314
+ const subs = [
271
315
  eventManager.subscribe(`show_wrap_${id}`, onShow),
272
316
  eventManager.subscribe(`onclose_${id}`, onClose),
273
317
  eventManager.subscribe(`hide_wrap_${id}`, onHide),
@@ -279,15 +323,25 @@ const RenderSheet = ({
279
323
 
280
324
  if (!Sheet) return null;
281
325
 
282
- return visible ? (
326
+ const stackContextValue: StackBehaviorContextValue = {
327
+ behavior: stackBehavior,
328
+ isTransitioning: isPending,
329
+ previousSheetId,
330
+ };
331
+
332
+ if (!visible) return null;
333
+
334
+ return (
283
335
  <ProviderContext.Provider value={context}>
284
336
  <SheetIDContext.Provider value={id}>
285
337
  <SheetRefContext.Provider value={ref}>
286
338
  <SheetPayloadContext.Provider value={payload}>
287
- <Sheet id={id} payload={payload} context={context} />
339
+ <StackBehaviorContext.Provider value={stackContextValue}>
340
+ <Sheet id={id} payload={payload} context={context} />
341
+ </StackBehaviorContext.Provider>
288
342
  </SheetPayloadContext.Provider>
289
343
  </SheetRefContext.Provider>
290
344
  </SheetIDContext.Provider>
291
345
  </ProviderContext.Provider>
292
- ) : null;
346
+ );
293
347
  };