@oxyhq/services 22.8.7 → 22.9.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.
- package/lib/commonjs/ui/components/OxyAccountDialog.js +30 -39
- package/lib/commonjs/ui/components/OxyAccountDialog.js.map +1 -1
- package/lib/commonjs/ui/components/OxyProvider.js +9 -29
- package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
- package/lib/commonjs/ui/components/SurfaceScreen.js +146 -0
- package/lib/commonjs/ui/components/SurfaceScreen.js.map +1 -0
- package/lib/commonjs/ui/context/OxyContext.js +32 -1
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/navigation/bottomSheetManager.js +38 -56
- package/lib/commonjs/ui/navigation/bottomSheetManager.js.map +1 -1
- package/lib/commonjs/ui/navigation/routes.js +7 -57
- package/lib/commonjs/ui/navigation/routes.js.map +1 -1
- package/lib/commonjs/ui/navigation/surfaceNavStack.js +98 -0
- package/lib/commonjs/ui/navigation/surfaceNavStack.js.map +1 -0
- package/lib/commonjs/ui/navigation/surfaceRegistry.js +108 -0
- package/lib/commonjs/ui/navigation/surfaceRegistry.js.map +1 -0
- package/lib/commonjs/ui/navigation/surfaces.js +219 -0
- package/lib/commonjs/ui/navigation/surfaces.js.map +1 -0
- package/lib/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.js +9 -7
- package/lib/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.js.map +1 -1
- package/lib/commonjs/ui/types/surfaceScreen.js +6 -0
- package/lib/commonjs/ui/types/surfaceScreen.js.map +1 -0
- package/lib/module/ui/components/OxyAccountDialog.js +31 -40
- package/lib/module/ui/components/OxyAccountDialog.js.map +1 -1
- package/lib/module/ui/components/OxyProvider.js +10 -30
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/components/SurfaceScreen.js +141 -0
- package/lib/module/ui/components/SurfaceScreen.js.map +1 -0
- package/lib/module/ui/context/OxyContext.js +32 -1
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/navigation/bottomSheetManager.js +37 -53
- package/lib/module/ui/navigation/bottomSheetManager.js.map +1 -1
- package/lib/module/ui/navigation/routes.js +6 -56
- package/lib/module/ui/navigation/routes.js.map +1 -1
- package/lib/module/ui/navigation/surfaceNavStack.js +95 -0
- package/lib/module/ui/navigation/surfaceNavStack.js.map +1 -0
- package/lib/module/ui/navigation/surfaceRegistry.js +102 -0
- package/lib/module/ui/navigation/surfaceRegistry.js.map +1 -0
- package/lib/module/ui/navigation/surfaces.js +207 -0
- package/lib/module/ui/navigation/surfaces.js.map +1 -0
- package/lib/module/ui/screens/fileManagement/hooks/useFileUploadState.js +8 -7
- package/lib/module/ui/screens/fileManagement/hooks/useFileUploadState.js.map +1 -1
- package/lib/module/ui/types/surfaceScreen.js +4 -0
- package/lib/module/ui/types/surfaceScreen.js.map +1 -0
- package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts +29 -27
- package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts +3 -2
- package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/SurfaceScreen.d.ts +24 -0
- package/lib/typescript/commonjs/ui/components/SurfaceScreen.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/bottomSheetManager.d.ts +24 -16
- package/lib/typescript/commonjs/ui/navigation/bottomSheetManager.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/routes.d.ts +2 -60
- package/lib/typescript/commonjs/ui/navigation/routes.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/surfaceNavStack.d.ts +51 -0
- package/lib/typescript/commonjs/ui/navigation/surfaceNavStack.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/navigation/surfaceRegistry.d.ts +117 -0
- package/lib/typescript/commonjs/ui/navigation/surfaceRegistry.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/navigation/surfaces.d.ts +79 -0
- package/lib/typescript/commonjs/ui/navigation/surfaces.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/types/surfaceScreen.d.ts +38 -0
- package/lib/typescript/commonjs/ui/types/surfaceScreen.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts +29 -27
- package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxyProvider.d.ts +3 -2
- package/lib/typescript/module/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/SurfaceScreen.d.ts +24 -0
- package/lib/typescript/module/ui/components/SurfaceScreen.d.ts.map +1 -0
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/bottomSheetManager.d.ts +24 -16
- package/lib/typescript/module/ui/navigation/bottomSheetManager.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/routes.d.ts +2 -60
- package/lib/typescript/module/ui/navigation/routes.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/surfaceNavStack.d.ts +51 -0
- package/lib/typescript/module/ui/navigation/surfaceNavStack.d.ts.map +1 -0
- package/lib/typescript/module/ui/navigation/surfaceRegistry.d.ts +117 -0
- package/lib/typescript/module/ui/navigation/surfaceRegistry.d.ts.map +1 -0
- package/lib/typescript/module/ui/navigation/surfaces.d.ts +79 -0
- package/lib/typescript/module/ui/navigation/surfaces.d.ts.map +1 -0
- package/lib/typescript/module/ui/screens/fileManagement/hooks/useFileUploadState.d.ts.map +1 -1
- package/lib/typescript/module/ui/types/surfaceScreen.d.ts +38 -0
- package/lib/typescript/module/ui/types/surfaceScreen.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/ui/components/OxyAccountDialog.tsx +32 -38
- package/src/ui/components/OxyProvider.tsx +12 -34
- package/src/ui/components/SurfaceScreen.tsx +166 -0
- package/src/ui/context/OxyContext.tsx +27 -1
- package/src/ui/navigation/__tests__/getSurfaceConfig.test.ts +39 -0
- package/src/ui/navigation/bottomSheetManager.ts +46 -73
- package/src/ui/navigation/routes.ts +7 -106
- package/src/ui/navigation/surfaceNavStack.ts +110 -0
- package/src/ui/navigation/surfaceRegistry.ts +174 -0
- package/src/ui/navigation/surfaces.ts +244 -0
- package/src/ui/screens/fileManagement/hooks/useFileUploadState.ts +9 -7
- package/src/ui/types/surfaceScreen.ts +38 -0
- package/lib/commonjs/ui/components/BottomSheet.js +0 -49
- package/lib/commonjs/ui/components/BottomSheet.js.map +0 -1
- package/lib/commonjs/ui/components/BottomSheetRouter.js +0 -298
- package/lib/commonjs/ui/components/BottomSheetRouter.js.map +0 -1
- package/lib/module/ui/components/BottomSheet.js +0 -45
- package/lib/module/ui/components/BottomSheet.js.map +0 -1
- package/lib/module/ui/components/BottomSheetRouter.js +0 -292
- package/lib/module/ui/components/BottomSheetRouter.js.map +0 -1
- package/lib/typescript/commonjs/ui/components/BottomSheet.d.ts +0 -29
- package/lib/typescript/commonjs/ui/components/BottomSheet.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/components/BottomSheetRouter.d.ts +0 -9
- package/lib/typescript/commonjs/ui/components/BottomSheetRouter.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/BottomSheet.d.ts +0 -29
- package/lib/typescript/module/ui/components/BottomSheet.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/BottomSheetRouter.d.ts +0 -9
- package/lib/typescript/module/ui/components/BottomSheetRouter.d.ts.map +0 -1
- package/src/ui/components/BottomSheet.tsx +0 -48
- package/src/ui/components/BottomSheetRouter.tsx +0 -310
- package/src/ui/navigation/__tests__/getSheetConfig.test.ts +0 -36
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { RouteName } from '../navigation/routes.js';
|
|
2
|
+
import type { SurfaceProps, SurfaceResult } from '../navigation/surfaceRegistry.js';
|
|
3
|
+
/**
|
|
4
|
+
* The typed prop contract a screen receives when it is bound into an SDK surface.
|
|
5
|
+
*
|
|
6
|
+
* This is the successor to the grab-bag `BaseScreenProps`: `props` carries the
|
|
7
|
+
* route's own {@link SurfaceProps typed props}, and the navigation methods are
|
|
8
|
+
* split along the two axes the surface system models — NAV-WITHIN
|
|
9
|
+
* (`navigate`/`replace`/`goBack`/`step`) inside this surface, and DEPTH
|
|
10
|
+
* (`present`/`dismiss`) across the stack.
|
|
11
|
+
*
|
|
12
|
+
* In P1 the ~35 screens still consume the legacy `BaseScreenProps` shape (the
|
|
13
|
+
* `SurfaceScreen` binding passes a superset that satisfies both), so this type is
|
|
14
|
+
* the target contract for the per-screen migration in a later phase — it is not
|
|
15
|
+
* yet the render-time prop shape for existing screens.
|
|
16
|
+
*/
|
|
17
|
+
export interface SurfaceScreenProps<K extends RouteName> {
|
|
18
|
+
/** The route's typed props (from the {@link SurfaceProps registry}). */
|
|
19
|
+
props: SurfaceProps<K>;
|
|
20
|
+
/** Drill in to another route — WITHIN this surface, or a new surface if the
|
|
21
|
+
* target's presentation differs. */
|
|
22
|
+
navigate: <T extends RouteName>(route: T, props?: SurfaceProps<T>) => void;
|
|
23
|
+
/** Replace the current route in place (no history entry). */
|
|
24
|
+
replace: <T extends RouteName>(route: T, props?: SurfaceProps<T>) => void;
|
|
25
|
+
/** Pop one level; if at the root, dismiss the surface. */
|
|
26
|
+
goBack: () => void;
|
|
27
|
+
/** Whether a back is possible (a deeper route OR a prior wizard step). */
|
|
28
|
+
canGoBack: () => boolean;
|
|
29
|
+
/** Dismiss THIS surface, resolving its `present()` promise with `result`. */
|
|
30
|
+
dismiss: (result?: SurfaceResult<K>) => void;
|
|
31
|
+
/** Present a NEW surface on top of the stack (DEPTH); resolves with its result. */
|
|
32
|
+
present: <T extends RouteName>(route: T, props?: SurfaceProps<T>) => Promise<SurfaceResult<T> | undefined>;
|
|
33
|
+
/** The current wizard step of this frame. */
|
|
34
|
+
step: number;
|
|
35
|
+
/** Set the current wizard step. */
|
|
36
|
+
setStep: (step: number) => void;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=surfaceScreen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"surfaceScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/types/surfaceScreen.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAsB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,kCAA+B,CAAC;AAEjF;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,SAAS;IACrD,wEAAwE;IACxE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IACvB;wCACoC;IACpC,QAAQ,EAAE,CAAC,CAAC,SAAS,SAAS,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IAC3E,6DAA6D;IAC7D,OAAO,EAAE,CAAC,CAAC,SAAS,SAAS,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IAC1E,0DAA0D;IAC1D,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,0EAA0E;IAC1E,SAAS,EAAE,MAAM,OAAO,CAAC;IACzB,6EAA6E;IAC7E,OAAO,EAAE,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IAC7C,mFAAmF;IACnF,OAAO,EAAE,CAAC,CAAC,SAAS,SAAS,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAC3G,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxyhq/services",
|
|
3
|
-
"version": "22.
|
|
3
|
+
"version": "22.9.0",
|
|
4
4
|
"description": "OxyHQ Expo/React Native SDK — UI components, screens, and native features",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -176,7 +176,7 @@
|
|
|
176
176
|
},
|
|
177
177
|
"peerDependencies": {
|
|
178
178
|
"@expo/vector-icons": "^15.0.3",
|
|
179
|
-
"@oxyhq/bloom": ">=0.
|
|
179
|
+
"@oxyhq/bloom": ">=0.46.0",
|
|
180
180
|
"@oxyhq/core": "^12.9.0",
|
|
181
181
|
"@react-native-async-storage/async-storage": "^2.0.0",
|
|
182
182
|
"@react-native-community/netinfo": "^11.4.1",
|
|
@@ -1,38 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OxyAccountDialog — the ONE unified account dialog for `@oxyhq/services`.
|
|
2
|
+
* OxyAccountDialog — the ONE unified account dialog BODY for `@oxyhq/services`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* button, and the Bloom `<Dialog>` surface itself.
|
|
4
|
+
* The header (title/subtitle per view + back button) around the headless chooser
|
|
5
|
+
* logic, which lives in `OxyAuthChooser` — the account switcher, sign-in,
|
|
6
|
+
* sign-up, and QR views are ALL there, extracted so the same chooser can be
|
|
7
|
+
* mounted bare by a future host with no Dialog chrome (e.g. an auth.oxy.so hub
|
|
8
|
+
* page driving the cross-origin passkey popup, b2). This file owns ONLY the
|
|
9
|
+
* header + the scroll body.
|
|
11
10
|
*
|
|
12
|
-
*
|
|
13
|
-
* `
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* Backdrop / swipe-to-dismiss is disabled (`dismissOnBackdrop={false}`) on purpose:
|
|
24
|
-
* Bloom's controlled `bottom` placement does not fire `onClose` on a gesture or
|
|
25
|
-
* backdrop dismissal, so allowing it would desync `isAccountDialogOpen` from the
|
|
26
|
-
* sheet and block reopening. The header close button (and `OxyAuthChooser`'s
|
|
27
|
-
* `onComplete`) drive `closeAccountDialog`, which flips `open` and runs the exit
|
|
28
|
-
* animation.
|
|
11
|
+
* It is presented as the `AccountDialog` route on the shared Bloom SURFACE STACK
|
|
12
|
+
* (`@oxyhq/bloom/surfaces`) — `OxyContext.openAccountDialog` calls
|
|
13
|
+
* `presentDetached('AccountDialog', …, { placement: { base: 'bottom', md:
|
|
14
|
+
* 'center' }, dismissOnBackdrop: false, maxWidth: 420 })`, so the STACK owns the
|
|
15
|
+
* responsive `<Dialog>` chrome and this component renders only its content. That
|
|
16
|
+
* replaces the previous standalone controlled `<Dialog open={isAccountDialogOpen}>`
|
|
17
|
+
* mount: dismissal now flows through the stack (backdrop/swipe disabled), and the
|
|
18
|
+
* header close button (and `OxyAuthChooser`'s `onComplete`) drive
|
|
19
|
+
* `useOxy().closeAccountDialog`, which dismisses the surface and runs its exit
|
|
20
|
+
* animation. The view-enum (`accounts|signin|qr|add|signup`) stays internal here,
|
|
21
|
+
* driven by the shared `AccountDialogController` in `@oxyhq/core`.
|
|
29
22
|
*/
|
|
30
23
|
|
|
31
24
|
import type React from 'react';
|
|
32
25
|
import { useCallback, useSyncExternalStore } from 'react';
|
|
33
26
|
import { Pressable, ScrollView, StyleSheet, View } from 'react-native';
|
|
34
27
|
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
|
35
|
-
import { Dialog } from '@oxyhq/bloom/dialog';
|
|
36
28
|
import { Text } from '@oxyhq/bloom/typography';
|
|
37
29
|
import { useTheme } from '@oxyhq/bloom/theme';
|
|
38
30
|
import type { AccountDialogSnapshot } from '@oxyhq/core';
|
|
@@ -45,11 +37,20 @@ type Theme = ReturnType<typeof useTheme>;
|
|
|
45
37
|
type Translate = ReturnType<typeof useI18n>['t'];
|
|
46
38
|
|
|
47
39
|
/**
|
|
48
|
-
* The unified account dialog
|
|
49
|
-
*
|
|
40
|
+
* The unified account dialog BODY — the header + `OxyAuthChooser` chooser.
|
|
41
|
+
*
|
|
42
|
+
* Presented as the `AccountDialog` surface (route) on the shared Bloom surface
|
|
43
|
+
* stack; the surface owns the `<Dialog>` chrome (responsive `{ base: 'bottom',
|
|
44
|
+
* md: 'center' }` placement, `dismissOnBackdrop={false}`, `maxWidth={420}` — set
|
|
45
|
+
* by `OxyContext` when it presents this surface), so this component renders ONLY
|
|
46
|
+
* the content. Open it via `useOxy().openAccountDialog(view?)` or the imperative
|
|
47
|
+
* `openAccountDialog('signin')`; the view-enum (`accounts|signin|qr|add|signup`)
|
|
48
|
+
* stays internal here, driven by the shared `AccountDialogController` in
|
|
49
|
+
* `@oxyhq/core`. Closing routes through `useOxy().closeAccountDialog`, which
|
|
50
|
+
* dismisses the surface and runs its exit animation.
|
|
50
51
|
*/
|
|
51
52
|
const OxyAccountDialog: React.FC = () => {
|
|
52
|
-
const { accountDialogController: controller,
|
|
53
|
+
const { accountDialogController: controller, closeAccountDialog } = useOxy();
|
|
53
54
|
const theme = useTheme();
|
|
54
55
|
const { t } = useI18n();
|
|
55
56
|
|
|
@@ -75,14 +76,7 @@ const OxyAccountDialog: React.FC = () => {
|
|
|
75
76
|
view === 'qr' || view === 'signup' || (view === 'add' && snapshot.accounts.length > 0);
|
|
76
77
|
|
|
77
78
|
return (
|
|
78
|
-
|
|
79
|
-
open={isAccountDialogOpen}
|
|
80
|
-
onClose={closeAccountDialog}
|
|
81
|
-
placement={{ base: 'bottom', md: 'center' }}
|
|
82
|
-
dismissOnBackdrop={false}
|
|
83
|
-
maxWidth={420}
|
|
84
|
-
label={headerCopy(view, snapshot.accounts.length, t).title}
|
|
85
|
-
>
|
|
79
|
+
<>
|
|
86
80
|
<DialogHeader
|
|
87
81
|
snapshot={snapshot}
|
|
88
82
|
theme={theme}
|
|
@@ -94,7 +88,7 @@ const OxyAccountDialog: React.FC = () => {
|
|
|
94
88
|
<ScrollView style={styles.body} contentContainerStyle={styles.bodyContent} showsVerticalScrollIndicator={false}>
|
|
95
89
|
<OxyAuthChooser onComplete={closeAccountDialog} />
|
|
96
90
|
</ScrollView>
|
|
97
|
-
|
|
91
|
+
</>
|
|
98
92
|
);
|
|
99
93
|
};
|
|
100
94
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useRef, useState, type FC, type ReactNode } from 'react';
|
|
2
2
|
import { AppState, Platform, StyleSheet, View } from 'react-native';
|
|
3
3
|
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
4
4
|
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
|
@@ -6,6 +6,7 @@ import type { OxyProviderProps } from '../types/navigation';
|
|
|
6
6
|
import { OxyContextProvider, type OxyContextProviderProps } from '../context/OxyContext';
|
|
7
7
|
import { QueryClientProvider, focusManager, onlineManager } from '@tanstack/react-query';
|
|
8
8
|
import { BloomDialogProvider } from '@oxyhq/bloom';
|
|
9
|
+
import { SurfaceProvider } from '@oxyhq/bloom/surfaces';
|
|
9
10
|
import { ToastOutlet } from '@oxyhq/bloom/toast';
|
|
10
11
|
import { logger as loggerUtil } from '@oxyhq/core';
|
|
11
12
|
import { RequireOxyAuth } from './RequireOxyAuth';
|
|
@@ -49,35 +50,14 @@ const isWeb = Platform.OS === 'web';
|
|
|
49
50
|
// static analyzer cannot trace this into the web bundle. Native-only.
|
|
50
51
|
const KEYBOARD_CONTROLLER_MODULE = 'react-native-keyboard-controller';
|
|
51
52
|
|
|
52
|
-
// Lazy-load optional components (avoids require() for ESM compatibility).
|
|
53
|
-
// The .then() extracts + casts the default export so that `lazy()` sees
|
|
54
|
-
// `Promise<{ default: ComponentType }>` instead of the full module namespace.
|
|
55
|
-
const LazyBottomSheetRouter = lazy((): Promise<{ default: ComponentType }> =>
|
|
56
|
-
import('./BottomSheetRouter').then(
|
|
57
|
-
(mod) => ({ default: mod.default as unknown as ComponentType }),
|
|
58
|
-
(error) => {
|
|
59
|
-
if (__DEV__) {
|
|
60
|
-
loggerUtil.error('Failed to load BottomSheetRouter', error instanceof Error ? error : new Error(String(error)), { component: 'OxyProvider' });
|
|
61
|
-
}
|
|
62
|
-
return { default: (() => null) as FC };
|
|
63
|
-
},
|
|
64
|
-
),
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
const LazyOxyAccountDialog = lazy((): Promise<{ default: ComponentType }> =>
|
|
68
|
-
import('./OxyAccountDialog').then(
|
|
69
|
-
(mod) => ({ default: mod.default as unknown as ComponentType }),
|
|
70
|
-
() => ({ default: (() => null) as FC }),
|
|
71
|
-
),
|
|
72
|
-
);
|
|
73
|
-
|
|
74
53
|
/**
|
|
75
54
|
* OxyProvider - Universal provider for Expo apps (native + web)
|
|
76
55
|
*
|
|
77
56
|
* Provides authentication, session management, query client, and UI overlays.
|
|
78
57
|
* Wraps its own overlay stack in SafeAreaProvider and GestureHandlerRootView so
|
|
79
|
-
*
|
|
80
|
-
*
|
|
58
|
+
* the shared Bloom SURFACE STACK (`<SurfaceProvider>` / `<SurfaceHost>`) — which
|
|
59
|
+
* hosts every SDK surface (the ~35 bottom-sheet routes + the AccountDialog) — can
|
|
60
|
+
* safely render even when a consuming app has not mounted those providers yet.
|
|
81
61
|
*
|
|
82
62
|
* Usage:
|
|
83
63
|
* ```tsx
|
|
@@ -316,15 +296,13 @@ const OxyProvider: FC<OxyProviderProps> = ({
|
|
|
316
296
|
hubSync={hubSync}
|
|
317
297
|
onAuthStateChange={onAuthStateChange as OxyContextProviderProps['onAuthStateChange']}
|
|
318
298
|
>
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
<LazyOxyAccountDialog />
|
|
327
|
-
</Suspense>
|
|
299
|
+
<SurfaceProvider>
|
|
300
|
+
{requireAuth === 'off' ? (
|
|
301
|
+
children
|
|
302
|
+
) : (
|
|
303
|
+
<RequireOxyAuth prompt={requireAuth}>{children}</RequireOxyAuth>
|
|
304
|
+
)}
|
|
305
|
+
</SurfaceProvider>
|
|
328
306
|
<ToastOutlet />
|
|
329
307
|
</OxyContextProvider>
|
|
330
308
|
</BloomDialogProvider>
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, type ErrorInfo } from 'react';
|
|
2
|
+
import { StyleSheet, Text, View } from 'react-native';
|
|
3
|
+
import { useStore } from 'zustand';
|
|
4
|
+
import { useTheme } from '@oxyhq/bloom/theme';
|
|
5
|
+
import type { SurfaceControls } from '@oxyhq/bloom/surfaces';
|
|
6
|
+
import type { RouteName } from '../navigation/routes';
|
|
7
|
+
import { getScreenComponent } from '../navigation/routes';
|
|
8
|
+
import type { SurfaceNavStack } from '../navigation/surfaceNavStack';
|
|
9
|
+
import type { SurfacePresentation } from '../navigation/surfaceRegistry';
|
|
10
|
+
import {
|
|
11
|
+
closeAllRouteSurfaces,
|
|
12
|
+
navigateWithinOrPresent,
|
|
13
|
+
presentRoute,
|
|
14
|
+
replaceWithinOrPresent,
|
|
15
|
+
} from '../navigation/surfaces';
|
|
16
|
+
import type { BaseScreenProps } from '../types/navigation';
|
|
17
|
+
|
|
18
|
+
/** Error boundary catching screen render failures (e.g. a lazy `require()` throw). */
|
|
19
|
+
interface ScreenErrorBoundaryState {
|
|
20
|
+
error: Error | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class ScreenErrorBoundary extends React.Component<
|
|
24
|
+
{ screenName: string; children: React.ReactNode },
|
|
25
|
+
ScreenErrorBoundaryState
|
|
26
|
+
> {
|
|
27
|
+
state: ScreenErrorBoundaryState = { error: null };
|
|
28
|
+
|
|
29
|
+
static getDerivedStateFromError(error: Error): ScreenErrorBoundaryState {
|
|
30
|
+
return { error };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
componentDidCatch(error: Error, info: ErrorInfo): void {
|
|
34
|
+
if (__DEV__) {
|
|
35
|
+
console.error(
|
|
36
|
+
`[SurfaceScreen] Screen "${this.props.screenName}" crashed:`,
|
|
37
|
+
error,
|
|
38
|
+
info.componentStack,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
componentDidUpdate(prevProps: { screenName: string }): void {
|
|
44
|
+
if (prevProps.screenName !== this.props.screenName && this.state.error) {
|
|
45
|
+
this.setState({ error: null });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
render(): React.ReactNode {
|
|
50
|
+
if (this.state.error) {
|
|
51
|
+
return (
|
|
52
|
+
<View style={errorStyles.container}>
|
|
53
|
+
<Text style={errorStyles.title}>Something went wrong</Text>
|
|
54
|
+
{__DEV__ && <Text style={errorStyles.message}>{this.state.error.message}</Text>}
|
|
55
|
+
</View>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return this.props.children;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const errorStyles = StyleSheet.create({
|
|
63
|
+
container: { flex: 1, alignItems: 'center', justifyContent: 'center', padding: 24 },
|
|
64
|
+
title: { fontSize: 16, fontWeight: '600', marginBottom: 8 },
|
|
65
|
+
message: { fontSize: 13, textAlign: 'center' },
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export interface SurfaceScreenProps {
|
|
69
|
+
/** This surface's route history (the NAV-WITHIN axis). */
|
|
70
|
+
navStack: SurfaceNavStack;
|
|
71
|
+
/** Bloom's per-surface controls (dismiss / present a child) — the DEPTH axis. */
|
|
72
|
+
surface: SurfaceControls;
|
|
73
|
+
/** This surface's presentation, so `navigate` knows when to drill in vs. stack. */
|
|
74
|
+
presentation: SurfacePresentation;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Binds one SDK surface to its content: resolves the lazy screen for the current
|
|
79
|
+
* nav-stack frame and renders it inside the error boundary with the per-surface
|
|
80
|
+
* navigation props. Mounted by `surfaces.present` as the Bloom surface's content.
|
|
81
|
+
*
|
|
82
|
+
* Dismissal is driven through the nav stack's `closing` flag (watched in an
|
|
83
|
+
* effect) rather than a captured-controls call, so a dismiss requested before
|
|
84
|
+
* this surface mounted still resolves the Bloom surface exactly once.
|
|
85
|
+
*/
|
|
86
|
+
function SurfaceScreen({ navStack, surface, presentation }: SurfaceScreenProps) {
|
|
87
|
+
const theme = useTheme();
|
|
88
|
+
const state = useStore(navStack.store);
|
|
89
|
+
const top = state.frames[state.frames.length - 1];
|
|
90
|
+
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
if (state.closing) surface.dismiss(state.closeResult);
|
|
93
|
+
}, [state.closing, state.closeResult, surface]);
|
|
94
|
+
|
|
95
|
+
const ScreenComponent = useMemo(() => getScreenComponent(top.route) ?? null, [top.route]);
|
|
96
|
+
|
|
97
|
+
const navigate = useCallback(
|
|
98
|
+
(route: RouteName, props?: Record<string, unknown>) =>
|
|
99
|
+
navigateWithinOrPresent(presentation, navStack, route, props),
|
|
100
|
+
[navStack, presentation],
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const replace = useCallback(
|
|
104
|
+
(route: RouteName, props?: Record<string, unknown>) =>
|
|
105
|
+
replaceWithinOrPresent(presentation, navStack, route, props),
|
|
106
|
+
[navStack, presentation],
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const dismiss = useCallback((result?: unknown) => navStack.requestDismiss(result), [navStack]);
|
|
110
|
+
|
|
111
|
+
const canGoBack = useCallback(() => navStack.canGoBack(), [navStack]);
|
|
112
|
+
|
|
113
|
+
const setStep = useCallback((step: number) => navStack.setStep(step), [navStack]);
|
|
114
|
+
|
|
115
|
+
// Screen-facing back: pop a frame, else step a wizard back, else dismiss the
|
|
116
|
+
// surface — the exact behaviour the single `BottomSheetRouter` had per session.
|
|
117
|
+
const goBack = useCallback((): void => {
|
|
118
|
+
if (navStack.goBack()) return;
|
|
119
|
+
const current = navStack.getTop();
|
|
120
|
+
if (current.step > 0) {
|
|
121
|
+
navStack.setStep(current.step - 1);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
navStack.requestDismiss();
|
|
125
|
+
}, [navStack]);
|
|
126
|
+
|
|
127
|
+
const present = useCallback(
|
|
128
|
+
(route: RouteName, props?: Record<string, unknown>) => presentRoute(route, props).result,
|
|
129
|
+
[],
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const screenProps = useMemo<BaseScreenProps>(() => {
|
|
133
|
+
const { initialStep: _omitInitialStep, ...rest } = top.props;
|
|
134
|
+
return {
|
|
135
|
+
// NAV-WITHIN
|
|
136
|
+
navigate,
|
|
137
|
+
goBack,
|
|
138
|
+
replace,
|
|
139
|
+
canGoBack,
|
|
140
|
+
initialStep: top.step,
|
|
141
|
+
step: top.step,
|
|
142
|
+
onStepChange: setStep,
|
|
143
|
+
setStep,
|
|
144
|
+
currentScreen: top.route,
|
|
145
|
+
// DEPTH — `onClose`/`onAuthenticated` unwind the whole session (matching the
|
|
146
|
+
// old `closeBottomSheet`); `dismiss`/`present` are the per-surface controls.
|
|
147
|
+
onClose: closeAllRouteSurfaces,
|
|
148
|
+
onAuthenticated: closeAllRouteSurfaces,
|
|
149
|
+
dismiss,
|
|
150
|
+
present,
|
|
151
|
+
// Theme + the route's own props.
|
|
152
|
+
theme: theme.mode,
|
|
153
|
+
...rest,
|
|
154
|
+
};
|
|
155
|
+
}, [navigate, goBack, replace, canGoBack, setStep, present, dismiss, theme.mode, top.route, top.step, top.props]);
|
|
156
|
+
|
|
157
|
+
if (!ScreenComponent) return null;
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<ScreenErrorBoundary screenName={top.route}>
|
|
161
|
+
<ScreenComponent {...screenProps} />
|
|
162
|
+
</ScreenErrorBoundary>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export default SurfaceScreen;
|
|
@@ -45,6 +45,7 @@ import { useDeviceManagement } from '../hooks/useDeviceManagement';
|
|
|
45
45
|
import { getStorageKeys, createPlatformStorage, type StorageInterface } from '../utils/storageHelpers';
|
|
46
46
|
import type { RouteName } from '../navigation/routes';
|
|
47
47
|
import { showBottomSheet as globalShowBottomSheet } from '../navigation/bottomSheetManager';
|
|
48
|
+
import { presentDetached, type SurfaceInstance } from '../navigation/surfaces';
|
|
48
49
|
import { useQueryClient, onlineManager } from '@tanstack/react-query';
|
|
49
50
|
import { clearQueryCache } from '../hooks/queryClient';
|
|
50
51
|
import { useAvatarPicker } from '../hooks/useAvatarPicker';
|
|
@@ -643,6 +644,11 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
643
644
|
const commitSwitchedSessionRef = useRef(commitSwitchedSession);
|
|
644
645
|
commitSwitchedSessionRef.current = commitSwitchedSession;
|
|
645
646
|
|
|
647
|
+
// The live AccountDialog surface (a stacked Bloom surface), while open. Held so
|
|
648
|
+
// `openAccountDialog` can no-op when already open and `closeAccountDialog` /
|
|
649
|
+
// `onSignedIn` can dismiss it. Cleared when the surface settles.
|
|
650
|
+
const accountDialogSurfaceRef = useRef<SurfaceInstance<'AccountDialog'> | null>(null);
|
|
651
|
+
|
|
646
652
|
const accountDialogControllerRef = useRef<AccountDialogController | null>(null);
|
|
647
653
|
if (!accountDialogControllerRef.current) {
|
|
648
654
|
accountDialogControllerRef.current = createAccountDialogController({
|
|
@@ -655,7 +661,10 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
655
661
|
socketFactory: io,
|
|
656
662
|
commitSession: (session) => handleWebSessionRef.current(session),
|
|
657
663
|
commitSwitchedSession: (session) => commitSwitchedSessionRef.current(session),
|
|
658
|
-
onSignedIn: () =>
|
|
664
|
+
onSignedIn: () => {
|
|
665
|
+
accountDialogSurfaceRef.current?.dismiss();
|
|
666
|
+
setAccountDialogOpen(false);
|
|
667
|
+
},
|
|
659
668
|
openUrl: (url) => {
|
|
660
669
|
if (isWebBrowser()) {
|
|
661
670
|
redirectToAuthorize(url);
|
|
@@ -680,11 +689,28 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
680
689
|
|
|
681
690
|
const openAccountDialog = useCallback((view?: AccountDialogView): void => {
|
|
682
691
|
accountDialogControllerRef.current?.setView(view ?? 'accounts');
|
|
692
|
+
// Present the AccountDialog surface on the shared Bloom stack the FIRST time
|
|
693
|
+
// (subsequent opens just re-point the controller's view above). `presentDetached`
|
|
694
|
+
// keeps it OUT of the `showBottomSheet` route-surface lineage, so closing the
|
|
695
|
+
// bottom-sheet session never touches the account dialog and vice-versa.
|
|
696
|
+
if (!accountDialogSurfaceRef.current) {
|
|
697
|
+
const instance = presentDetached(
|
|
698
|
+
'AccountDialog',
|
|
699
|
+
{ initialView: view ?? 'accounts' },
|
|
700
|
+
{ placement: { base: 'bottom', md: 'center' }, dismissOnBackdrop: false, maxWidth: 420 },
|
|
701
|
+
);
|
|
702
|
+
accountDialogSurfaceRef.current = instance;
|
|
703
|
+
instance.result.finally(() => {
|
|
704
|
+
if (accountDialogSurfaceRef.current === instance) accountDialogSurfaceRef.current = null;
|
|
705
|
+
setAccountDialogOpen(false);
|
|
706
|
+
});
|
|
707
|
+
}
|
|
683
708
|
setAccountDialogOpen(true);
|
|
684
709
|
}, []);
|
|
685
710
|
|
|
686
711
|
const closeAccountDialog = useCallback((): void => {
|
|
687
712
|
accountDialogControllerRef.current?.cancelSignIn();
|
|
713
|
+
accountDialogSurfaceRef.current?.dismiss();
|
|
688
714
|
setAccountDialogOpen(false);
|
|
689
715
|
}, []);
|
|
690
716
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { getSurfaceConfig, getSurfacePresentation } from '../surfaceRegistry';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `getSurfaceConfig` resolves which Bloom surface a route is presented in. The
|
|
5
|
+
* flagship image picker (FileManagement in image-only select mode) upgrades to a
|
|
6
|
+
* full-bleed `'fullScreen'` surface that owns its own scrolling; every other
|
|
7
|
+
* route stays on the responsive `'sheet'`. These guard the routing contract.
|
|
8
|
+
*/
|
|
9
|
+
describe('getSurfaceConfig presentation', () => {
|
|
10
|
+
const imageOnlyPickerProps = {
|
|
11
|
+
selectMode: true,
|
|
12
|
+
disabledMimeTypes: ['video/', 'audio/', 'application/pdf'],
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
it('defaults every route to the responsive sheet', () => {
|
|
16
|
+
expect(getSurfaceConfig('ManageAccount', {}).presentation).toBe('sheet');
|
|
17
|
+
expect(getSurfaceConfig('AvatarCrop', {}).presentation).toBe('sheet');
|
|
18
|
+
expect(getSurfacePresentation('ManageAccount', {})).toBe('sheet');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('routes the FileManagement image-only picker to a full-screen surface', () => {
|
|
22
|
+
const config = getSurfaceConfig('FileManagement', imageOnlyPickerProps);
|
|
23
|
+
expect(config.presentation).toBe('fullScreen');
|
|
24
|
+
// The full-bleed picker owns its own FlatList → the host must not wrap it.
|
|
25
|
+
expect(config.scrollable).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('keeps FileManagement on the sheet when it is not an image-only picker', () => {
|
|
29
|
+
// Browse mode (no selectMode) — the standard file manager.
|
|
30
|
+
expect(getSurfaceConfig('FileManagement', {}).presentation).toBe('sheet');
|
|
31
|
+
// Select mode that still allows non-image types is not the flagship picker.
|
|
32
|
+
expect(
|
|
33
|
+
getSurfaceConfig('FileManagement', {
|
|
34
|
+
selectMode: true,
|
|
35
|
+
disabledMimeTypes: ['video/'],
|
|
36
|
+
}).presentation,
|
|
37
|
+
).toBe('sheet');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -1,88 +1,61 @@
|
|
|
1
1
|
import type { RouteName } from './routes';
|
|
2
2
|
import { isValidRoute } from './routes';
|
|
3
|
-
import {
|
|
3
|
+
import { getSurfacePresentation } from './surfaceRegistry';
|
|
4
|
+
import { closeAllRouteSurfaces, presentRoute, topRouteSurface } from './surfaces';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
|
-
* Bottom
|
|
7
|
+
* Bottom-sheet manager — the INTERNAL adapter that keeps the historical
|
|
8
|
+
* `showBottomSheet` / `closeBottomSheet` API working on top of the new Bloom
|
|
9
|
+
* surface stack (`navigation/surfaces.ts`). The public signatures are unchanged;
|
|
10
|
+
* this file just re-expresses them as thin calls into the typed surface layer.
|
|
11
|
+
*
|
|
12
|
+
* The old single-surface store is gone: `showBottomSheet` now open-or-navigates
|
|
13
|
+
* the "base" (top-most) route surface, and cross-presentation targets (the
|
|
14
|
+
* full-bleed image picker) stack a NEW surface on top — Bloom coordinates the
|
|
15
|
+
* z-order / backdrop / dismiss so nothing clashes.
|
|
7
16
|
*/
|
|
8
17
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
currentScreen: null,
|
|
20
|
-
screenProps: {},
|
|
21
|
-
currentStep: undefined,
|
|
22
|
-
history: [],
|
|
23
|
-
isOpen: false,
|
|
24
|
-
fullScreen: false,
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export const bottomSheetStore = createStore<BottomSheetState>(() => initialState);
|
|
28
|
-
|
|
29
|
-
export const getState = () => bottomSheetStore.getState();
|
|
30
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Open a bottom-sheet route. If the top-most active surface hosts the SAME
|
|
20
|
+
* presentation (the common all-sheets case) the route is drilled into it; a route
|
|
21
|
+
* with a different presentation (the image picker's full-bleed surface) is
|
|
22
|
+
* presented as a NEW surface on top. Opening with no active surface presents the
|
|
23
|
+
* first one.
|
|
24
|
+
*
|
|
25
|
+
* `fullScreen` is accepted for signature compatibility and ignored — a route's
|
|
26
|
+
* surface is now derived from its registry config, not a per-call flag.
|
|
27
|
+
*/
|
|
31
28
|
export const showBottomSheet = (
|
|
32
|
-
|
|
29
|
+
screenOrConfig:
|
|
30
|
+
| RouteName
|
|
31
|
+
| { screen: RouteName; props?: Record<string, unknown>; fullScreen?: boolean },
|
|
33
32
|
): void => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
history: [...state.history, {
|
|
49
|
-
screen: state.currentScreen,
|
|
50
|
-
props: state.screenProps,
|
|
51
|
-
step: state.currentStep,
|
|
52
|
-
}],
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
bottomSheetStore.setState({
|
|
57
|
-
currentScreen: screen,
|
|
58
|
-
screenProps: props,
|
|
59
|
-
currentStep: typeof props.initialStep === 'number' ? props.initialStep : undefined,
|
|
60
|
-
isOpen: true,
|
|
61
|
-
fullScreen,
|
|
62
|
-
});
|
|
33
|
+
const screen = typeof screenOrConfig === 'string' ? screenOrConfig : screenOrConfig.screen;
|
|
34
|
+
const props = typeof screenOrConfig === 'string' ? {} : screenOrConfig.props ?? {};
|
|
35
|
+
|
|
36
|
+
if (!isValidRoute(screen)) {
|
|
37
|
+
if (__DEV__) console.warn(`[BottomSheet] Invalid route: ${screen}`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const top = topRouteSurface();
|
|
42
|
+
if (top && top.presentation === getSurfacePresentation(screen, props)) {
|
|
43
|
+
top.navigate(screen, props);
|
|
44
|
+
} else {
|
|
45
|
+
presentRoute(screen, props);
|
|
46
|
+
}
|
|
63
47
|
};
|
|
64
48
|
|
|
49
|
+
/** Dismiss the whole bottom-sheet session (every active route surface). */
|
|
65
50
|
export const closeBottomSheet = (): void => {
|
|
66
|
-
|
|
51
|
+
closeAllRouteSurfaces();
|
|
67
52
|
};
|
|
68
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Step back within the top-most surface's route history. Returns `true` when a
|
|
56
|
+
* frame was popped, `false` when the surface is already at its root frame.
|
|
57
|
+
*/
|
|
69
58
|
export const goBack = (): boolean => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (history.length > 0) {
|
|
73
|
-
const prev = history[history.length - 1];
|
|
74
|
-
bottomSheetStore.setState({
|
|
75
|
-
currentScreen: prev.screen,
|
|
76
|
-
screenProps: prev.props,
|
|
77
|
-
currentStep: prev.step,
|
|
78
|
-
history: history.slice(0, -1),
|
|
79
|
-
});
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return false;
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
export const updateState = (updates: Partial<BottomSheetState>) => {
|
|
87
|
-
bottomSheetStore.setState((state) => ({ ...state, ...updates }));
|
|
59
|
+
const top = topRouteSurface();
|
|
60
|
+
return top ? top.goBack() : false;
|
|
88
61
|
};
|