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
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Registry for portal session counters per sheet.
3
+ * This persists across sheet deletions to ensure unique Portal/PortalHost names
4
+ * and work around react-native-teleport connection issues after replace flows.
5
+ */
6
+ const portalSessionRegistry = new Map<string, number>();
7
+
8
+ export function getNextPortalSession(sheetId: string): number {
9
+ const current = portalSessionRegistry.get(sheetId) ?? 0;
10
+ const next = current + 1;
11
+ portalSessionRegistry.set(sheetId, next);
12
+ return next;
13
+ }
14
+
15
+ export function getCurrentPortalSession(sheetId: string): number | undefined {
16
+ return portalSessionRegistry.get(sheetId);
17
+ }
18
+
19
+ /**
20
+ * Reset all portal sessions. Useful for testing.
21
+ * @internal
22
+ */
23
+ export function __resetPortalSessions(): void {
24
+ portalSessionRegistry.clear();
25
+ }
package/src/refsMap.ts CHANGED
@@ -16,3 +16,11 @@ export function setSheetRef(sheetId: string, ref: SheetRef): void {
16
16
  export function cleanupSheetRef(sheetId: string): void {
17
17
  sheetRefsMap.delete(sheetId);
18
18
  }
19
+
20
+ /**
21
+ * Reset all sheet refs. Useful for testing.
22
+ * @internal
23
+ */
24
+ export function __resetSheetRefs(): void {
25
+ sheetRefsMap.clear();
26
+ }
@@ -0,0 +1,65 @@
1
+ import type { BottomSheetState, BottomSheetStatus, OpenMode } from './types';
2
+
3
+ export const MODE_STATUS_MAP: Record<OpenMode, BottomSheetStatus | null> = {
4
+ push: null,
5
+ switch: 'hidden',
6
+ replace: 'closing',
7
+ };
8
+
9
+ export function isActivatableKeepMounted(
10
+ sheet: BottomSheetState | undefined
11
+ ): sheet is BottomSheetState {
12
+ return Boolean(sheet?.keepMounted && sheet.status === 'hidden');
13
+ }
14
+
15
+ export function isHidden(sheet: BottomSheetState | undefined): boolean {
16
+ return sheet?.status === 'hidden';
17
+ }
18
+
19
+ export function isOpening(sheet: BottomSheetState | undefined): boolean {
20
+ return sheet?.status === 'opening';
21
+ }
22
+
23
+ export function updateSheet(
24
+ sheetsById: Record<string, BottomSheetState>,
25
+ id: string,
26
+ update: Partial<BottomSheetState>
27
+ ): Record<string, BottomSheetState> {
28
+ const sheet = sheetsById[id];
29
+ if (!sheet) return sheetsById;
30
+
31
+ return {
32
+ ...sheetsById,
33
+ [id]: { ...sheet, ...update },
34
+ };
35
+ }
36
+
37
+ export function applyModeToTopSheet(
38
+ sheetsById: Record<string, BottomSheetState>,
39
+ stackOrder: string[],
40
+ mode: OpenMode
41
+ ): Record<string, BottomSheetState> {
42
+ const targetStatus = MODE_STATUS_MAP[mode];
43
+ if (!targetStatus) return sheetsById;
44
+
45
+ const topId = stackOrder[stackOrder.length - 1];
46
+ if (!topId || !sheetsById[topId]) return sheetsById;
47
+
48
+ return updateSheet(sheetsById, topId, { status: targetStatus });
49
+ }
50
+
51
+ export function removeFromStack(stackOrder: string[], id: string): string[] {
52
+ return stackOrder.filter((sheetId) => sheetId !== id);
53
+ }
54
+
55
+ export function getTopSheetId(stackOrder: string[]): string | undefined {
56
+ return stackOrder[stackOrder.length - 1];
57
+ }
58
+
59
+ export function getSheetBelowId(
60
+ stackOrder: string[],
61
+ id: string
62
+ ): string | undefined {
63
+ const index = stackOrder.indexOf(id);
64
+ return index > 0 ? stackOrder[index - 1] : undefined;
65
+ }
@@ -0,0 +1,50 @@
1
+ import { shallow } from 'zustand/shallow';
2
+ import { useBottomSheetStore } from './store';
3
+
4
+ // State hooks
5
+
6
+ export const useSheet = (id: string) =>
7
+ useBottomSheetStore((state) => state.sheetsById[id], shallow);
8
+
9
+ export const useSheetStatus = (id: string) =>
10
+ useBottomSheetStore((state) => state.sheetsById[id]?.status, shallow);
11
+
12
+ export const useSheetParams = (id: string) =>
13
+ useBottomSheetStore((state) => state.sheetsById[id]?.params, shallow);
14
+
15
+ export const useSheetContent = (id: string) =>
16
+ useBottomSheetStore((state) => state.sheetsById[id]?.content, shallow);
17
+
18
+ export const useSheetUsePortal = (id: string) =>
19
+ useBottomSheetStore((state) => state.sheetsById[id]?.usePortal, shallow);
20
+
21
+ export const useSheetKeepMounted = (id: string) =>
22
+ useBottomSheetStore((state) => state.sheetsById[id]?.keepMounted, shallow);
23
+ export const useSheetPortalSession = (id: string) =>
24
+ useBottomSheetStore((state) => state.sheetsById[id]?.portalSession, shallow);
25
+
26
+ export const useSheetExists = (id: string) =>
27
+ useBottomSheetStore((state) => !!state.sheetsById[id], shallow);
28
+
29
+ export const useIsSheetOpen = (id: string) =>
30
+ useBottomSheetStore((state) => {
31
+ const status = state.sheetsById[id]?.status;
32
+ return status === 'open' || status === 'opening';
33
+ }, shallow);
34
+
35
+ // Action hooks
36
+
37
+ export const useOpen = () => useBottomSheetStore((state) => state.open);
38
+
39
+ export const useStartClosing = () =>
40
+ useBottomSheetStore((state) => state.startClosing);
41
+
42
+ export const useUpdateParams = () =>
43
+ useBottomSheetStore((state) => state.updateParams);
44
+
45
+ export const useClearGroup = () =>
46
+ useBottomSheetStore((state) => state.clearGroup);
47
+
48
+ export const useMount = () => useBottomSheetStore((state) => state.mount);
49
+
50
+ export const useUnmount = () => useBottomSheetStore((state) => state.unmount);
@@ -0,0 +1,4 @@
1
+ export * from './types';
2
+ export * from './store';
3
+ export * from './hooks';
4
+ export { getTopSheetId } from './helpers';
@@ -0,0 +1,168 @@
1
+ import { subscribeWithSelector } from 'zustand/middleware';
2
+ import { createWithEqualityFn as create } from 'zustand/traditional';
3
+
4
+ import {
5
+ applyModeToTopSheet,
6
+ getSheetBelowId,
7
+ getTopSheetId,
8
+ isActivatableKeepMounted,
9
+ isHidden,
10
+ isOpening,
11
+ removeFromStack,
12
+ updateSheet,
13
+ } from './helpers';
14
+ import { getNextPortalSession } from '../portalSessionRegistry';
15
+ import type { BottomSheetState, BottomSheetStore } from './types';
16
+
17
+ export const useBottomSheetStore = create(
18
+ subscribeWithSelector<BottomSheetStore>((set) => ({
19
+ sheetsById: {},
20
+ stackOrder: [],
21
+
22
+ open: (sheet, mode = 'push') =>
23
+ set((state) => {
24
+ const existingSheet = state.sheetsById[sheet.id];
25
+
26
+ if (existingSheet && !isActivatableKeepMounted(existingSheet)) {
27
+ return state;
28
+ }
29
+
30
+ const updatedSheetsById = applyModeToTopSheet(
31
+ state.sheetsById,
32
+ state.stackOrder,
33
+ mode
34
+ );
35
+
36
+ // Get next portalSession from registry for portal-based sheets
37
+ // Registry persists across sheet deletions to ensure unique Portal/PortalHost names
38
+ const nextPortalSession = sheet.usePortal
39
+ ? getNextPortalSession(sheet.id)
40
+ : undefined;
41
+
42
+ const newSheet: BottomSheetState = existingSheet
43
+ ? {
44
+ ...existingSheet,
45
+ status: 'opening',
46
+ scaleBackground:
47
+ sheet.scaleBackground ?? existingSheet.scaleBackground,
48
+ params: sheet.params ?? existingSheet.params,
49
+ portalSession: nextPortalSession ?? existingSheet.portalSession,
50
+ }
51
+ : { ...sheet, status: 'opening', portalSession: nextPortalSession };
52
+
53
+ return {
54
+ sheetsById: { ...updatedSheetsById, [sheet.id]: newSheet },
55
+ stackOrder: [...state.stackOrder, sheet.id],
56
+ };
57
+ }),
58
+
59
+ markOpen: (id) =>
60
+ set((state) => {
61
+ if (!state.sheetsById[id]) return state;
62
+ return {
63
+ sheetsById: updateSheet(state.sheetsById, id, { status: 'open' }),
64
+ };
65
+ }),
66
+
67
+ startClosing: (id) =>
68
+ set((state) => {
69
+ const sheet = state.sheetsById[id];
70
+ if (!sheet || isHidden(sheet) || isOpening(sheet)) return state;
71
+
72
+ let updatedSheetsById = updateSheet(state.sheetsById, id, {
73
+ status: 'closing',
74
+ });
75
+
76
+ const belowId = getSheetBelowId(state.stackOrder, id);
77
+ if (belowId && isHidden(updatedSheetsById[belowId])) {
78
+ updatedSheetsById = updateSheet(updatedSheetsById, belowId, {
79
+ status: 'opening',
80
+ });
81
+ }
82
+
83
+ return { sheetsById: updatedSheetsById };
84
+ }),
85
+
86
+ finishClosing: (id) =>
87
+ set((state) => {
88
+ const sheet = state.sheetsById[id];
89
+ if (!sheet) return state;
90
+
91
+ let updatedSheetsById = { ...state.sheetsById };
92
+
93
+ if (sheet.keepMounted) {
94
+ updatedSheetsById = updateSheet(updatedSheetsById, id, {
95
+ status: 'hidden',
96
+ });
97
+ } else {
98
+ delete updatedSheetsById[id];
99
+ }
100
+
101
+ const newStackOrder = removeFromStack(state.stackOrder, id);
102
+ const topId = getTopSheetId(newStackOrder);
103
+
104
+ if (topId && isHidden(updatedSheetsById[topId])) {
105
+ updatedSheetsById = updateSheet(updatedSheetsById, topId, {
106
+ status: 'opening',
107
+ });
108
+ }
109
+
110
+ return {
111
+ sheetsById: updatedSheetsById,
112
+ stackOrder: newStackOrder,
113
+ };
114
+ }),
115
+
116
+ updateParams: (id, params) =>
117
+ set((state) => {
118
+ if (!state.sheetsById[id]) return state;
119
+ return { sheetsById: updateSheet(state.sheetsById, id, { params }) };
120
+ }),
121
+
122
+ clearGroup: (groupId) =>
123
+ set((state) => {
124
+ const idsToRemove = new Set(
125
+ Object.keys(state.sheetsById).filter(
126
+ (id) => state.sheetsById[id]?.groupId === groupId
127
+ )
128
+ );
129
+
130
+ if (idsToRemove.size === 0) return state;
131
+
132
+ const updatedSheetsById = { ...state.sheetsById };
133
+ idsToRemove.forEach((id) => delete updatedSheetsById[id]);
134
+
135
+ return {
136
+ sheetsById: updatedSheetsById,
137
+ stackOrder: state.stackOrder.filter((id) => !idsToRemove.has(id)),
138
+ };
139
+ }),
140
+
141
+ clearAll: () => set({ sheetsById: {}, stackOrder: [] }),
142
+
143
+ mount: (sheet) =>
144
+ set((state) => {
145
+ if (state.sheetsById[sheet.id]) return state;
146
+
147
+ return {
148
+ sheetsById: {
149
+ ...state.sheetsById,
150
+ [sheet.id]: { ...sheet, status: 'hidden' },
151
+ },
152
+ };
153
+ }),
154
+
155
+ unmount: (id) =>
156
+ set((state) => {
157
+ if (!state.sheetsById[id]) return state;
158
+
159
+ const updatedSheetsById = { ...state.sheetsById };
160
+ delete updatedSheetsById[id];
161
+
162
+ return {
163
+ sheetsById: updatedSheetsById,
164
+ stackOrder: removeFromStack(state.stackOrder, id),
165
+ };
166
+ }),
167
+ }))
168
+ );
@@ -0,0 +1,42 @@
1
+ import { type ReactNode } from 'react';
2
+
3
+ export type BottomSheetStatus = 'opening' | 'open' | 'closing' | 'hidden';
4
+ export type OpenMode = 'push' | 'switch' | 'replace';
5
+
6
+ export interface BottomSheetState {
7
+ groupId: string;
8
+ id: string;
9
+ content?: ReactNode;
10
+ status: BottomSheetStatus;
11
+ scaleBackground?: boolean;
12
+ usePortal?: boolean;
13
+ params?: Record<string, unknown>;
14
+ keepMounted?: boolean;
15
+ /**
16
+ * Incremented each time a portal-based sheet is opened.
17
+ * Used to create unique Portal/PortalHost names to work around
18
+ * react-native-teleport connection issues after replace flows.
19
+ */
20
+ portalSession?: number;
21
+ }
22
+
23
+ export type TriggerState = Omit<BottomSheetState, 'status'>;
24
+
25
+ export interface BottomSheetStoreState {
26
+ sheetsById: Record<string, BottomSheetState>;
27
+ stackOrder: string[];
28
+ }
29
+
30
+ export interface BottomSheetStoreActions {
31
+ open(sheet: TriggerState, mode?: OpenMode): void;
32
+ markOpen(id: string): void;
33
+ startClosing(id: string): void;
34
+ finishClosing(id: string): void;
35
+ updateParams(id: string, params: Record<string, unknown> | undefined): void;
36
+ clearGroup(groupId: string): void;
37
+ clearAll(): void;
38
+ mount(sheet: Omit<BottomSheetState, 'status'>): void;
39
+ unmount(id: string): void;
40
+ }
41
+
42
+ export type BottomSheetStore = BottomSheetStoreState & BottomSheetStoreActions;
@@ -1,6 +1,5 @@
1
- import { shallow } from 'zustand/shallow';
2
1
  import { useMaybeBottomSheetContext } from './BottomSheet.context';
3
- import { useBottomSheetStore } from './bottomSheet.store';
2
+ import { useSheetParams, useStartClosing } from './bottomSheet.store';
4
3
  import type {
5
4
  BottomSheetPortalId,
6
5
  BottomSheetPortalParams,
@@ -24,27 +23,19 @@ export function useBottomSheetContext<
24
23
  T extends BottomSheetPortalId,
25
24
  >(): UseBottomSheetContextReturn<BottomSheetPortalParams<T> | unknown> {
26
25
  const context = useMaybeBottomSheetContext();
26
+ const params = useSheetParams(context?.id || '');
27
+ const startClosing = useStartClosing();
27
28
 
28
- const { id, params } = useBottomSheetStore(
29
- (state) => ({
30
- id: state.sheetsById[context?.id!]?.id,
31
- params: state.sheetsById[context?.id!]?.params,
32
- }),
33
- shallow
34
- );
35
-
36
- const startClosing = useBottomSheetStore((state) => state.startClosing);
37
-
38
- if (!id) {
29
+ if (!context?.id) {
39
30
  throw new Error(
40
31
  'useBottomSheetContext must be used within a BottomSheet component'
41
32
  );
42
33
  }
43
34
 
44
- const close = () => startClosing(id);
35
+ const close = () => startClosing(context.id);
45
36
 
46
37
  return {
47
- id,
38
+ id: context.id,
48
39
  params: params as BottomSheetPortalParams<T>,
49
40
  close,
50
41
  closeBottomSheet: close,
@@ -1,14 +1,19 @@
1
1
  import React from 'react';
2
2
  import type { BottomSheetMethods } from '@gorhom/bottom-sheet/lib/typescript/types';
3
3
 
4
- import { useBottomSheetStore, type OpenMode } from './bottomSheet.store';
4
+ import {
5
+ useOpen,
6
+ useStartClosing,
7
+ useUpdateParams,
8
+ type OpenMode,
9
+ } from './bottomSheet.store';
5
10
  import { useMaybeBottomSheetManagerContext } from './BottomSheetManager.provider';
6
11
  import type {
7
12
  BottomSheetPortalId,
8
13
  BottomSheetPortalParams,
9
14
  HasParams,
10
15
  } from './portal.types';
11
- import { setSheetRef } from './refsMap';
16
+ import { getSheetRef, setSheetRef } from './refsMap';
12
17
 
13
18
  interface BaseOpenOptions<TParams> {
14
19
  mode?: OpenMode;
@@ -41,15 +46,19 @@ export function useBottomSheetControl<T extends BottomSheetPortalId>(
41
46
  ): UseBottomSheetControlReturn<T> {
42
47
  const bottomSheetManagerContext = useMaybeBottomSheetManagerContext();
43
48
 
44
- const storeOpen = useBottomSheetStore((state) => state.open);
45
- const startClosing = useBottomSheetStore((state) => state.startClosing);
46
- const storeUpdateParams = useBottomSheetStore((state) => state.updateParams);
49
+ const storeOpen = useOpen();
50
+ const startClosing = useStartClosing();
51
+ const storeUpdateParams = useUpdateParams();
47
52
 
48
53
  const open = (options?: OpenOptions<T>) => {
49
54
  const groupId = bottomSheetManagerContext?.groupId || 'default';
50
55
 
51
- const ref = React.createRef<BottomSheetMethods>();
52
- setSheetRef(id, ref);
56
+ // Only create ref if it doesn't exist (keepMounted sheets already have one)
57
+ const existingRef = getSheetRef(id);
58
+ if (!existingRef) {
59
+ const ref = React.createRef<BottomSheetMethods>();
60
+ setSheetRef(id, ref);
61
+ }
53
62
 
54
63
  storeOpen(
55
64
  {
@@ -1,22 +1,21 @@
1
1
  import React from 'react';
2
2
 
3
- import { useBottomSheetStore, type OpenMode } from './bottomSheet.store';
3
+ import {
4
+ useOpen,
5
+ useStartClosing,
6
+ useClearGroup,
7
+ type OpenMode,
8
+ } from './bottomSheet.store';
4
9
  import { useMaybeBottomSheetManagerContext } from './BottomSheetManager.provider';
5
10
  import { setSheetRef } from './refsMap';
6
11
  import type { BottomSheetMethods } from '@gorhom/bottom-sheet/lib/typescript/types';
7
- import { shallow } from 'zustand/shallow';
8
12
 
9
13
  export const useBottomSheetManager = () => {
10
14
  const bottomSheetManagerContext = useMaybeBottomSheetManagerContext();
11
15
 
12
- const { storeOpen, startClosing, storeClearGroup } = useBottomSheetStore(
13
- (store) => ({
14
- storeOpen: store.open,
15
- storeClearGroup: store.clearGroup,
16
- startClosing: store.startClosing,
17
- }),
18
- shallow
19
- );
16
+ const storeOpen = useOpen();
17
+ const startClosing = useStartClosing();
18
+ const storeClearGroup = useClearGroup();
20
19
 
21
20
  const openBottomSheet = (
22
21
  content: React.ReactElement,
@@ -1,25 +1,15 @@
1
- import {
2
- useBottomSheetStore,
3
- type BottomSheetStatus,
4
- } from './bottomSheet.store';
5
- import type { BottomSheetPortalId } from './portal.types';
1
+ import { useSheetStatus, type BottomSheetStatus } from './bottomSheet.store';
6
2
 
7
3
  export interface UseBottomSheetStatusReturn {
8
4
  status: BottomSheetStatus | null;
9
5
  isOpen: boolean;
10
6
  }
11
7
 
12
- export function useBottomSheetStatus(
13
- id: BottomSheetPortalId
14
- ): UseBottomSheetStatusReturn {
15
- const status = useBottomSheetStore(
16
- (state) => state.sheetsById[id]?.status ?? null
17
- );
18
-
19
- const isOpen = status === 'open' || status === 'opening';
8
+ export function useBottomSheetStatus(id: string): UseBottomSheetStatusReturn {
9
+ const status = useSheetStatus(id) ?? null;
20
10
 
21
11
  return {
22
12
  status,
23
- isOpen,
13
+ isOpen: status === 'open' || status === 'opening',
24
14
  };
25
15
  }
@@ -0,0 +1,17 @@
1
+ import { useCallback, useLayoutEffect, useRef } from 'react';
2
+
3
+ // biome-ignore lint/suspicious/noExplicitAny: No better alternative available.
4
+ type CallbackType = (...args: any[]) => any;
5
+
6
+ // RFC: https://github.com/reactjs/rfcs/blob/useevent/text/0000-useevent.md
7
+ export const useEvent = <T extends CallbackType>(callback: T) => {
8
+ const callbackRef = useRef(callback);
9
+
10
+ useLayoutEffect(() => {
11
+ callbackRef.current = callback;
12
+ });
13
+
14
+ return useCallback((...args: Parameters<T>): ReturnType<T> => {
15
+ return callbackRef.current(...args);
16
+ }, []);
17
+ };
@@ -2,15 +2,12 @@ import { useRef } from 'react';
2
2
  import {
3
3
  useAnimatedStyle,
4
4
  useDerivedValue,
5
- withTiming,
6
5
  withSpring,
7
- type WithTimingConfig,
6
+ withTiming,
8
7
  type WithSpringConfig,
8
+ type WithTimingConfig,
9
9
  } from 'react-native-reanimated';
10
- import {
11
- useBottomSheetStore,
12
- type BottomSheetStore,
13
- } from './bottomSheet.store';
10
+ import { useBottomSheetStore } from './bottomSheet.store';
14
11
  import { useBottomSheetManagerContext } from './BottomSheetManager.provider';
15
12
 
16
13
  export type ScaleAnimationConfig =
@@ -40,55 +37,66 @@ const DEFAULT_CONFIG = {
40
37
  animation: DEFAULT_ANIMATION,
41
38
  } satisfies Required<ScaleConfig>;
42
39
 
43
- /**
44
- * Returns the number of sheets with scaleBackground above a given element.
45
- * For background wrapper, pass undefined as sheetId - returns 0 or 1 (binary).
46
- * For sheets, returns the count of scaleBackground sheets above it.
47
- * Uses shallow comparison internally for optimal re-renders.
48
- */
49
- export function useScaleDepth(groupId: string, sheetId?: string): number {
40
+ function useBackgroundScaleDepth(groupId: string): number {
41
+ return useBottomSheetStore((state) => {
42
+ const { stackOrder, sheetsById } = state;
43
+
44
+ for (let i = 0; i < stackOrder.length; i++) {
45
+ const id = stackOrder[i]!;
46
+ const sheet = sheetsById[id];
47
+ if (
48
+ sheet &&
49
+ sheet.groupId === groupId &&
50
+ sheet.status !== 'closing' &&
51
+ sheet.status !== 'hidden'
52
+ ) {
53
+ return sheet.scaleBackground ? 1 : 0;
54
+ }
55
+ }
56
+ return 0;
57
+ });
58
+ }
59
+
60
+ function useSheetScaleDepth(
61
+ groupId: string,
62
+ sheetId: string | undefined
63
+ ): number {
50
64
  const prevDepthRef = useRef(0);
51
65
 
52
- const scaleDepthSelector = (state: BottomSheetStore) => {
53
- const { stackOrder, sheetsById } = state;
66
+ return useBottomSheetStore((state) => {
67
+ if (!sheetId) {
68
+ return 0;
69
+ }
54
70
 
55
- const startIndex = sheetId ? stackOrder.indexOf(sheetId) + 1 : 0;
71
+ const { stackOrder, sheetsById } = state;
72
+ const sheetIndex = stackOrder.indexOf(sheetId);
56
73
 
57
- if (sheetId && startIndex === 0) {
74
+ if (sheetIndex === -1) {
58
75
  return prevDepthRef.current;
59
76
  }
60
77
 
61
78
  let depth = 0;
62
- for (let i = startIndex; i < stackOrder.length; i++) {
79
+ for (let i = sheetIndex + 1; i < stackOrder.length; i++) {
63
80
  const id = stackOrder[i]!;
64
81
  const sheet = sheetsById[id];
65
82
  if (
66
83
  sheet &&
67
84
  sheet.groupId === groupId &&
68
85
  sheet.scaleBackground &&
69
- sheet.status !== 'closing'
86
+ sheet.status !== 'closing' &&
87
+ sheet.status !== 'hidden'
70
88
  ) {
71
89
  depth++;
72
- if (!sheetId) {
73
- break;
74
- }
75
90
  }
76
91
  }
77
92
 
78
93
  prevDepthRef.current = depth;
79
94
  return depth;
80
- };
81
-
82
- return useBottomSheetStore(scaleDepthSelector);
95
+ });
83
96
  }
84
97
 
85
- /**
86
- * Returns animated style for scale effect based on depth.
87
- * Uses power scaling: scale^depth for cascading effect.
88
- */
89
98
  export function useScaleAnimatedStyle({ id }: { id?: string } = {}) {
90
99
  const { groupId, scaleConfig } = useBottomSheetManagerContext();
91
- const scaleDepth = useScaleDepth(groupId, id);
92
100
 
93
101
  const {
94
102
  scale = DEFAULT_CONFIG.scale,
@@ -97,6 +105,14 @@ export function useScaleAnimatedStyle({ id }: { id?: string } = {}) {
97
105
  animation = DEFAULT_CONFIG.animation,
98
106
  } = scaleConfig ?? {};
99
107
 
108
+ const isBackground = id === undefined;
109
+
110
+ const backgroundDepth = useBackgroundScaleDepth(groupId);
111
+ const sheetDepth = useSheetScaleDepth(groupId, id);
112
+
113
+ const scaleDepth = isBackground ? backgroundDepth : sheetDepth;
114
+
115
+ // Animate the depth change
100
116
  const progress = useDerivedValue(() => {
101
117
  if (animation.type === 'spring') {
102
118
  return withSpring(scaleDepth, animation.config);