@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
package/README.md CHANGED
@@ -1,131 +1,477 @@
1
1
  # Bottom Sheet Router & Manager
2
2
 
3
- A bottom sheet manager inspired by package [react-native-actions-sheet](https://github.com/ammarahm-ed/@repo/bottom-sheet) and adapted to package [@gorhom/bottom-sheet](https://github.com/gorhom/react-native-bottom-sheet).
3
+ A powerful bottom sheet manager and router for React Native, inspired by [react-native-actions-sheet](https://github.com/ammarahm-ed/@repo/bottom-sheet) and built on top of [@gorhom/bottom-sheet](https://github.com/gorhom/react-native-bottom-sheet).
4
4
 
5
+ ## Features
5
6
 
6
- ## Installation
7
+ - 🎯 **Simple API** - Show/hide sheets from anywhere in your app
8
+ - 🔄 **Stack Behaviors** - Control how sheets stack with `push`, `switch`, or `replace` behaviors
9
+ - 🧭 **React Navigation Integration** - Full support for React Navigation v6/v7 and Expo Router
10
+ - 📱 **iOS 18 Modal Animation** - Native-like modal sheet animations
11
+ - 🎨 **TypeScript Support** - Full type safety with IntelliSense
12
+ - ⚡ **Performance Optimized** - High-performance event system with O(1) lookups
13
+ - 🔌 **Flexible Hooks** - Rich set of hooks for advanced use cases
7
14
 
8
- Add it to your project with package.json:
15
+ ## Installation
9
16
 
10
17
  ```bash
11
18
  npm install @niibase/bottom-sheet-manager
12
19
  ```
13
20
 
14
- ## Usage
21
+ ## Quick Start
22
+
23
+ ### Basic Usage
15
24
 
16
- `SheetManager` is great because it helps you save lots of development time. One great feature is that you can reuse the same modal sheet in the app and don't have to create or define it in multiple places. Another is that you don't have to write boilerplate for every component. Everything just works.
25
+ `SheetManager` helps you save development time by allowing you to reuse modal sheets throughout your app without boilerplate.
17
26
 
18
27
  ```tsx
19
- import { BottomSheet } from '@niibase/bottom-sheet-manager';
28
+ import { BottomSheet, SheetManager } from "@niibase/bottom-sheet-manager";
29
+ import type { SheetProps } from "@niibase/bottom-sheet-manager";
20
30
  ```
21
31
 
22
- Create your BottomSheet component and export it.
32
+ Create your BottomSheet component:
23
33
 
24
34
  ```tsx
25
- function ExampleSheet({ id }: SheetProps<"example">) {
26
- return (
27
- <BottomSheet id={id}>
28
- <View>
29
- <Text>Hello World</Text>
30
- </View>
31
- </BottomSheet>
32
- );
35
+ function ExampleSheet({ id }: SheetProps<"example-sheet">) {
36
+ return (
37
+ <BottomSheet id={id} snapPoints={["50%", "90%"]}>
38
+ <BottomSheet.View>
39
+ <Text>Hello World</Text>
40
+ </BottomSheet.View>
41
+ </BottomSheet>
42
+ );
33
43
  }
34
44
 
35
45
  export default ExampleSheet;
36
46
  ```
37
47
 
38
- Create a `sheets.ts` file and import your sheet then register it.
48
+ Register your sheet **at module level** in a `sheets.ts` file (never inside JSX):
39
49
 
40
50
  ```ts
41
- import {registerSheet} from '@niibase/bottom-sheet-manager';
42
- import ExampleSheet from 'example-sheet';
51
+ import type { SheetDefinition } from "@niibase/bottom-sheet-manager";
52
+ import { registerSheet } from "@niibase/bottom-sheet-manager";
43
53
 
44
- registerSheet('example-sheet', ExampleSheet);
54
+ import ExampleSheet from "./ExampleSheet";
45
55
 
46
- // We extend some of the types here to give us great intellisense
47
- // across the app for all registered sheets.
48
- declare module '@niibase/bottom-sheet-manager' {
49
- interface Sheets {
50
- 'example-sheet': SheetDefinition;
51
- }
52
- }
56
+ // Correct: register at module level
57
+ registerSheet("example-sheet", ExampleSheet);
53
58
 
54
- export {};
59
+ // Extend types for IntelliSense
60
+ declare module "@niibase/bottom-sheet-manager" {
61
+ interface Sheets {
62
+ "example-sheet": SheetDefinition;
63
+ }
64
+ }
55
65
  ```
56
66
 
57
- In `App.js` import `sheets.ts` and wrap your app in `SheetProvider`.
67
+ Import `sheets.ts` in your app entry point, then wrap your app with `SheetProvider`:
58
68
 
59
69
  ```tsx
60
- import { SheetProvider } from '@niibase/bottom-sheet-manager';
61
- import 'sheets.tsx';
70
+ import { SheetProvider } from "@niibase/bottom-sheet-manager";
71
+
72
+ import "./sheets";
62
73
 
63
74
  function App() {
64
- return (
65
- <SheetProvider>
66
- {
67
- // your app components
68
- }
69
- </SheetProvider>
70
- );
75
+ return <SheetProvider>{/* your app components */}</SheetProvider>;
71
76
  }
72
77
  ```
73
78
 
74
- Open the modal sheet from anywhere in the app.
79
+ Show and hide sheets:
80
+
81
+ ```tsx
82
+ // Show a sheet
83
+ SheetManager.show("example-sheet");
84
+
85
+ // Show with payload
86
+ SheetManager.show("example-sheet", { payload: { userId: 123 } });
75
87
 
76
- ```jsx
77
- SheetManager.show('example-sheet');
88
+ // Hide a sheet
89
+ SheetManager.hide("example-sheet");
90
+
91
+ // Hide and get return value
92
+ const result = await SheetManager.show("example-sheet");
93
+
94
+ // Check if a sheet is visible
95
+ SheetManager.isVisible("example-sheet"); // boolean
96
+
97
+ // Get the sheet instance directly
98
+ const instance = SheetManager.get("example-sheet");
99
+ instance?.expand();
78
100
  ```
79
101
 
80
- Hide the modal sheet
102
+ ## Stack Behaviors
103
+
104
+ Control how sheets behave when opened on top of existing sheets:
81
105
 
82
- ```jsx
83
- SheetManager.hide('example-sheet');
106
+ - **`switch`** (default): Dismisses the current sheet before showing the new one. Previous sheet is restored when new one closes.
107
+ - **`replace`**: Swaps the current sheet's content with smooth crossfade animation. Previous sheet is removed from stack.
108
+ - **`push`**: Pushes new sheet on top, creating a navigable stack. Previous sheet remains visible underneath.
109
+
110
+ ```tsx
111
+ // Set stack behavior per sheet
112
+ <BottomSheet
113
+ id={id}
114
+ stackBehavior="push" // or "switch" or "replace"
115
+ snapPoints={["50%", "90%"]}
116
+ >
117
+ {/* content */}
118
+ </BottomSheet>
84
119
  ```
85
120
 
86
- ### As a React Navigation Screen
121
+ ## React Navigation Integration
87
122
 
88
- Support for React Navigation v6 and v7 is available including expo router support.
123
+ Full support for React Navigation v6/v7 and Expo Router. The first screen in the navigator is rendered as main content, and subsequent screens are rendered as bottom sheet modals.
124
+
125
+ ### With Expo Router
89
126
 
90
127
  ```tsx
128
+ import { Slot, withLayoutContext } from "expo-router";
129
+
91
130
  import {
92
- createBottomSheetNavigator,
93
- BottomSheetNavigationOptions,
94
- BottomSheetNavigationEventMap,
95
- BottomSheetNavigationState,
131
+ BottomSheetNavigationEventMap,
132
+ BottomSheetNavigationOptions,
133
+ BottomSheetNavigationState,
134
+ createBottomSheetNavigator,
96
135
  } from "@niibase/bottom-sheet-manager";
97
- import { Slot, withLayoutContext } from "expo-router";
98
136
 
99
137
  const { Navigator } = createBottomSheetNavigator();
100
138
  const BottomSheet = withLayoutContext<
101
- BottomSheetNavigationOptions,
102
- typeof Navigator,
103
- BottomSheetNavigationState<any>,
104
- BottomSheetNavigationEventMap
139
+ BottomSheetNavigationOptions,
140
+ typeof Navigator,
141
+ BottomSheetNavigationState<any>,
142
+ BottomSheetNavigationEventMap
105
143
  >(Navigator);
106
144
 
107
145
  export const unstable_settings = {
108
- initialRouteName: "index",
146
+ initialRouteName: "index",
109
147
  };
110
148
 
111
149
  export default function Layout() {
112
- if (typeof window === "undefined") return <Slot />;
113
- return (
114
- <BottomSheet
115
- screenOptions={
116
- {
117
- // API Reference: `@niibase/bottom-sheet-manager/router/types.ts`
118
- // And: https://gorhom.github.io/react-native-bottom-sheet/modal/props/
119
- }
120
- }
121
- />
122
- );
150
+ // SSR guard - navigator doesn't work on server
151
+ if (typeof window === "undefined") return <Slot />;
152
+
153
+ return (
154
+ <BottomSheet
155
+ screenOptions={{
156
+ snapPoints: ["50%", "90%"],
157
+ // See: https://gorhom.github.io/react-native-bottom-sheet/modal/props/
158
+ }}
159
+ />
160
+ );
161
+ }
162
+ ```
163
+
164
+ ### With React Navigation
165
+
166
+ ```tsx
167
+ import { createBottomSheetNavigator } from "@niibase/bottom-sheet-manager";
168
+
169
+ const { Navigator, Screen } = createBottomSheetNavigator();
170
+
171
+ function App() {
172
+ return (
173
+ <Navigator>
174
+ <Screen name="Home" component={HomeScreen} />
175
+ <Screen
176
+ name="Details"
177
+ component={DetailsSheet}
178
+ options={{
179
+ snapPoints: ["50%", "100%"],
180
+ enableBlurKeyboardOnGesture: true,
181
+ }}
182
+ />
183
+ </Navigator>
184
+ );
185
+ }
186
+ ```
187
+
188
+ ### Navigation Actions
189
+
190
+ Use the navigation object to control sheets programmatically:
191
+
192
+ ```tsx
193
+ import { useBottomSheetNavigation } from "@niibase/bottom-sheet-manager";
194
+
195
+ function MySheet() {
196
+ const navigation = useBottomSheetNavigation();
197
+
198
+ // Snap to a specific index
199
+ const handleExpand = () => {
200
+ navigation.snapTo(1); // Snap to second snap point
201
+ };
202
+
203
+ // Dismiss the current sheet
204
+ const handleDismiss = () => {
205
+ navigation.dismiss();
206
+ };
207
+
208
+ return (
209
+ <View>
210
+ <Button title="Expand" onPress={handleExpand} />
211
+ <Button title="Dismiss" onPress={handleDismiss} />
212
+ </View>
213
+ );
214
+ }
215
+ ```
216
+
217
+ ## Hooks
218
+
219
+ ### `useBottomSheetNavigation`
220
+
221
+ Access navigation helpers including `snapTo()` and `dismiss()`:
222
+
223
+ ```tsx
224
+ import { useBottomSheetNavigation } from "@niibase/bottom-sheet-manager";
225
+
226
+ const navigation = useBottomSheetNavigation();
227
+ navigation.snapTo(1);
228
+ navigation.dismiss();
229
+ ```
230
+
231
+ ### `useSheetRef`
232
+
233
+ Get a ref to control the sheet instance. Always use optional chaining (`?.`) since `current` can be null before the sheet is mounted:
234
+
235
+ ```tsx
236
+ import { useSheetRef } from "@niibase/bottom-sheet-manager";
237
+
238
+ function MySheet({ id }: SheetProps<"my-sheet">) {
239
+ const ref = useSheetRef<"my-sheet">();
240
+
241
+ // Control the sheet — use ?. since current may be null
242
+ ref.current?.expand();
243
+ ref.current?.collapse();
244
+ ref.current?.snapToIndex(1);
245
+ ref.current?.close({ value: "result" });
246
+ }
247
+ ```
248
+
249
+ ### `useSheetPayload`
250
+
251
+ Access the payload passed when showing the sheet:
252
+
253
+ ```tsx
254
+ import { useSheetPayload } from "@niibase/bottom-sheet-manager";
255
+
256
+ function MySheet({ id }: SheetProps<"my-sheet">) {
257
+ const payload = useSheetPayload<"my-sheet">();
258
+ // payload is typed based on your SheetDefinition
123
259
  }
124
260
  ```
125
261
 
262
+ ### `useSheetStackBehavior`
263
+
264
+ Get the current stack behavior context:
265
+
266
+ ```tsx
267
+ import { useSheetStackBehavior } from "@niibase/bottom-sheet-manager";
268
+
269
+ function MySheet() {
270
+ const { behavior, isTransitioning, previousSheetId } = useSheetStackBehavior();
271
+ // behavior: "push" | "replace" | "switch"
272
+ }
273
+ ```
274
+
275
+ ### `useOnSheet`
276
+
277
+ Subscribe to sheet events. Takes the sheet id, an event type (`"show"`, `"hide"`, or `"onclose"`), and a listener:
278
+
279
+ ```tsx
280
+ import { useOnSheet } from "@niibase/bottom-sheet-manager";
281
+
282
+ function MyComponent() {
283
+ useOnSheet("my-sheet", "show", (payload, context) => {
284
+ console.log("Sheet shown with:", payload);
285
+ });
286
+
287
+ useOnSheet("my-sheet", "onclose", (returnValue, context) => {
288
+ console.log("Sheet closed with:", returnValue);
289
+ });
290
+ }
291
+ ```
292
+
293
+ ## iOS 18 Modal Animation
294
+
295
+ Enable native-like iOS 18 modal sheet animations:
296
+
297
+ ```tsx
298
+ <BottomSheet
299
+ id={id}
300
+ iosModalSheetTypeOfAnimation={true}
301
+ snapPoints={["50%", "90%", "100%"]}
302
+ >
303
+ {/* At 90% snap point, content behind scales down with border radius */}
304
+ </BottomSheet>
305
+ ```
306
+
307
+ Or in navigation options:
308
+
309
+ ```tsx
310
+ <Screen
311
+ name="Details"
312
+ component={DetailsSheet}
313
+ options={{
314
+ iosModalSheetTypeOfAnimation: true,
315
+ snapPoints: ["50%", "90%", "100%"],
316
+ }}
317
+ />
318
+ ```
319
+
320
+ > **Note:** When `iosModalSheetTypeOfAnimation` is `false` (the default), no animated wrappers are added to the component tree, keeping your app's layout overhead minimal.
321
+
322
+ ## Advanced Features
323
+
324
+ ### Custom Contexts
325
+
326
+ Use separate contexts for nested sheets or modals.
327
+
328
+ > **Important:** Context names must be unique across all `SheetProvider` instances in your app.
329
+
330
+ ```tsx
331
+ // Register at module level — not inside JSX
332
+ registerSheet("local-sheet", LocalSheet, "modal-context");
333
+
334
+ // Then use the provider where you want the sheet to appear
335
+ function MyModal() {
336
+ return <SheetProvider context="modal-context">{/* Modal content */}</SheetProvider>;
337
+ }
338
+ ```
339
+
340
+ ### Sheet Instance Methods
341
+
342
+ Control sheets programmatically:
343
+
344
+ ```tsx
345
+ // Get the sheet instance
346
+ const instance = SheetManager.get("example-sheet");
347
+
348
+ // Expand to maximum snap point
349
+ instance?.expand();
350
+
351
+ // Collapse to minimum snap point
352
+ instance?.collapse();
353
+
354
+ // Snap to specific index
355
+ instance?.snapToIndex(1);
356
+
357
+ // Snap to specific position
358
+ instance?.snapToPosition("75%");
359
+
360
+ // Close with return value
361
+ instance?.close({ value: { success: true } });
362
+ ```
363
+
364
+ ### Checking Visibility
365
+
366
+ ```tsx
367
+ // Check if a sheet is currently open
368
+ const isOpen = SheetManager.isVisible("example-sheet");
369
+
370
+ // Check in a specific context
371
+ const isOpen = SheetManager.isVisible("example-sheet", "modal-context");
372
+ ```
373
+
374
+ ### Animation Configuration
375
+
376
+ Customize animations:
377
+
378
+ ```tsx
379
+ instance?.expand({
380
+ animationConfigs: {
381
+ type: "spring",
382
+ damping: 20,
383
+ stiffness: 90,
384
+ },
385
+ });
386
+ ```
387
+
388
+ ### Reset (for Testing)
389
+
390
+ ```tsx
391
+ import { SheetManager } from "@niibase/bottom-sheet-manager";
392
+
393
+ // In your test setup/teardown
394
+ afterEach(() => {
395
+ SheetManager.reset();
396
+ });
397
+ ```
398
+
399
+ ## API Reference
400
+
401
+ ### `SheetManager`
402
+
403
+ Global manager for showing and hiding sheets.
404
+
405
+ | Method | Signature | Description |
406
+ | ----------- | ------------------------------------------------------ | --------------------------------------------- |
407
+ | `show` | `show(id, options?) → Promise<ReturnValue>` | Show a sheet, optionally with payload |
408
+ | `hide` | `hide(id, options?) → Promise<ReturnValue>` | Hide a specific sheet |
409
+ | `hideAll` | `hideAll(id?) → void` | Hide all open sheets |
410
+ | `get` | `get(id, context?) → BottomSheetInstance \| undefined` | Get the sheet's imperative instance |
411
+ | `isVisible` | `isVisible(id, context?) → boolean` | Check if a sheet is currently visible |
412
+ | `replace` | `replace(id, options?) → Promise<ReturnValue>` | Show with `replace` stack behavior |
413
+ | `push` | `push(id, options?) → Promise<ReturnValue>` | Show with `push` stack behavior |
414
+ | `pop` | `pop() → void` | Close top-most pushed sheet |
415
+ | `reset` | `reset() → void` | Reset all internal state (useful for testing) |
416
+
417
+ ### `BottomSheet` Props
418
+
419
+ All props from `@gorhom/bottom-sheet` are supported, plus:
420
+
421
+ | Prop | Type | Default | Description |
422
+ | ------------------------------ | --------------------------------- | ---------- | ------------------------------------------------------------------------------------- |
423
+ | `id` | `SheetID<SheetIds>` | - | Unique identifier for the sheet |
424
+ | `stackBehavior` | `"push" \| "replace" \| "switch"` | `"switch"` | How sheets stack when opened |
425
+ | `iosModalSheetTypeOfAnimation` | `boolean` | `false` | Enable iOS 18 modal animation |
426
+ | `clickThrough` | `boolean` | `false` | Allow tapping through backdrop |
427
+ | `opacity` | `number` | `0.45` | Backdrop opacity |
428
+ | `hardwareBackPressToClose` | `boolean` | `true` | Close on hardware back button (Android) |
429
+ | `onClose` | `(data?) => ReturnValue \| void` | - | Callback when sheet closes; return a value to override what's sent back to the caller |
430
+ | `onBeforeShow` | `(data?) => void` | - | Callback before sheet shows |
431
+
432
+ ### `SheetProvider` Props
433
+
434
+ | Prop | Type | Default | Description |
435
+ | ------------- | ---------------- | ----------- | -------------------------------------------------------------------------------------------------- |
436
+ | `context` | `string` | `"global"` | Unique name for this provider context |
437
+ | `statusBar` | `StatusBarStyle` | `"default"` | Status bar style when a full-screen sheet is open |
438
+ | `scaleConfig` | `object` | - | Controls the iOS 18-style scale/translate/radius animation applied to the content behind the sheet |
439
+
440
+ ### `BottomSheetNavigationOptions`
441
+
442
+ Screen options for navigation-based sheets:
443
+
444
+ | Option | Type | Default | Description |
445
+ | ------------------------------ | ------------------------- | --------- | ------------------------------ |
446
+ | `snapPoints` | `Array<string \| number>` | `['66%']` | Snap points for the sheet |
447
+ | `clickThrough` | `boolean` | `false` | Allow tapping through backdrop |
448
+ | `iosModalSheetTypeOfAnimation` | `boolean` | `false` | Enable iOS 18 modal animation |
449
+ | `opacity` | `number` | `0.45` | Backdrop opacity |
450
+
451
+ ### Navigation Actions
452
+
453
+ ```tsx
454
+ import { BottomSheetActions } from "@niibase/bottom-sheet-manager";
455
+
456
+ // Snap to index
457
+ navigation.dispatch(BottomSheetActions.snapTo(1));
458
+
459
+ // Dismiss sheet
460
+ navigation.dispatch(BottomSheetActions.dismiss());
461
+
462
+ // Remove from stack
463
+ navigation.dispatch(BottomSheetActions.remove());
464
+ ```
465
+
126
466
  ## Examples
127
467
 
128
- The source code for the example (showcase) app is under the [/example](/example/) directory.
468
+ The source code for the example (showcase) app is under the [/example](/example/) directory. It includes:
469
+
470
+ - Basic sheet usage
471
+ - Stack behavior demos (push, replace, switch) including mixed stacks
472
+ - React Navigation integration
473
+ - iOS modal animation examples
474
+ - Navigation actions and hooks usage
129
475
 
130
476
  ## Contributing
131
477
 
@@ -141,7 +487,6 @@ Contributions are welcome! Please feel free to submit a Pull Request.
141
487
 
142
488
  **Built with ❤️ by [@divineniiquaye](https://github.com/divineniiquaye) using React Native and [@gorhom/bottom-sheet](https://github.com/gorhom/react-native-bottom-sheet).**
143
489
 
144
- [⬆ Back to Top](#installation)
490
+ [⬆ Back to Top](#bottom-sheet-router--manager)
145
491
 
146
492
  </div>
147
-
@@ -4,32 +4,117 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.eventManager = exports.default = void 0;
7
- var _reactNative = require("react-native");
8
- /* eslint-disable curly */
7
+ /**
8
+ * High-performance event manager using Maps for O(1) lookups.
9
+ * Replaces React Native's EventEmitter which has significant overhead.
10
+ */
9
11
 
10
12
  class EventManager {
11
- constructor() {
12
- this._registry = _reactNative.Platform.select({
13
- ios: _reactNative.NativeAppEventEmitter,
14
- android: _reactNative.DeviceEventEmitter
15
- });
16
- }
13
+ _handlers = new Map();
14
+ _nextId = 0;
15
+
16
+ /**
17
+ * Subscribe to an event with a handler function.
18
+ * Returns a subscription object with an unsubscribe method.
19
+ */
17
20
  subscribe(name, handler) {
18
- if (!name || !handler) throw new Error("name and handler are required.");
19
- const event = this._registry?.addListener(name, handler);
21
+ if (!name || typeof handler !== "function") {
22
+ throw new Error("Event name and handler function are required.");
23
+ }
24
+ const id = this._nextId++;
25
+ const entry = {
26
+ handler: handler,
27
+ id
28
+ };
29
+ const handlers = this._handlers.get(name);
30
+ if (handlers) {
31
+ handlers.push(entry);
32
+ } else {
33
+ this._handlers.set(name, [entry]);
34
+ }
20
35
  return {
21
- unsubscribe: () => event?.remove()
36
+ unsubscribe: () => {
37
+ const currentHandlers = this._handlers.get(name);
38
+ if (currentHandlers) {
39
+ const index = currentHandlers.findIndex(h => h.id === id);
40
+ if (index !== -1) {
41
+ currentHandlers.splice(index, 1);
42
+ if (currentHandlers.length === 0) {
43
+ this._handlers.delete(name);
44
+ }
45
+ }
46
+ }
47
+ }
22
48
  };
23
49
  }
50
+
51
+ /**
52
+ * Publish an event with optional arguments.
53
+ * All subscribed handlers will be called synchronously.
54
+ */
24
55
  publish(name, ...args) {
25
- this._registry?.emit(name, ...args);
56
+ const handlers = this._handlers.get(name);
57
+ if (!handlers || handlers.length === 0) return;
58
+
59
+ // Create a copy to avoid issues if handlers modify the list during iteration
60
+ const handlersSnapshot = handlers.slice();
61
+ for (let i = 0; i < handlersSnapshot.length; i++) {
62
+ handlersSnapshot[i].handler(...args);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Publish an event asynchronously using microtasks for better performance.
68
+ * Useful when you don't need immediate execution.
69
+ */
70
+ publishAsync(name, ...args) {
71
+ queueMicrotask(() => this.publish(name, ...args));
26
72
  }
73
+
74
+ /**
75
+ * Check if an event has any subscribers.
76
+ */
77
+ hasSubscribers(name) {
78
+ const handlers = this._handlers.get(name);
79
+ return !!handlers && handlers.length > 0;
80
+ }
81
+
82
+ /**
83
+ * Get the number of subscribers for an event.
84
+ */
85
+ subscriberCount(name) {
86
+ return this._handlers.get(name)?.length ?? 0;
87
+ }
88
+
89
+ /**
90
+ * Remove all listeners for the specified event names.
91
+ */
27
92
  remove(...names) {
28
- for (const eventType of names) {
29
- this._registry?.removeAllListeners(eventType);
93
+ for (const name of names) {
94
+ this._handlers.delete(name);
30
95
  }
31
96
  }
97
+
98
+ /**
99
+ * Remove all event listeners.
100
+ */
101
+ clear() {
102
+ this._handlers.clear();
103
+ }
104
+
105
+ /**
106
+ * Subscribe to an event that will only fire once.
107
+ */
108
+ once(name, handler) {
109
+ const subscription = this.subscribe(name, (...args) => {
110
+ subscription.unsubscribe();
111
+ handler(...args);
112
+ });
113
+ return subscription;
114
+ }
32
115
  }
33
- exports.default = EventManager;
116
+
117
+ // Singleton instance for global event management
34
118
  const eventManager = exports.eventManager = new EventManager();
119
+ var _default = exports.default = EventManager;
35
120
  //# sourceMappingURL=events.js.map