@symbiote-native/navigation 2.0.3 → 2.1.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/build/angular/drawer/index.js +6 -4
- package/build/angular/stack/index.d.ts +2 -0
- package/build/angular/stack/index.js +23 -3
- package/build/angular/tabs/index.d.ts +1 -1
- package/build/angular/tabs/index.js +20 -7
- package/build/core/index.d.ts +2 -1
- package/build/core/index.js +5 -1
- package/build/core/stack-router-state/index.d.ts +2 -0
- package/build/core/stack-router-state/index.js +44 -0
- package/build/core/tab-router-state/index.d.ts +1 -0
- package/build/core/tab-router-state/index.js +19 -0
- package/build/react/drawer/index.js +23 -13
- package/build/react/stack/index.js +13 -2
- package/build/react/tabs/index.js +23 -9
- package/build/svelte/attachments.d.ts +5 -0
- package/build/svelte/attachments.js +61 -0
- package/build/svelte/descriptor-subtree.d.ts +3 -0
- package/build/svelte/descriptor-subtree.js +37 -0
- package/build/svelte/drawer/drawer-props.d.ts +14 -0
- package/build/svelte/drawer/drawer-props.js +3 -0
- package/build/svelte/drawer/index.d.ts +10 -0
- package/build/svelte/drawer/index.js +6 -0
- package/build/svelte/drawer/index.svelte +421 -0
- package/build/svelte/drawer/index.svelte.d.ts +10 -0
- package/build/svelte/drawer-screen-props.d.ts +13 -0
- package/build/svelte/drawer-screen-props.js +4 -0
- package/build/svelte/drawer-screen.svelte +24 -0
- package/build/svelte/drawer-screen.svelte.d.ts +4 -0
- package/build/svelte/index.d.ts +21 -0
- package/build/svelte/index.js +20 -0
- package/build/svelte/linking.svelte.d.ts +2 -0
- package/build/svelte/linking.svelte.js +55 -0
- package/build/svelte/navigation-context.d.ts +14 -0
- package/build/svelte/navigation-context.js +44 -0
- package/build/svelte/navigation-scope.svelte +24 -0
- package/build/svelte/navigation-scope.svelte.d.ts +9 -0
- package/build/svelte/registry-host.d.ts +1 -0
- package/build/svelte/registry-host.js +26 -0
- package/build/svelte/runes/index.d.ts +12 -0
- package/build/svelte/runes/index.js +12 -0
- package/build/svelte/runes/use-drawer-navigation.svelte.d.ts +6 -0
- package/build/svelte/runes/use-drawer-navigation.svelte.js +19 -0
- package/build/svelte/runes/use-focus-effect.svelte.d.ts +1 -0
- package/build/svelte/runes/use-focus-effect.svelte.js +31 -0
- package/build/svelte/runes/use-is-focused.svelte.d.ts +3 -0
- package/build/svelte/runes/use-is-focused.svelte.js +34 -0
- package/build/svelte/runes/use-navigation-state.svelte.d.ts +4 -0
- package/build/svelte/runes/use-navigation-state.svelte.js +39 -0
- package/build/svelte/runes/use-navigation.svelte.d.ts +9 -0
- package/build/svelte/runes/use-navigation.svelte.js +22 -0
- package/build/svelte/runes/use-route.svelte.d.ts +4 -0
- package/build/svelte/runes/use-route.svelte.js +12 -0
- package/build/svelte/runes/use-stack-navigation.svelte.d.ts +6 -0
- package/build/svelte/runes/use-stack-navigation.svelte.js +22 -0
- package/build/svelte/runes/use-tab-navigation.svelte.d.ts +6 -0
- package/build/svelte/runes/use-tab-navigation.svelte.js +19 -0
- package/build/svelte/screen-props.d.ts +21 -0
- package/build/svelte/screen-props.js +6 -0
- package/build/svelte/screen-registry.d.ts +17 -0
- package/build/svelte/screen-registry.js +57 -0
- package/build/svelte/screen.svelte +28 -0
- package/build/svelte/screen.svelte.d.ts +4 -0
- package/build/svelte/stack/index.d.ts +14 -0
- package/build/svelte/stack/index.js +7 -0
- package/build/svelte/stack/index.svelte +224 -0
- package/build/svelte/stack/index.svelte.d.ts +15 -0
- package/build/svelte/stack/stack-props.d.ts +20 -0
- package/build/svelte/stack/stack-props.js +4 -0
- package/build/svelte/stack/stack-screen.svelte +148 -0
- package/build/svelte/stack/stack-screen.svelte.d.ts +4 -0
- package/build/svelte/tab-screen-props.d.ts +13 -0
- package/build/svelte/tab-screen-props.js +4 -0
- package/build/svelte/tab-screen.svelte +26 -0
- package/build/svelte/tab-screen.svelte.d.ts +4 -0
- package/build/svelte/tabs/index.d.ts +8 -0
- package/build/svelte/tabs/index.js +6 -0
- package/build/svelte/tabs/index.svelte +247 -0
- package/build/svelte/tabs/index.svelte.d.ts +8 -0
- package/build/svelte/tabs/tab-props.d.ts +7 -0
- package/build/svelte/tabs/tab-props.js +3 -0
- package/build/vue/drawer/index.js +6 -5
- package/build/vue/stack/index.js +26 -12
- package/build/vue/tabs/index.js +48 -35
- package/build-ngc/angular/drawer/index.js +9 -7
- package/build-ngc/angular/drawer/index.js.map +1 -1
- package/build-ngc/angular/drawer-screen.directive.js +3 -3
- package/build-ngc/angular/navigation-context.service.js +3 -3
- package/build-ngc/angular/navigation-scope.directive.js +3 -3
- package/build-ngc/angular/screen.directive.js +3 -3
- package/build-ngc/angular/search-bar-ref.directive.js +3 -3
- package/build-ngc/angular/stack/index.d.ts +2 -0
- package/build-ngc/angular/stack/index.js +26 -6
- package/build-ngc/angular/stack/index.js.map +1 -1
- package/build-ngc/angular/tab-screen.directive.js +3 -3
- package/build-ngc/angular/tabs/index.d.ts +1 -1
- package/build-ngc/angular/tabs/index.js +23 -10
- package/build-ngc/angular/tabs/index.js.map +1 -1
- package/build-ngc/core/index.d.ts +2 -1
- package/build-ngc/core/index.js +5 -1
- package/build-ngc/core/index.js.map +1 -1
- package/build-ngc/core/stack-router-state/index.d.ts +2 -0
- package/build-ngc/core/stack-router-state/index.js +45 -0
- package/build-ngc/core/stack-router-state/index.js.map +1 -0
- package/build-ngc/core/tab-router-state/index.d.ts +1 -0
- package/build-ngc/core/tab-router-state/index.js +19 -0
- package/build-ngc/core/tab-router-state/index.js.map +1 -1
- package/package.json +28 -14
- package/src/angular/drawer/index.ts +6 -4
- package/src/angular/stack/index.ts +23 -2
- package/src/angular/tabs/index.ts +19 -5
- package/src/core/index.ts +11 -1
- package/src/core/stack-router-state/index.ts +49 -0
- package/src/core/tab-router-state/index.ts +25 -0
- package/src/react/drawer/index.ts +28 -13
- package/src/react/stack/index.ts +15 -1
- package/src/react/tabs/index.ts +43 -10
- package/src/svelte/attachments.ts +68 -0
- package/src/svelte/descriptor-subtree.ts +49 -0
- package/src/svelte/drawer/drawer-props.ts +28 -0
- package/src/svelte/drawer/index.svelte +421 -0
- package/src/svelte/drawer/index.ts +10 -0
- package/src/svelte/drawer-screen-props.ts +20 -0
- package/src/svelte/drawer-screen.svelte +24 -0
- package/src/svelte/fabric-tree.test-helper.ts +61 -0
- package/src/svelte/index.ts +71 -0
- package/src/svelte/linking.svelte.ts +66 -0
- package/src/svelte/navigation-context.ts +62 -0
- package/src/svelte/navigation-scope.svelte +24 -0
- package/src/svelte/registry-host.ts +26 -0
- package/src/svelte/runes/index.ts +17 -0
- package/src/svelte/runes/use-drawer-navigation.svelte.ts +29 -0
- package/src/svelte/runes/use-focus-effect.svelte.ts +35 -0
- package/src/svelte/runes/use-is-focused.svelte.ts +38 -0
- package/src/svelte/runes/use-navigation-state.svelte.ts +46 -0
- package/src/svelte/runes/use-navigation.svelte.ts +39 -0
- package/src/svelte/runes/use-route.svelte.ts +16 -0
- package/src/svelte/runes/use-stack-navigation.svelte.ts +32 -0
- package/src/svelte/runes/use-tab-navigation.svelte.ts +29 -0
- package/src/svelte/screen-props.ts +45 -0
- package/src/svelte/screen-registry.ts +92 -0
- package/src/svelte/screen.svelte +28 -0
- package/src/svelte/stack/index.svelte +224 -0
- package/src/svelte/stack/index.ts +11 -0
- package/src/svelte/stack/stack-props.ts +31 -0
- package/src/svelte/stack/stack-screen.svelte +148 -0
- package/src/svelte/svelte-ambient.d.ts +6 -0
- package/src/svelte/svelte-compile.test-helper.ts +143 -0
- package/src/svelte/tab-screen-props.ts +20 -0
- package/src/svelte/tab-screen.svelte +26 -0
- package/src/svelte/tabs/index.svelte +247 -0
- package/src/svelte/tabs/index.ts +10 -0
- package/src/svelte/tabs/tab-props.ts +13 -0
- package/src/vue/drawer/index.ts +6 -5
- package/src/vue/stack/index.ts +28 -11
- package/src/vue/tabs/index.ts +68 -65
|
@@ -221,12 +221,14 @@ export class Drawer implements AfterContentInit, OnChanges, OnDestroy, IDrawerNa
|
|
|
221
221
|
this.dispatch({ type: 'toggleDrawer' });
|
|
222
222
|
};
|
|
223
223
|
readonly jumpTo = (name: string): void => {
|
|
224
|
-
//
|
|
225
|
-
//
|
|
226
|
-
//
|
|
224
|
+
// Both sides of the dispatch, mirroring vue/drawer/index.ts's jumpTo: an unregistered name is
|
|
225
|
+
// a documented reducer no-op that hands the SAME state back, so animating off the pre-dispatch
|
|
226
|
+
// snapshot alone would slide the panel shut while the router still says isOpen. The signal is
|
|
227
|
+
// set synchronously inside dispatch, so the second read is already the reducer's own answer.
|
|
227
228
|
const wasOpen = this.stateSignal()?.isOpen ?? false;
|
|
228
229
|
this.dispatch({ type: 'jumpTo', name });
|
|
229
|
-
|
|
230
|
+
const isOpenNow = this.stateSignal()?.isOpen ?? false;
|
|
231
|
+
if (wasOpen && !isOpenNow) this.animateProgressTo(false);
|
|
230
232
|
};
|
|
231
233
|
|
|
232
234
|
readonly panResponder = PanResponder.create({
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
// self-registers instead: `adapters/angular/babel-register-composed.cjs` (a Metro babel preset
|
|
36
36
|
// applied bundle-wide, not scoped to adapters/angular) scans this package's own AOT-compiled
|
|
37
37
|
// (`ngc`) `ɵɵngDeclareComponent({selector: 'Stack', ...})` output and auto-calls
|
|
38
|
-
// `registerComposedComponent('Stack')` at bundle time - same mechanism
|
|
38
|
+
// `registerComposedComponent('Stack')` at bundle time - same mechanism `examples/angular`
|
|
39
39
|
// navigation-demo screens and `@symbiote-native/slider`'s `Slider` rely on for their own composed
|
|
40
40
|
// components mounted statically or via `NgComponentOutlet`. Unregistered, `createElement('Stack')`
|
|
41
41
|
// falls through to a real `createNode` call and RN paints its own "Unimplemented component"
|
|
@@ -73,6 +73,7 @@ import {
|
|
|
73
73
|
createInitialNavigatorState,
|
|
74
74
|
createNavigationEmitter,
|
|
75
75
|
navigatorReducer,
|
|
76
|
+
reconcileStackRoutes,
|
|
76
77
|
resolveHeaderInModalStackStyle,
|
|
77
78
|
resolveScreenRenderPlan,
|
|
78
79
|
resolveStackProps,
|
|
@@ -242,6 +243,20 @@ export class Stack implements AfterContentInit, OnDestroy, INavigatorHandle {
|
|
|
242
243
|
for (const screen of this.screenChildren) {
|
|
243
244
|
this.registry.set(screen.name, screen);
|
|
244
245
|
}
|
|
246
|
+
this.reconcileWithRegistry();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// A `<ng-template symbioteScreen>` marker can leave the @ContentChildren query (a marker behind
|
|
250
|
+
// an `@if`, a data-driven screen list) while its route is still in the pushed history, which
|
|
251
|
+
// would leave that entry with nothing for componentFor() to mount (reconcileStackRoutes' header).
|
|
252
|
+
// The signal write lives HERE, in the query-change callback, and never in a computed/template
|
|
253
|
+
// accessor - reconciliation is a reaction to the registry changing, not a derivation of it.
|
|
254
|
+
private reconcileWithRegistry(): void {
|
|
255
|
+
const current = this.stateSignal();
|
|
256
|
+
if (current === undefined) return;
|
|
257
|
+
const next = reconcileStackRoutes(current, [...this.registry.keys()]);
|
|
258
|
+
if (next === current) return;
|
|
259
|
+
this.commitState(next);
|
|
245
260
|
}
|
|
246
261
|
|
|
247
262
|
private initializeState(): void {
|
|
@@ -269,7 +284,13 @@ export class Stack implements AfterContentInit, OnDestroy, INavigatorHandle {
|
|
|
269
284
|
private dispatch(action: INavigatorAction): void {
|
|
270
285
|
const current = this.stateSignal();
|
|
271
286
|
if (current === undefined) return;
|
|
272
|
-
|
|
287
|
+
this.commitState(navigatorReducer(current, action));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// The one write path into stateSignal, shared by a dispatched action and a registry-driven
|
|
291
|
+
// reconciliation: both invalidate the plan cache, broadcast to every still-live route, and prune
|
|
292
|
+
// the emitters of routes that are gone.
|
|
293
|
+
private commitState(next: INavigatorState): void {
|
|
273
294
|
this.planCache.clear();
|
|
274
295
|
this.stateSignal.set(next);
|
|
275
296
|
const liveRouteKeys = new Set(next.routes.map(route => route.key));
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
createNavigationEmitter,
|
|
40
40
|
diffFocusedRoute,
|
|
41
41
|
isFocusedRoute,
|
|
42
|
+
reconcileTabRoutes,
|
|
42
43
|
renderTabBar,
|
|
43
44
|
tabRouterReducer,
|
|
44
45
|
} from '../../core';
|
|
@@ -114,10 +115,9 @@ export class Tab implements AfterContentInit, OnDestroy, ITabNavigatorHandle {
|
|
|
114
115
|
this.dispatch({ type: 'setParams', key, params });
|
|
115
116
|
|
|
116
117
|
ngAfterContentInit(): void {
|
|
117
|
-
this.
|
|
118
|
-
this.initializeState();
|
|
118
|
+
this.syncRegistry();
|
|
119
119
|
this.tabScreenChildrenSubscription = this.tabScreenChildren.changes.subscribe(() => {
|
|
120
|
-
this.
|
|
120
|
+
this.syncRegistry();
|
|
121
121
|
});
|
|
122
122
|
}
|
|
123
123
|
|
|
@@ -141,10 +141,24 @@ export class Tab implements AfterContentInit, OnDestroy, ITabNavigatorHandle {
|
|
|
141
141
|
}));
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
// A <ng-template symbioteTabScreen> can appear or disappear after mount (a marker behind an
|
|
145
|
+
// @if, a data-driven screen list), so the route list follows the LIVE query instead of staying
|
|
146
|
+
// frozen at whatever ngAfterContentInit first saw: reconcileTabRoutes (core) keeps each
|
|
147
|
+
// surviving route's key and accumulated params and moves focus only when the focused route is
|
|
148
|
+
// the one that left. Runs from the query's own `changes` subscription - an ordinary imperative
|
|
149
|
+
// callback, deliberately not a computed, since this writes the state signal.
|
|
150
|
+
private syncRegistry(): void {
|
|
151
|
+
this.rebuildRegistry();
|
|
146
152
|
const routes = this.routesFromRegistry();
|
|
147
153
|
if (routes.length === 0) dlog('Tab: no <ng-template symbioteTabScreen> children registered');
|
|
154
|
+
const current = this.stateSignal();
|
|
155
|
+
if (current !== undefined) {
|
|
156
|
+
this.stateSignal.set(reconcileTabRoutes(current, routes));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
// Seeding an empty list would resolve initialRouteName against nothing and lose it once the
|
|
160
|
+
// markers do arrive, so the state stays unseeded until at least one has registered.
|
|
161
|
+
if (routes.length === 0) return;
|
|
148
162
|
this.stateSignal.set(createInitialTabState(routes, this.initialRouteName));
|
|
149
163
|
}
|
|
150
164
|
|
package/src/core/index.ts
CHANGED
|
@@ -16,6 +16,11 @@ export {
|
|
|
16
16
|
} from './navigator-state';
|
|
17
17
|
export type { IRoute, INavigatorState, INavigatorAction } from './navigator-state';
|
|
18
18
|
|
|
19
|
+
// Drops history entries whose `<Stack.Screen>` marker has unregistered - see
|
|
20
|
+
// stack-router-state.ts's header for why the Stack's history needs this and the Tab's projection
|
|
21
|
+
// does not.
|
|
22
|
+
export { reconcileStackRoutes } from './stack-router-state';
|
|
23
|
+
|
|
19
24
|
export type {
|
|
20
25
|
IStackAnimation,
|
|
21
26
|
IStackPresentation,
|
|
@@ -89,7 +94,12 @@ export { isRecord } from './guards';
|
|
|
89
94
|
|
|
90
95
|
// Bottom-tabs navigator: a FOCUSED-INDEX router (not a stack - see tab-router-state.ts) driving
|
|
91
96
|
// a pure-JS tab bar render, framework-agnostic and shared verbatim by every adapter.
|
|
92
|
-
export {
|
|
97
|
+
export {
|
|
98
|
+
createInitialTabState,
|
|
99
|
+
tabRouterReducer,
|
|
100
|
+
reconcileTabRoutes,
|
|
101
|
+
isFocusedRoute,
|
|
102
|
+
} from './tab-router-state';
|
|
93
103
|
export type { ITabRouterState, ITabRouterAction } from './tab-router-state';
|
|
94
104
|
|
|
95
105
|
export type { ITabBarIcon, ITabOptions } from './tab-options';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// StackRouter: the registry-reconciliation half (framework-agnostic, zero render). A Stack's route
|
|
2
|
+
// list is navigation HISTORY, not a projection of the registered `<Stack.Screen>` markers the way a
|
|
3
|
+
// Tab's is (tab-router-state) - it seeds exactly one route and grows only through push/replace. So
|
|
4
|
+
// a marker that unregisters while its route still sits in the history (a marker behind an `{#if}`,
|
|
5
|
+
// a data-driven screen list) leaves behind an entry nothing can render: the route stays, the
|
|
6
|
+
// adapter's componentFor() finds no screen, and the user is parked on a blank RNSScreen with no
|
|
7
|
+
// way to tell what happened. Mirrors @react-navigation/routers' StackRouter.getStateForRouteNames-
|
|
8
|
+
// Change, which filters those routes out of the history and pulls the focused index back into
|
|
9
|
+
// range.
|
|
10
|
+
//
|
|
11
|
+
// Deliberately NOT a navigatorReducer action: this is not something a caller dispatches, it is a
|
|
12
|
+
// reaction to the registry changing underneath an unchanged history, and each adapter observes
|
|
13
|
+
// that change through its own lifecycle. Keeping it a separate pure function lets every adapter
|
|
14
|
+
// wire it its own way without teaching the reducer about a registry it has never seen.
|
|
15
|
+
|
|
16
|
+
import type { INavigatorState } from '../navigator-state';
|
|
17
|
+
|
|
18
|
+
// The contract, and the two places it deliberately parts from upstream:
|
|
19
|
+
//
|
|
20
|
+
// - SURVIVORS KEEP THEIR IDENTITY. A stack route's key is COUNTER-derived (every adapter's
|
|
21
|
+
// createRoute increments a per-navigator sequence), not name-derived like a tab route's, so
|
|
22
|
+
// re-deriving the list from the registry would hand every survivor a brand-new key and remount
|
|
23
|
+
// live screens. Only the phantom entries go; every other route object passes through by
|
|
24
|
+
// reference, params included.
|
|
25
|
+
//
|
|
26
|
+
// - THE FOCUSED ROUTE IS THE TOP ONE. This state carries no `index` field: the focused route is
|
|
27
|
+
// always the last one (see computeActivityState's comment for why no route can outrank it), so
|
|
28
|
+
// upstream's `index: Math.min(state.index, routes.length - 1)` degenerates here to "the last
|
|
29
|
+
// survivor". Dropping the FOCUSED route therefore lands the user on the nearest still-registered
|
|
30
|
+
// route beneath it - where a pop() would have left them - rather than on nothing.
|
|
31
|
+
//
|
|
32
|
+
// - THE ROUTE LIST IS NEVER EMPTIED. `routes.length - 1` IS the focused index, so an empty list
|
|
33
|
+
// means no focused route at all - the blank screen this function exists to prevent. When nothing
|
|
34
|
+
// in the history survives (every marker gone, or the whole screen set swapped for new names) the
|
|
35
|
+
// state is returned UNCHANGED. Upstream instead pushes a freshly nanoid()-keyed route for
|
|
36
|
+
// initialRouteName; we cannot, because minting a route key belongs to the caller here
|
|
37
|
+
// (navigator-state.ts's header) and a derivation that mints one would allocate a new key on every
|
|
38
|
+
// re-run. Unchanged is also the right answer for the usual cause of a momentarily empty registry
|
|
39
|
+
// - markers unregistering and re-registering across a re-render - since the next run reconciles
|
|
40
|
+
// for real.
|
|
41
|
+
export function reconcileStackRoutes(
|
|
42
|
+
state: INavigatorState,
|
|
43
|
+
registeredNames: readonly string[],
|
|
44
|
+
): INavigatorState {
|
|
45
|
+
const routes = state.routes.filter(route => registeredNames.includes(route.name));
|
|
46
|
+
// filter preserves order, so an unchanged length means nothing was dropped.
|
|
47
|
+
if (routes.length === state.routes.length || routes.length === 0) return state;
|
|
48
|
+
return { routes };
|
|
49
|
+
}
|
|
@@ -34,6 +34,31 @@ export function createInitialTabState(
|
|
|
34
34
|
return { routes, index: index === -1 ? INITIAL_FOCUSED_INDEX : index };
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
// A screen marker can appear or disappear after mount (a marker behind an `{#if}`, a data-driven
|
|
38
|
+
// screen list), so a navigator's route list has to follow its registry instead of staying frozen
|
|
39
|
+
// at whatever createInitialTabState saw first. Mirrors TabRouter.getStateForRouteNamesChange:
|
|
40
|
+
// membership and order come from `nextRoutes`, but a route that survives keeps its IDENTITY (its
|
|
41
|
+
// key) and everything jumpTo/setParams accumulated onto its params - only genuinely new names
|
|
42
|
+
// take their entry from `nextRoutes`. Focus follows the previously focused route's NAME, so an
|
|
43
|
+
// unrelated screen disappearing never moves the user; when the focused route is the one that
|
|
44
|
+
// disappeared, focus falls back to the first route, the same landing spot every unresolvable
|
|
45
|
+
// focus gets here (createInitialTabState's own fallback).
|
|
46
|
+
export function reconcileTabRoutes(
|
|
47
|
+
state: ITabRouterState,
|
|
48
|
+
nextRoutes: readonly IRouteEntry[],
|
|
49
|
+
): ITabRouterState {
|
|
50
|
+
const routes = nextRoutes.map(
|
|
51
|
+
next => state.routes.find(route => route.name === next.name) ?? next,
|
|
52
|
+
);
|
|
53
|
+
const isUnchanged =
|
|
54
|
+
routes.length === state.routes.length &&
|
|
55
|
+
routes.every((route, at) => route === state.routes[at]);
|
|
56
|
+
if (isUnchanged) return state;
|
|
57
|
+
const focusedName = state.routes[state.index]?.name;
|
|
58
|
+
const index = routes.findIndex(route => route.name === focusedName);
|
|
59
|
+
return { routes, index: index === -1 ? INITIAL_FOCUSED_INDEX : index };
|
|
60
|
+
}
|
|
61
|
+
|
|
37
62
|
// jumpTo moves the focused index to an existing route by name and optionally merges params
|
|
38
63
|
// (navigation.jumpTo(name, params) - the tab-actions doc's own signature); it never adds,
|
|
39
64
|
// removes, or reorders routes (mirrors TabRouter: JUMP_TO is a no-op when the target name isn't
|
|
@@ -53,6 +53,7 @@ import type {
|
|
|
53
53
|
IDrawerDescriptorMap,
|
|
54
54
|
IDrawerNavigatorHandle,
|
|
55
55
|
IDrawerOptions,
|
|
56
|
+
IDrawerRouterAction,
|
|
56
57
|
IDrawerRouterState,
|
|
57
58
|
IDrawerScreenOptions,
|
|
58
59
|
IDrawerSlot,
|
|
@@ -163,8 +164,10 @@ const DrawerImpl = forwardRef<IDrawerNavigatorHandle, IDrawerProps>((props, forw
|
|
|
163
164
|
optionsRef.current = options;
|
|
164
165
|
const screenWidthRef = useRef(screenWidth);
|
|
165
166
|
screenWidthRef.current = screenWidth;
|
|
166
|
-
|
|
167
|
-
|
|
167
|
+
// The whole router state, not just its isOpen bit: jumpTo below has to re-run the reducer to
|
|
168
|
+
// learn what the dispatch actually produced, and that needs the routes too.
|
|
169
|
+
const stateRef = useRef(state);
|
|
170
|
+
stateRef.current = state;
|
|
168
171
|
|
|
169
172
|
const animateProgressTo = useCallback(
|
|
170
173
|
(open: boolean): void => {
|
|
@@ -190,23 +193,31 @@ const DrawerImpl = forwardRef<IDrawerNavigatorHandle, IDrawerProps>((props, forw
|
|
|
190
193
|
const handle = useMemo<IDrawerNavigatorHandle>(
|
|
191
194
|
() => ({
|
|
192
195
|
openDrawer: () => {
|
|
193
|
-
dlog(`Drawer: openDrawer() called, isOpen=${
|
|
196
|
+
dlog(`Drawer: openDrawer() called, isOpen=${stateRef.current.isOpen} at t=${Date.now()}`);
|
|
194
197
|
animateProgressTo(true);
|
|
195
198
|
dispatch({ type: 'openDrawer' });
|
|
196
199
|
},
|
|
197
200
|
closeDrawer: () => {
|
|
198
|
-
dlog(`Drawer: closeDrawer() called, isOpen=${
|
|
201
|
+
dlog(`Drawer: closeDrawer() called, isOpen=${stateRef.current.isOpen} at t=${Date.now()}`);
|
|
199
202
|
animateProgressTo(false);
|
|
200
203
|
dispatch({ type: 'closeDrawer' });
|
|
201
204
|
},
|
|
202
205
|
toggleDrawer: () => {
|
|
203
|
-
dlog(`Drawer: toggleDrawer() called, isOpen=${
|
|
204
|
-
animateProgressTo(!
|
|
206
|
+
dlog(`Drawer: toggleDrawer() called, isOpen=${stateRef.current.isOpen} at t=${Date.now()}`);
|
|
207
|
+
animateProgressTo(!stateRef.current.isOpen);
|
|
205
208
|
dispatch({ type: 'toggleDrawer' });
|
|
206
209
|
},
|
|
207
210
|
jumpTo: (name: string) => {
|
|
208
|
-
|
|
209
|
-
|
|
211
|
+
// An unregistered name is a documented reducer no-op that hands the SAME state back, so
|
|
212
|
+
// animating off `isOpen` alone would slide the panel shut while the router still says
|
|
213
|
+
// open. useReducer's dispatch is async - stateRef only catches up on the next render - so
|
|
214
|
+
// the reducer (a pure function) is re-run here to learn what the dispatch produces, and
|
|
215
|
+
// the animation runs only on a genuine open -> closed transition.
|
|
216
|
+
const action: IDrawerRouterAction = { type: 'jumpTo', name };
|
|
217
|
+
const current = stateRef.current;
|
|
218
|
+
const next = drawerRouterReducer(current, action);
|
|
219
|
+
dispatch(action);
|
|
220
|
+
if (current.isOpen && !next.isOpen) animateProgressTo(false);
|
|
210
221
|
},
|
|
211
222
|
}),
|
|
212
223
|
[animateProgressTo],
|
|
@@ -224,7 +235,7 @@ const DrawerImpl = forwardRef<IDrawerNavigatorHandle, IDrawerProps>((props, forw
|
|
|
224
235
|
event,
|
|
225
236
|
gestureState,
|
|
226
237
|
screenWidthRef.current,
|
|
227
|
-
|
|
238
|
+
stateRef.current.isOpen,
|
|
228
239
|
optionsRef.current,
|
|
229
240
|
'start',
|
|
230
241
|
),
|
|
@@ -236,13 +247,13 @@ const DrawerImpl = forwardRef<IDrawerNavigatorHandle, IDrawerProps>((props, forw
|
|
|
236
247
|
event,
|
|
237
248
|
gestureState,
|
|
238
249
|
screenWidthRef.current,
|
|
239
|
-
|
|
250
|
+
stateRef.current.isOpen,
|
|
240
251
|
optionsRef.current,
|
|
241
252
|
'move',
|
|
242
253
|
),
|
|
243
254
|
onPanResponderGrant: (): void => {
|
|
244
255
|
dlog('Drawer: gesture grant');
|
|
245
|
-
dragStartProgress.current =
|
|
256
|
+
dragStartProgress.current = stateRef.current.isOpen ? 1 : 0;
|
|
246
257
|
},
|
|
247
258
|
onPanResponderMove: (
|
|
248
259
|
_event: ISymbioteEvent,
|
|
@@ -259,7 +270,11 @@ const DrawerImpl = forwardRef<IDrawerNavigatorHandle, IDrawerProps>((props, forw
|
|
|
259
270
|
_event: ISymbioteEvent,
|
|
260
271
|
gestureState: IPanResponderGestureState,
|
|
261
272
|
): void => {
|
|
262
|
-
const intent = resolveSwipeIntent(
|
|
273
|
+
const intent = resolveSwipeIntent(
|
|
274
|
+
gestureState,
|
|
275
|
+
stateRef.current.isOpen,
|
|
276
|
+
optionsRef.current,
|
|
277
|
+
);
|
|
263
278
|
const open = intent === 'open';
|
|
264
279
|
dlog(`Drawer: gesture release -> ${open ? 'open' : 'close'}`);
|
|
265
280
|
animateProgressTo(open);
|
|
@@ -267,7 +282,7 @@ const DrawerImpl = forwardRef<IDrawerNavigatorHandle, IDrawerProps>((props, forw
|
|
|
267
282
|
},
|
|
268
283
|
onPanResponderTerminate: (): void => {
|
|
269
284
|
dlog('Drawer: gesture terminated, snapping back');
|
|
270
|
-
animateProgressTo(
|
|
285
|
+
animateProgressTo(stateRef.current.isOpen);
|
|
271
286
|
},
|
|
272
287
|
}),
|
|
273
288
|
).current;
|
package/src/react/stack/index.ts
CHANGED
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
createInitialNavigatorState,
|
|
47
47
|
createNavigationEmitter,
|
|
48
48
|
navigatorReducer,
|
|
49
|
+
reconcileStackRoutes,
|
|
49
50
|
resolveScreenRenderPlan,
|
|
50
51
|
resolveStackProps,
|
|
51
52
|
} from '../../core';
|
|
@@ -122,7 +123,9 @@ const StackImpl = forwardRef<INavigatorHandle, IStackProps>((props, forwardedRef
|
|
|
122
123
|
[routeIdPrefix],
|
|
123
124
|
);
|
|
124
125
|
|
|
125
|
-
const
|
|
126
|
+
const registeredNames = useMemo(() => [...registry.keys()], [registry]);
|
|
127
|
+
|
|
128
|
+
const [dispatchedState, dispatch] = useReducer(navigatorReducer, undefined, () => {
|
|
126
129
|
const initialRouteName = props.initialRouteName ?? registry.keys().next().value;
|
|
127
130
|
if (initialRouteName === undefined) {
|
|
128
131
|
dlog('Stack: no <Stack.Screen> children registered');
|
|
@@ -133,6 +136,17 @@ const StackImpl = forwardRef<INavigatorHandle, IStackProps>((props, forwardedRef
|
|
|
133
136
|
);
|
|
134
137
|
});
|
|
135
138
|
|
|
139
|
+
// A <Stack.Screen> marker can unregister while its route is still in the pushed history, which
|
|
140
|
+
// would leave that entry with nothing to render (reconcileStackRoutes' header). Reconciling here
|
|
141
|
+
// rather than in an effect repairs the CURRENT paint - the call is pure and hands back the same
|
|
142
|
+
// reference when nothing changed, so it is safe during render; the effect below only PERSISTS
|
|
143
|
+
// the repair, so the next push builds on the pruned history instead of the phantom one.
|
|
144
|
+
const state = reconcileStackRoutes(dispatchedState, registeredNames);
|
|
145
|
+
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
if (state !== dispatchedState) dispatch({ type: 'reset', state });
|
|
148
|
+
}, [state, dispatchedState]);
|
|
149
|
+
|
|
136
150
|
const handle = useMemo<INavigatorHandle>(
|
|
137
151
|
() => ({
|
|
138
152
|
push: (name, params) => dispatch({ type: 'push', route: createRoute(name, params) }),
|
package/src/react/tabs/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Tab, the React lifecycle half. The focused-index router (tab-router-state) and the tab-bar
|
|
2
2
|
// Descriptor builder (render-tabs) live in @symbiote-native/navigation core, shared verbatim
|
|
3
|
-
// with the Vue/Angular adapters; here React supplies the lifecycle -
|
|
4
|
-
//
|
|
5
|
-
// plus the descriptor bridge for the tab-bar leaf, exactly like Stack bridges its header
|
|
6
|
-
// (react/stack.ts). Unlike Stack, a bottom-tabs bar is a PURE-JS UI: it paints ordinary
|
|
3
|
+
// with the Vue/Angular adapters; here React supplies the lifecycle - useState for the dispatched
|
|
4
|
+
// router state, useId for route-key generation, useImperativeHandle for the jumpTo/setParams
|
|
5
|
+
// handle - plus the descriptor bridge for the tab-bar leaf, exactly like Stack bridges its header
|
|
6
|
+
// config (react/stack.ts). Unlike Stack, a bottom-tabs bar is a PURE-JS UI: it paints ordinary
|
|
7
7
|
// `symbiote-view`/`symbiote-text` primitives via the shared render fn, so there is no
|
|
8
8
|
// react-native-screens ViewConfig to register here - Tab needs no `../register` import.
|
|
9
9
|
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
useId,
|
|
18
18
|
useImperativeHandle,
|
|
19
19
|
useMemo,
|
|
20
|
-
|
|
20
|
+
useState,
|
|
21
21
|
} from 'react';
|
|
22
22
|
import type { ReactElement, ReactNode } from 'react';
|
|
23
23
|
import { descriptorToReact } from '@symbiote-native/react';
|
|
@@ -28,10 +28,18 @@ import {
|
|
|
28
28
|
createInitialTabState,
|
|
29
29
|
createNavigationEmitter,
|
|
30
30
|
isFocusedRoute,
|
|
31
|
+
reconcileTabRoutes,
|
|
31
32
|
renderTabBar,
|
|
32
33
|
tabRouterReducer,
|
|
33
34
|
} from '../../core';
|
|
34
|
-
import type {
|
|
35
|
+
import type {
|
|
36
|
+
IRoute,
|
|
37
|
+
ITabBarItemView,
|
|
38
|
+
ITabNavigatorHandle,
|
|
39
|
+
ITabOptions,
|
|
40
|
+
ITabRouterAction,
|
|
41
|
+
ITabRouterState,
|
|
42
|
+
} from '../../core';
|
|
35
43
|
import { collectRegistry } from '../collect-registry';
|
|
36
44
|
import { NavigationContext } from '../navigation-context';
|
|
37
45
|
import { TabScreen } from '../tab-screen';
|
|
@@ -84,19 +92,44 @@ const TabImpl = forwardRef<ITabNavigatorHandle, ITabProps>((props, forwardedRef)
|
|
|
84
92
|
[registry, routeIdPrefix],
|
|
85
93
|
);
|
|
86
94
|
|
|
87
|
-
|
|
88
|
-
|
|
95
|
+
// A <Tab.Screen> can appear or disappear after mount (a marker behind a conditional, a
|
|
96
|
+
// data-driven screen list), so the route list is re-derived from `children` on every render and
|
|
97
|
+
// the STORED state is reconciled against it (reconcileTabRoutes, core - it preserves each
|
|
98
|
+
// surviving route's key and accumulated params and keeps focus on the same route NAME) rather
|
|
99
|
+
// than staying frozen at whatever the first render saw. `null` means no marker had registered
|
|
100
|
+
// yet: seeding an empty list would resolve initialRouteName against nothing and lose it, so the
|
|
101
|
+
// state stays derived until either a marker arrives or something dispatches.
|
|
102
|
+
const [storedState, setStoredState] = useState<ITabRouterState | null>(() =>
|
|
103
|
+
routes.length === 0 ? null : createInitialTabState(routes, props.initialRouteName),
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const resolveState = useCallback(
|
|
107
|
+
(stored: ITabRouterState | null): ITabRouterState =>
|
|
108
|
+
stored === null
|
|
109
|
+
? createInitialTabState(routes, props.initialRouteName)
|
|
110
|
+
: reconcileTabRoutes(stored, routes),
|
|
111
|
+
[routes, props.initialRouteName],
|
|
89
112
|
);
|
|
90
113
|
|
|
114
|
+
const state = resolveState(storedState);
|
|
115
|
+
|
|
91
116
|
if (routes.length === 0) dlog('Tab: no <Tab.Screen> children registered');
|
|
92
117
|
|
|
118
|
+
// The updater re-resolves rather than closing over `state` so that two dispatches batched into
|
|
119
|
+
// one render still compose: the second reduces over the first's result, not a stale snapshot.
|
|
120
|
+
const dispatch = useCallback(
|
|
121
|
+
(action: ITabRouterAction) =>
|
|
122
|
+
setStoredState(current => tabRouterReducer(resolveState(current), action)),
|
|
123
|
+
[resolveState],
|
|
124
|
+
);
|
|
125
|
+
|
|
93
126
|
const jumpTo = useCallback(
|
|
94
127
|
(name: string, params?: unknown) => dispatch({ type: 'jumpTo', name, params }),
|
|
95
|
-
[],
|
|
128
|
+
[dispatch],
|
|
96
129
|
);
|
|
97
130
|
const setParams = useCallback(
|
|
98
131
|
(params: unknown, key: string) => dispatch({ type: 'setParams', key, params }),
|
|
99
|
-
[],
|
|
132
|
+
[dispatch],
|
|
100
133
|
);
|
|
101
134
|
|
|
102
135
|
const handle = useMemo<ITabNavigatorHandle>(() => ({ jumpTo, setParams }), [jumpTo, setParams]);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Svelte attachments (`{@attach ...}`) for the react-native-screens native leaves.
|
|
2
|
+
//
|
|
3
|
+
// WHY THESE EXIST AT ALL: every react-native-screens Fabric view name is capitalized and
|
|
4
|
+
// un-hyphenated (RNSScreen, RNSScreenStack, RNSScreenStackHeaderConfig, RNSSearchBar, ...), so a
|
|
5
|
+
// literal `<RNSScreen>` in a Svelte template parses as a COMPONENT reference, not an element -
|
|
6
|
+
// the same wall @symbiote-native/slider's 'RNCSlider' leaf hit. Slider sidestepped it by mounting
|
|
7
|
+
// the whole leaf through the Descriptor bridge, which works because that leaf carries no live
|
|
8
|
+
// framework children. A stack screen DOES (the app's own screen component lives inside
|
|
9
|
+
// RNSScreenContentWrapper), so the bridge is not an option here.
|
|
10
|
+
//
|
|
11
|
+
// The way through is `<svelte:element this={'RNSScreen'}>`: it creates the element via
|
|
12
|
+
// `document.createElement(tag)` with a plain runtime string the compiler never inspects, and it
|
|
13
|
+
// accepts ordinary framework children. Its one catch is documented in svelte-adapter-dom-shim
|
|
14
|
+
// skill §15 - a dynamic tag compiles through Svelte's generic setAttribute/property-diffing
|
|
15
|
+
// codegen, NOT the custom-element `p=` property-SET path, so writing `p={bag}` as an ATTRIBUTE on
|
|
16
|
+
// it silently fails. An attachment bypasses that entirely: it is handed the raw element and
|
|
17
|
+
// assigns the property from plain JS, then re-runs whenever the props it read change. Verified
|
|
18
|
+
// against the real compiler + the real shim before this was built on.
|
|
19
|
+
|
|
20
|
+
import { dlog, isSymbioteNode } from '@symbiote-native/engine';
|
|
21
|
+
import type { ShimElement } from '@symbiote-native/svelte/native-view-bridge';
|
|
22
|
+
import { buildSearchBarHandle } from '../core';
|
|
23
|
+
import type { ISearchBarCommands } from '../core';
|
|
24
|
+
|
|
25
|
+
// `unknown` rather than `Element`: the value Svelte hands an attachment is one of the DOM shim's
|
|
26
|
+
// own element classes, not a real DOM Element, and a function accepting `unknown` is still
|
|
27
|
+
// assignable everywhere Svelte expects `(element: Element) => ...`.
|
|
28
|
+
function isShimElement(value: unknown): value is ShimElement {
|
|
29
|
+
return typeof value === 'object' && value !== null && 'engineNode' in value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// The object-bag prop set, the same entry point every adapter's props go through (routeProp).
|
|
33
|
+
// Re-runs on every change of `props`, and the shim's own `p` setter diffs per key from there.
|
|
34
|
+
export function hostProps(props: Record<string, unknown>): (node: unknown) => void {
|
|
35
|
+
return node => {
|
|
36
|
+
if (!isShimElement(node)) {
|
|
37
|
+
dlog('navigation: hostProps attached to a non-shim element, ignored');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
node.p = props;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// The Svelte twin of Angular's SearchBarRefDirective / React's and Vue's callback `ref` on the
|
|
45
|
+
// RNSSearchBar element: fills the app-supplied cell once the native node exists and clears it on
|
|
46
|
+
// teardown. buildSearchBarHandle's own getter is LAZY, so handing it the node here is safe even
|
|
47
|
+
// before the first commit.
|
|
48
|
+
export function searchBarRef(
|
|
49
|
+
ref: { current: ISearchBarCommands | null } | undefined,
|
|
50
|
+
): (node: unknown) => (() => void) | void {
|
|
51
|
+
return node => {
|
|
52
|
+
if (ref === undefined) return;
|
|
53
|
+
if (!isShimElement(node)) {
|
|
54
|
+
ref.current = null;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const engineNode = node.engineNode;
|
|
58
|
+
if (!isSymbioteNode(engineNode)) {
|
|
59
|
+
dlog('navigation: search bar attachment ran before the shim went live');
|
|
60
|
+
ref.current = null;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
ref.current = buildSearchBarHandle(() => engineNode);
|
|
64
|
+
return () => {
|
|
65
|
+
ref.current = null;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// A shape-CHANGE-tolerant wrapper over @symbiote-native/svelte's Descriptor bridge.
|
|
2
|
+
//
|
|
3
|
+
// `mountDescriptorChildren` (svelte-adapter-dom-shim skill §19) is deliberately shape-stable: it
|
|
4
|
+
// builds each shim node once and only re-sets props thereafter, throwing if the tree shape ever
|
|
5
|
+
// changes, because every `core/components` render-*.ts produces a constant shape. The navigation
|
|
6
|
+
// package's tab bar is the exception - `renderTabBar` emits one child per registered route, and
|
|
7
|
+
// that count genuinely varies (it is 0 until the `<Tab.Screen>` markers have registered, and
|
|
8
|
+
// changes again if a screen is added or removed). Same situation @symbiote-native/slider's own
|
|
9
|
+
// steps-indicator overlay is in, and the same resolution: keep the fast path for the common
|
|
10
|
+
// "same shape, new prop values" case and rebuild only when the shape actually changed.
|
|
11
|
+
//
|
|
12
|
+
// The shape signature covers types and nesting, not just the top-level count, because a tab bar
|
|
13
|
+
// item's own children vary too (an item gains an icon wrapper only when it has an icon or badge).
|
|
14
|
+
|
|
15
|
+
import { mountDescriptorChildren } from '@symbiote-native/svelte/native-view-bridge';
|
|
16
|
+
import type {
|
|
17
|
+
IDescriptorChildrenMount,
|
|
18
|
+
ShimElement,
|
|
19
|
+
} from '@symbiote-native/svelte/native-view-bridge';
|
|
20
|
+
import type { IDescriptorChild } from '@symbiote-native/components';
|
|
21
|
+
|
|
22
|
+
const TEXT_CHILD_MARK = '#';
|
|
23
|
+
|
|
24
|
+
function shapeOf(children: readonly IDescriptorChild[]): string {
|
|
25
|
+
return children
|
|
26
|
+
.map(child =>
|
|
27
|
+
typeof child === 'string' ? TEXT_CHILD_MARK : `${child.type}(${shapeOf(child.children)})`,
|
|
28
|
+
)
|
|
29
|
+
.join(',');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function createDescriptorSubtreeSync(): (
|
|
33
|
+
host: ShimElement | null,
|
|
34
|
+
children: IDescriptorChild[],
|
|
35
|
+
) => void {
|
|
36
|
+
let mounted: IDescriptorChildrenMount | undefined;
|
|
37
|
+
let shape: string | undefined;
|
|
38
|
+
return (host, children) => {
|
|
39
|
+
if (host === null) return;
|
|
40
|
+
const nextShape = shapeOf(children);
|
|
41
|
+
if (mounted === undefined || nextShape !== shape) {
|
|
42
|
+
for (const child of host.children.slice()) host.removeChild(child);
|
|
43
|
+
mounted = mountDescriptorChildren(host, children);
|
|
44
|
+
shape = nextShape;
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
mounted.update(children);
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Drawer's own public prop surface. Plain `.ts` for the tsc-cannot-see-a-`.svelte`-named-export
|
|
2
|
+
// reason ../screen-props.ts documents.
|
|
3
|
+
|
|
4
|
+
import type { Snippet } from 'svelte';
|
|
5
|
+
import type { IStyleProp, IViewStyle } from '@symbiote-native/engine';
|
|
6
|
+
import type {
|
|
7
|
+
IDrawerDescriptorMap,
|
|
8
|
+
IDrawerNavigatorHandle,
|
|
9
|
+
IDrawerOptions,
|
|
10
|
+
IDrawerRouterState,
|
|
11
|
+
} from '../../core';
|
|
12
|
+
|
|
13
|
+
// React's `renderDrawerContent` render-PROP becomes a Snippet WITH A PARAMETER here (Vue's twin
|
|
14
|
+
// is a scoped slot) - the drawer panel's content is app-authored and needs the router state, the
|
|
15
|
+
// per-route options map, and the navigator handle to paint a menu.
|
|
16
|
+
export type IDrawerContentSlotProps = {
|
|
17
|
+
state: IDrawerRouterState;
|
|
18
|
+
descriptors: IDrawerDescriptorMap;
|
|
19
|
+
navigation: IDrawerNavigatorHandle;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type IDrawerProps = IDrawerOptions & {
|
|
23
|
+
initialRouteName?: string;
|
|
24
|
+
drawerStyle?: IStyleProp<IViewStyle>;
|
|
25
|
+
// The `<Drawer.Screen>` markers.
|
|
26
|
+
children?: Snippet;
|
|
27
|
+
drawerContent?: Snippet<[IDrawerContentSlotProps]>;
|
|
28
|
+
};
|