@symbiote-native/navigation 2.1.0 → 3.0.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/README.md +89 -14
- package/build/angular/drawer/index.js +23 -9
- package/build/angular/index.d.ts +4 -4
- package/build/angular/index.js +1 -1
- package/build/angular/injectors/inject-navigation.js +1 -1
- package/build/angular/navigation-scope.directive.js +1 -1
- package/build/angular/search-bar-ref.directive.js +1 -1
- package/build/angular/stack/index.js +20 -6
- package/build/angular/tabs/index.js +21 -7
- package/build/core/drawer-options/index.js +19 -5
- package/build/core/drawer-router-state/index.js +8 -2
- package/build/core/guards.js +3 -1
- package/build/core/header-bar-buttons.js +14 -4
- package/build/core/index.d.ts +4 -4
- package/build/core/index.js +2 -2
- package/build/core/navigator-state/index.js +12 -4
- package/build/core/render-drawer.js +4 -1
- package/build/core/render-stack.js +13 -4
- package/build/core/screen-options.js +1 -1
- package/build/core/search-bar-commands.js +1 -1
- package/build/core/state-persistence/index.js +6 -2
- package/build/core/tab-router-state/index.js +8 -2
- package/build/react/drawer/index.js +36 -13
- package/build/react/index.d.ts +7 -7
- package/build/react/index.js +3 -3
- package/build/react/stack/index.js +34 -8
- package/build/react/tabs/index.js +14 -4
- package/build/solid/drawer/index.d.ts +22 -0
- package/build/solid/drawer/index.js +394 -0
- package/build/solid/drawer-screen-props.d.ts +13 -0
- package/build/solid/drawer-screen-props.js +2 -0
- package/build/solid/host.d.ts +2 -0
- package/build/solid/host.js +23 -0
- package/build/solid/index.d.ts +19 -0
- package/build/solid/index.js +24 -0
- package/build/solid/linking.d.ts +3 -0
- package/build/solid/linking.js +55 -0
- package/build/solid/navigation-context.d.ts +13 -0
- package/build/solid/navigation-context.js +37 -0
- package/build/solid/primitives/create-focus-effect.d.ts +1 -0
- package/build/solid/primitives/create-focus-effect.js +30 -0
- package/build/solid/primitives/create-is-focused.d.ts +2 -0
- package/build/solid/primitives/create-is-focused.js +31 -0
- package/build/solid/primitives/create-navigation-state.d.ts +3 -0
- package/build/solid/primitives/create-navigation-state.js +30 -0
- package/build/solid/primitives/index.d.ts +12 -0
- package/build/solid/primitives/index.js +8 -0
- package/build/solid/primitives/use-drawer-navigation.d.ts +5 -0
- package/build/solid/primitives/use-drawer-navigation.js +15 -0
- package/build/solid/primitives/use-navigation.d.ts +8 -0
- package/build/solid/primitives/use-navigation.js +23 -0
- package/build/solid/primitives/use-route.d.ts +3 -0
- package/build/solid/primitives/use-route.js +14 -0
- package/build/solid/primitives/use-stack-navigation.d.ts +5 -0
- package/build/solid/primitives/use-stack-navigation.js +19 -0
- package/build/solid/primitives/use-tab-navigation.d.ts +5 -0
- package/build/solid/primitives/use-tab-navigation.js +15 -0
- package/build/solid/screen-props.d.ts +19 -0
- package/build/solid/screen-props.js +9 -0
- package/build/solid/screen-registry.d.ts +31 -0
- package/build/solid/screen-registry.js +86 -0
- package/build/solid/screen.d.ts +6 -0
- package/build/solid/screen.js +69 -0
- package/build/solid/stack/index.d.ts +15 -0
- package/build/solid/stack/index.js +348 -0
- package/build/solid/tab-screen-props.d.ts +13 -0
- package/build/solid/tab-screen-props.js +3 -0
- package/build/solid/tabs/index.d.ts +14 -0
- package/build/solid/tabs/index.js +228 -0
- package/build/svelte/descriptor-subtree.js +3 -1
- package/build/svelte/drawer/index.svelte +163 -25
- package/build/svelte/drawer-screen.svelte +2 -1
- package/build/svelte/index.d.ts +3 -3
- package/build/svelte/index.js +1 -1
- package/build/svelte/navigation-scope.svelte +4 -1
- package/build/svelte/runes/use-navigation.svelte.js +5 -1
- package/build/svelte/stack/index.svelte +43 -6
- package/build/svelte/stack/stack-screen.svelte +66 -9
- package/build/svelte/tabs/index.svelte +42 -20
- package/build/vue/composables/use-navigation.js +5 -1
- package/build/vue/drawer/index.js +30 -8
- package/build/vue/index.d.ts +3 -3
- package/build/vue/index.js +1 -1
- package/build/vue/navigation-context.js +1 -1
- package/build/vue/stack/index.js +17 -6
- package/build/vue/tabs/index.js +18 -5
- package/build-ngc/angular/drawer/index.js +31 -11
- package/build-ngc/angular/drawer/index.js.map +1 -1
- package/build-ngc/angular/index.d.ts +4 -4
- package/build-ngc/angular/index.js.map +1 -1
- package/build-ngc/angular/injectors/inject-focus-effect.js.map +1 -1
- package/build-ngc/angular/injectors/inject-is-focused.js.map +1 -1
- package/build-ngc/angular/injectors/inject-navigation-state.js.map +1 -1
- package/build-ngc/angular/injectors/inject-navigation.js +1 -1
- package/build-ngc/angular/injectors/inject-navigation.js.map +1 -1
- package/build-ngc/angular/linking.js.map +1 -1
- package/build-ngc/angular/navigation-context.service.js.map +1 -1
- package/build-ngc/angular/navigation-scope.directive.js +1 -1
- package/build-ngc/angular/navigation-scope.directive.js.map +1 -1
- package/build-ngc/angular/screen.directive.js.map +1 -1
- package/build-ngc/angular/search-bar-ref.directive.js +1 -1
- package/build-ngc/angular/search-bar-ref.directive.js.map +1 -1
- package/build-ngc/angular/stack/index.js +26 -8
- package/build-ngc/angular/stack/index.js.map +1 -1
- package/build-ngc/angular/tab-screen.directive.js.map +1 -1
- package/build-ngc/angular/tabs/index.js +21 -7
- package/build-ngc/angular/tabs/index.js.map +1 -1
- package/build-ngc/core/constants.js.map +1 -1
- package/build-ngc/core/drawer-options/index.js +19 -5
- package/build-ngc/core/drawer-options/index.js.map +1 -1
- package/build-ngc/core/drawer-router-state/index.js +8 -2
- package/build-ngc/core/drawer-router-state/index.js.map +1 -1
- package/build-ngc/core/guards.js +3 -1
- package/build-ngc/core/guards.js.map +1 -1
- package/build-ngc/core/header-bar-buttons.js +14 -4
- package/build-ngc/core/header-bar-buttons.js.map +1 -1
- package/build-ngc/core/index.d.ts +4 -4
- package/build-ngc/core/index.js +2 -2
- package/build-ngc/core/index.js.map +1 -1
- package/build-ngc/core/linking-config/index.js.map +1 -1
- package/build-ngc/core/navigation-events/index.js.map +1 -1
- package/build-ngc/core/navigator-handles/index.js.map +1 -1
- package/build-ngc/core/navigator-state/index.js +12 -4
- package/build-ngc/core/navigator-state/index.js.map +1 -1
- package/build-ngc/core/render-drawer.js +4 -1
- package/build-ngc/core/render-drawer.js.map +1 -1
- package/build-ngc/core/render-stack.js +13 -4
- package/build-ngc/core/render-stack.js.map +1 -1
- package/build-ngc/core/render-tabs.js.map +1 -1
- package/build-ngc/core/screen-options.js +1 -1
- package/build-ngc/core/screen-options.js.map +1 -1
- package/build-ngc/core/search-bar-commands.js +1 -1
- package/build-ngc/core/search-bar-commands.js.map +1 -1
- package/build-ngc/core/stack-router-state/index.js.map +1 -1
- package/build-ngc/core/state-persistence/index.js +6 -2
- package/build-ngc/core/state-persistence/index.js.map +1 -1
- package/build-ngc/core/tab-router-state/index.js +8 -2
- package/build-ngc/core/tab-router-state/index.js.map +1 -1
- package/package.json +40 -15
- package/src/angular/drawer/index.ts +69 -25
- package/src/angular/index.ts +20 -4
- package/src/angular/injectors/inject-focus-effect.ts +8 -2
- package/src/angular/injectors/inject-is-focused.ts +6 -4
- package/src/angular/injectors/inject-navigation-state.ts +7 -4
- package/src/angular/injectors/inject-navigation.ts +12 -3
- package/src/angular/linking.ts +4 -1
- package/src/angular/navigation-context.service.ts +5 -2
- package/src/angular/navigation-scope.directive.ts +6 -2
- package/src/angular/screen.directive.ts +13 -3
- package/src/angular/search-bar-ref.directive.ts +10 -2
- package/src/angular/stack/index.ts +68 -22
- package/src/angular/tab-screen.directive.ts +3 -1
- package/src/angular/tabs/index.ts +36 -12
- package/src/core/constants.ts +6 -3
- package/src/core/drawer-options/index.ts +40 -13
- package/src/core/drawer-router-state/index.ts +11 -3
- package/src/core/guards.ts +3 -1
- package/src/core/header-bar-buttons.ts +41 -12
- package/src/core/index.ts +18 -4
- package/src/core/linking-config/index.ts +31 -10
- package/src/core/navigation-events/index.ts +8 -2
- package/src/core/navigator-handles/index.ts +8 -3
- package/src/core/navigator-props.ts +13 -4
- package/src/core/navigator-state/index.ts +24 -7
- package/src/core/render-drawer.ts +24 -9
- package/src/core/render-stack.ts +52 -13
- package/src/core/render-tabs.ts +24 -6
- package/src/core/screen-options.ts +15 -5
- package/src/core/search-bar-commands.ts +12 -3
- package/src/core/stack-router-state/index.ts +5 -2
- package/src/core/state-persistence/index.ts +13 -3
- package/src/core/tab-router-state/index.ts +10 -3
- package/src/react/drawer/index.ts +408 -344
- package/src/react/drawer-screen.ts +8 -2
- package/src/react/hooks/use-focus-effect.ts +8 -2
- package/src/react/hooks/use-is-focused.ts +6 -2
- package/src/react/hooks/use-navigation.ts +8 -2
- package/src/react/index.ts +37 -7
- package/src/react/linking.ts +4 -1
- package/src/react/navigation-context.ts +6 -2
- package/src/react/screen.ts +12 -3
- package/src/react/stack/index.ts +294 -231
- package/src/react/tab-screen.ts +3 -1
- package/src/react/tabs/index.ts +188 -158
- package/src/solid/drawer/index.ts +570 -0
- package/src/solid/drawer-screen-props.ts +24 -0
- package/src/solid/host.ts +29 -0
- package/src/solid/index.ts +87 -0
- package/src/solid/linking.ts +74 -0
- package/src/solid/navigation-context.ts +56 -0
- package/src/solid/primitives/create-focus-effect.ts +41 -0
- package/src/solid/primitives/create-is-focused.ts +41 -0
- package/src/solid/primitives/create-navigation-state.ts +44 -0
- package/src/solid/primitives/index.ts +12 -0
- package/src/solid/primitives/use-drawer-navigation.ts +25 -0
- package/src/solid/primitives/use-navigation.ts +44 -0
- package/src/solid/primitives/use-route.ts +18 -0
- package/src/solid/primitives/use-stack-navigation.ts +29 -0
- package/src/solid/primitives/use-tab-navigation.ts +25 -0
- package/src/solid/screen-props.ts +51 -0
- package/src/solid/screen-registry.ts +159 -0
- package/src/solid/screen.ts +80 -0
- package/src/solid/stack/index.ts +528 -0
- package/src/solid/tab-screen-props.ts +21 -0
- package/src/solid/tabs/index.ts +327 -0
- package/src/svelte/attachments.ts +3 -1
- package/src/svelte/descriptor-subtree.ts +3 -1
- package/src/svelte/drawer/index.svelte +163 -25
- package/src/svelte/drawer-screen-props.ts +8 -2
- package/src/svelte/drawer-screen.svelte +2 -1
- package/src/svelte/fabric-tree.test-helper.ts +37 -3
- package/src/svelte/index.ts +15 -3
- package/src/svelte/linking.svelte.ts +11 -5
- package/src/svelte/navigation-scope.svelte +4 -1
- package/src/svelte/runes/use-drawer-navigation.svelte.ts +3 -1
- package/src/svelte/runes/use-focus-effect.svelte.ts +8 -2
- package/src/svelte/runes/use-navigation-state.svelte.ts +3 -1
- package/src/svelte/runes/use-navigation.svelte.ts +13 -3
- package/src/svelte/runes/use-stack-navigation.svelte.ts +3 -1
- package/src/svelte/screen-props.ts +7 -2
- package/src/svelte/screen-registry.ts +9 -3
- package/src/svelte/stack/index.svelte +43 -6
- package/src/svelte/stack/stack-screen.svelte +66 -9
- package/src/svelte/svelte-compile.test-helper.ts +16 -12
- package/src/svelte/tab-screen-props.ts +3 -1
- package/src/svelte/tabs/index.svelte +42 -20
- package/src/vue/composables/use-navigation-state.ts +7 -4
- package/src/vue/composables/use-navigation.ts +13 -3
- package/src/vue/drawer/index.ts +122 -36
- package/src/vue/drawer-screen.ts +12 -5
- package/src/vue/index.ts +15 -3
- package/src/vue/linking.ts +7 -2
- package/src/vue/navigation-context.ts +17 -5
- package/src/vue/screen.ts +7 -2
- package/src/vue/stack/index.ts +82 -34
- package/src/vue/tab-screen.ts +3 -1
- package/src/vue/tabs/index.ts +51 -13
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
// Tab, the Solid lifecycle half. The focused-index router (tab-router-state) and the tab-bar
|
|
2
|
+
// Descriptor builder (render-tabs) live in @symbiote-native/navigation core, shared verbatim with
|
|
3
|
+
// every other adapter; here Solid supplies the lifecycle - a signal for the dispatched router
|
|
4
|
+
// state, a callback `ref` for the jumpTo/setParams handle - plus the descriptor bridge for the
|
|
5
|
+
// tab-bar leaf. Unlike Stack, a bottom-tabs bar is a PURE-JS UI painting ordinary
|
|
6
|
+
// `symbiote-view`/`symbiote-text` primitives, so there is no react-native-screens ViewConfig to
|
|
7
|
+
// register here.
|
|
8
|
+
//
|
|
9
|
+
// TWO SOLID-ONLY REBUILD BOUNDARIES, both invisible in every other adapter:
|
|
10
|
+
//
|
|
11
|
+
// 1. THE TAB BAR'S SHAPE FOLLOWS ITS DATA. renderTabBar emits one child per item, and each item
|
|
12
|
+
// emits an icon wrapper only when it has an icon or a badge - so adding a screen, or a badge
|
|
13
|
+
// appearing, CHANGES the Descriptor's shape, which descriptorToSolid's shape guard rejects by
|
|
14
|
+
// design (it builds once and updates props in place). `barSignature` captures exactly the parts
|
|
15
|
+
// of the data the shape depends on, and the bar is rebuilt when that string changes and only
|
|
16
|
+
// then. Every other adapter re-renders the whole bar every time and never meets this.
|
|
17
|
+
//
|
|
18
|
+
// 2. ONLY THE FOCUSED SCREEN IS MOUNTED, so a focus change is a genuine rebuild while a
|
|
19
|
+
// `setParams` on the focused route must NOT be. The content memo therefore depends on the
|
|
20
|
+
// focused route KEY and its component identity alone, with the build `untrack`ed
|
|
21
|
+
// (.claude/rules/solid-descriptor-bridge.md §5); the route object itself reaches the screen
|
|
22
|
+
// through the scope accessor, which is what keeps useRoute() live without rebuilding anything.
|
|
23
|
+
|
|
24
|
+
import { createEffect, createMemo, createSignal, untrack } from 'solid-js';
|
|
25
|
+
import { descriptorToSolid } from '@symbiote-native/solid';
|
|
26
|
+
import type { JSX } from '@symbiote-native/solid/jsx-runtime';
|
|
27
|
+
// createComponent from solid-js, insert from the renderer - see stack/index.ts's note.
|
|
28
|
+
import { createComponent } from 'solid-js';
|
|
29
|
+
import { insert } from '@symbiote-native/solid/renderer';
|
|
30
|
+
import type { ISymbioteNode } from '@symbiote-native/engine';
|
|
31
|
+
import { dlog } from '@symbiote-native/engine';
|
|
32
|
+
import {
|
|
33
|
+
NAVIGATION_EVENT_BLUR,
|
|
34
|
+
NAVIGATION_EVENT_FOCUS,
|
|
35
|
+
createInitialTabState,
|
|
36
|
+
createNavigationEmitter,
|
|
37
|
+
diffFocusedRoute,
|
|
38
|
+
isFocusedRoute,
|
|
39
|
+
reconcileTabRoutes,
|
|
40
|
+
renderTabBar,
|
|
41
|
+
tabRouterReducer,
|
|
42
|
+
} from '../../core';
|
|
43
|
+
import type {
|
|
44
|
+
INavigationEmitter,
|
|
45
|
+
IRoute,
|
|
46
|
+
ITabBarIcon,
|
|
47
|
+
ITabBarItemView,
|
|
48
|
+
ITabNavigatorHandle,
|
|
49
|
+
ITabOptions,
|
|
50
|
+
ITabRouterAction,
|
|
51
|
+
ITabRouterState,
|
|
52
|
+
} from '../../core';
|
|
53
|
+
import { hostElement } from '../host';
|
|
54
|
+
import {
|
|
55
|
+
NavigationScopeProvider,
|
|
56
|
+
useNavigationScope,
|
|
57
|
+
} from '../navigation-context';
|
|
58
|
+
import type { INavigationScope } from '../navigation-context';
|
|
59
|
+
import {
|
|
60
|
+
ScreenCollectorProvider,
|
|
61
|
+
createScreenSignal,
|
|
62
|
+
toRegistry,
|
|
63
|
+
} from '../screen-registry';
|
|
64
|
+
import type { IRegisteredScreen } from '../screen-registry';
|
|
65
|
+
import { TabScreen } from '../screen';
|
|
66
|
+
import type {
|
|
67
|
+
ITabScreenOptionsArgs,
|
|
68
|
+
ITabScreenProps,
|
|
69
|
+
} from '../tab-screen-props';
|
|
70
|
+
|
|
71
|
+
export type { ITabNavigatorHandle } from '../../core';
|
|
72
|
+
|
|
73
|
+
export type ITabProps = {
|
|
74
|
+
initialRouteName?: string;
|
|
75
|
+
screenOptions?: ITabOptions;
|
|
76
|
+
ref?: (handle: ITabNavigatorHandle) => void;
|
|
77
|
+
children?: JSX.Element;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const TAB_CONTENT_STYLE = { flex: 1 };
|
|
81
|
+
const TAB_ROOT_STYLE = { flex: 1 };
|
|
82
|
+
|
|
83
|
+
let navigatorSequence = 0;
|
|
84
|
+
|
|
85
|
+
type ITabScreenEntry = IRegisteredScreen<ITabScreenProps['options']>;
|
|
86
|
+
|
|
87
|
+
function resolveTabOptions(
|
|
88
|
+
entry: ITabScreenEntry,
|
|
89
|
+
args: ITabScreenOptionsArgs,
|
|
90
|
+
screenOptions: ITabOptions | undefined,
|
|
91
|
+
): ITabOptions {
|
|
92
|
+
const own =
|
|
93
|
+
typeof entry.options === 'function' ? entry.options(args) : entry.options;
|
|
94
|
+
return { ...screenOptions, ...own };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// A Descriptor icon is spliced into the bar verbatim, so only its ROOT type is visible here - an
|
|
98
|
+
// app that swaps one Descriptor icon for another of the same type but a different child count
|
|
99
|
+
// would still trip the bridge's shape guard, with the guard's own message. That is the honest
|
|
100
|
+
// boundary: this signature covers the shape decisions render-tabs.ts itself makes.
|
|
101
|
+
function iconKind(icon: ITabBarIcon | undefined): string {
|
|
102
|
+
if (icon === undefined) return '-';
|
|
103
|
+
return typeof icon === 'string' ? 'glyph' : icon.type;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function barSignature(items: readonly ITabBarItemView[]): string {
|
|
107
|
+
return items
|
|
108
|
+
.map(
|
|
109
|
+
item =>
|
|
110
|
+
`${item.key}:${iconKind(item.icon)}:${item.badge === undefined ? 0 : 1}`,
|
|
111
|
+
)
|
|
112
|
+
.join('|');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function TabImpl(props: ITabProps): JSX.Element {
|
|
116
|
+
// Read BEFORE this Tab provides its own per-screen scope - becomes the `parent` link a nested
|
|
117
|
+
// screen's useNavigation().getParent() walks. undefined when this Tab is the nesting root.
|
|
118
|
+
const parentScope = useNavigationScope();
|
|
119
|
+
|
|
120
|
+
const { screens, collector } = createScreenSignal<
|
|
121
|
+
'tab',
|
|
122
|
+
ITabScreenProps['options']
|
|
123
|
+
>('tab');
|
|
124
|
+
const registry = createMemo(() => toRegistry(screens()));
|
|
125
|
+
|
|
126
|
+
navigatorSequence += 1;
|
|
127
|
+
const routeIdPrefix = `tab-${navigatorSequence}`;
|
|
128
|
+
|
|
129
|
+
// Route keys are derived from the screen NAME, never a counter: a Tab's route list is rebuilt
|
|
130
|
+
// from the registry on every registration change, and a counter would mint a fresh key each time,
|
|
131
|
+
// re-mounting every screen for an unrelated marker edit.
|
|
132
|
+
const routes = createMemo<readonly IRoute<unknown>[]>(() =>
|
|
133
|
+
[...registry().entries()].map(([name, entry]) => ({
|
|
134
|
+
key: `${routeIdPrefix}-${name}`,
|
|
135
|
+
name,
|
|
136
|
+
params: entry.initialParams,
|
|
137
|
+
})),
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
// `null` means nothing has dispatched yet, which is what keeps initialRouteName honored when the
|
|
141
|
+
// markers arrive after the body has already run (screen-registry.ts).
|
|
142
|
+
const [dispatched, setDispatched] = createSignal<ITabRouterState | null>(
|
|
143
|
+
null,
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const state = createMemo<ITabRouterState>(() => {
|
|
147
|
+
const current = routes();
|
|
148
|
+
const previous = dispatched();
|
|
149
|
+
return previous === null
|
|
150
|
+
? createInitialTabState(current, props.initialRouteName)
|
|
151
|
+
: reconcileTabRoutes(previous, current);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
function dispatch(action: ITabRouterAction): void {
|
|
155
|
+
setDispatched(tabRouterReducer(state(), action));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const jumpTo = (name: string, params?: unknown): void =>
|
|
159
|
+
dispatch({ type: 'jumpTo', name, params });
|
|
160
|
+
const setParams = (params: unknown, key: string): void =>
|
|
161
|
+
dispatch({ type: 'setParams', key, params });
|
|
162
|
+
|
|
163
|
+
const handle: ITabNavigatorHandle = { jumpTo, setParams };
|
|
164
|
+
props.ref?.(handle);
|
|
165
|
+
|
|
166
|
+
// One emitter per route.key, created lazily and cached for the navigator's whole lifetime. Emitter
|
|
167
|
+
// IDENTITY (stable, looked up by key) is deliberately decoupled from emit TIMING (deferred below):
|
|
168
|
+
// a scheme that recreated the emitter on each focus change raced the build of the very screen that
|
|
169
|
+
// was supposed to subscribe to it.
|
|
170
|
+
const emitters = new Map<string, INavigationEmitter>();
|
|
171
|
+
function emitterFor(routeKey: string): INavigationEmitter {
|
|
172
|
+
let emitter = emitters.get(routeKey);
|
|
173
|
+
if (emitter === undefined) {
|
|
174
|
+
emitter = createNavigationEmitter();
|
|
175
|
+
emitters.set(routeKey, emitter);
|
|
176
|
+
}
|
|
177
|
+
return emitter;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const focusedKey = createMemo(() => {
|
|
181
|
+
const current = state();
|
|
182
|
+
return current.routes[current.index]?.key;
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Tab paints its own bar in pure JS - there is no native onAppear/onDisappear to hook (unlike
|
|
186
|
+
// Stack's RNSScreen), so focus/blur is synthesized here. The emit is deferred to a microtask,
|
|
187
|
+
// which resolves only after Solid's whole effect flush has drained - including the build of the
|
|
188
|
+
// newly focused screen and the subscriptions createIsFocused/createFocusEffect register inside
|
|
189
|
+
// it. Emitting inline would reach zero subscribers.
|
|
190
|
+
let lastFocusedKey: string | undefined;
|
|
191
|
+
createEffect(() => {
|
|
192
|
+
const nextKey = focusedKey();
|
|
193
|
+
const { blurKey, focusKey } = diffFocusedRoute(lastFocusedKey, nextKey);
|
|
194
|
+
if (blurKey === undefined && focusKey === undefined) return;
|
|
195
|
+
lastFocusedKey = nextKey;
|
|
196
|
+
queueMicrotask(() => {
|
|
197
|
+
if (blurKey !== undefined) {
|
|
198
|
+
dlog(`Tab: route "${blurKey}" blurred at t=${Date.now()}`);
|
|
199
|
+
emitterFor(blurKey).emit(NAVIGATION_EVENT_BLUR);
|
|
200
|
+
}
|
|
201
|
+
if (focusKey !== undefined) {
|
|
202
|
+
dlog(`Tab: route "${focusKey}" focused at t=${Date.now()}`);
|
|
203
|
+
emitterFor(focusKey).emit(NAVIGATION_EVENT_FOCUS);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const items = createMemo<readonly ITabBarItemView[]>(() => {
|
|
209
|
+
const current = state();
|
|
210
|
+
const entries = registry();
|
|
211
|
+
return current.routes.map((route, index) => {
|
|
212
|
+
const entry = entries.get(route.name);
|
|
213
|
+
const focused = isFocusedRoute(index, current.index);
|
|
214
|
+
if (entry === undefined) {
|
|
215
|
+
dlog(`Tab: no screen registered for route name "${route.name}"`);
|
|
216
|
+
return { key: route.key, focused, label: route.name, passthrough: {} };
|
|
217
|
+
}
|
|
218
|
+
const options = resolveTabOptions(
|
|
219
|
+
entry,
|
|
220
|
+
{ route, navigation: handle },
|
|
221
|
+
props.screenOptions,
|
|
222
|
+
);
|
|
223
|
+
return {
|
|
224
|
+
key: route.key,
|
|
225
|
+
focused,
|
|
226
|
+
label: options.tabBarLabel ?? options.title ?? route.name,
|
|
227
|
+
icon: options.tabBarIcon,
|
|
228
|
+
badge: options.tabBarBadge,
|
|
229
|
+
activeTintColor: options.tabBarActiveTintColor,
|
|
230
|
+
inactiveTintColor: options.tabBarInactiveTintColor,
|
|
231
|
+
passthrough: {
|
|
232
|
+
onPress: () => jumpTo(route.name),
|
|
233
|
+
accessibilityRole: 'tab',
|
|
234
|
+
accessibilityState: { selected: focused },
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
const focusedOptions = createMemo<ITabOptions | undefined>(() => {
|
|
241
|
+
const current = state();
|
|
242
|
+
const route = current.routes[current.index];
|
|
243
|
+
const entry = route === undefined ? undefined : registry().get(route.name);
|
|
244
|
+
if (entry === undefined || route === undefined) return props.screenOptions;
|
|
245
|
+
return resolveTabOptions(
|
|
246
|
+
entry,
|
|
247
|
+
{ route, navigation: handle },
|
|
248
|
+
props.screenOptions,
|
|
249
|
+
);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Header note 1: the bar is rebuilt only when its Descriptor SHAPE would change; every tint,
|
|
253
|
+
// label and badge-value change flows through descriptorToSolid's prop diff on the live nodes.
|
|
254
|
+
const barShape = createMemo(() => barSignature(items()));
|
|
255
|
+
const tabBar = createMemo(() => {
|
|
256
|
+
barShape();
|
|
257
|
+
return untrack(() =>
|
|
258
|
+
descriptorToSolid(() =>
|
|
259
|
+
renderTabBar({
|
|
260
|
+
items: items(),
|
|
261
|
+
style: focusedOptions()?.tabBarStyle,
|
|
262
|
+
passthrough: {},
|
|
263
|
+
}),
|
|
264
|
+
),
|
|
265
|
+
);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// Header note 2. Only the focused route's screen is ever mounted (unlike Stack, which keeps every
|
|
269
|
+
// pushed route alive), so the previous screen's whole subtree - and every listener it registered
|
|
270
|
+
// - is torn down by Solid's ordinary disposal when focus moves on.
|
|
271
|
+
const focusedComponent = createMemo(() => {
|
|
272
|
+
const current = state();
|
|
273
|
+
const route = current.routes[current.index];
|
|
274
|
+
return route === undefined
|
|
275
|
+
? undefined
|
|
276
|
+
: registry().get(route.name)?.component;
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
const content = createMemo(() => {
|
|
280
|
+
const routeKey = focusedKey();
|
|
281
|
+
const Component = focusedComponent();
|
|
282
|
+
return untrack(() => {
|
|
283
|
+
if (routeKey === undefined || Component === undefined) return undefined;
|
|
284
|
+
const scope: INavigationScope = () => ({
|
|
285
|
+
route: routeFor(routeKey),
|
|
286
|
+
navigation: handle,
|
|
287
|
+
emitter: emitterFor(routeKey),
|
|
288
|
+
parent: parentScope?.(),
|
|
289
|
+
});
|
|
290
|
+
return createComponent(NavigationScopeProvider, {
|
|
291
|
+
value: scope,
|
|
292
|
+
// No route/navigation props: the screen reads both through the primitives off the scope
|
|
293
|
+
// provided here (useRoute / useTabNavigation).
|
|
294
|
+
get children(): JSX.Element {
|
|
295
|
+
return createComponent(Component, {});
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// Reads the CURRENT object for a key rather than closing over the one that was focused at build
|
|
302
|
+
// time - the accessor is what makes `setParams` reach a mounted screen's useRoute().
|
|
303
|
+
function routeFor(routeKey: string): IRoute<unknown> {
|
|
304
|
+
const found = state().routes.find(route => route.key === routeKey);
|
|
305
|
+
return found ?? { key: routeKey, name: '', params: undefined };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const contentHost = hostElement('symbiote-view', () => ({
|
|
309
|
+
style: TAB_CONTENT_STYLE,
|
|
310
|
+
}));
|
|
311
|
+
insert(contentHost, content);
|
|
312
|
+
|
|
313
|
+
const root = hostElement('symbiote-view', () => ({ style: TAB_ROOT_STYLE }));
|
|
314
|
+
// ONE array insert rather than two: a second `insert` on the same parent would find the first
|
|
315
|
+
// child already there and REPLACE it (solid-js/universal's insertExpression), while an array is
|
|
316
|
+
// reconciled by identity - contentHost keeps its node, only the bar is swapped when it rebuilds.
|
|
317
|
+
insert(root, (): readonly ISymbioteNode[] => [contentHost, tabBar()]);
|
|
318
|
+
|
|
319
|
+
return createComponent(ScreenCollectorProvider, {
|
|
320
|
+
value: collector,
|
|
321
|
+
get children(): JSX.Element {
|
|
322
|
+
return [props.children, root];
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export const Tab = Object.assign(TabImpl, { Screen: TabScreen });
|
|
@@ -31,7 +31,9 @@ function isShimElement(value: unknown): value is ShimElement {
|
|
|
31
31
|
|
|
32
32
|
// The object-bag prop set, the same entry point every adapter's props go through (routeProp).
|
|
33
33
|
// Re-runs on every change of `props`, and the shim's own `p` setter diffs per key from there.
|
|
34
|
-
export function hostProps(
|
|
34
|
+
export function hostProps(
|
|
35
|
+
props: Record<string, unknown>,
|
|
36
|
+
): (node: unknown) => void {
|
|
35
37
|
return node => {
|
|
36
38
|
if (!isShimElement(node)) {
|
|
37
39
|
dlog('navigation: hostProps attached to a non-shim element, ignored');
|
|
@@ -24,7 +24,9 @@ const TEXT_CHILD_MARK = '#';
|
|
|
24
24
|
function shapeOf(children: readonly IDescriptorChild[]): string {
|
|
25
25
|
return children
|
|
26
26
|
.map(child =>
|
|
27
|
-
typeof child === 'string'
|
|
27
|
+
typeof child === 'string'
|
|
28
|
+
? TEXT_CHILD_MARK
|
|
29
|
+
: `${child.type}(${shapeOf(child.children)})`,
|
|
28
30
|
)
|
|
29
31
|
.join(',');
|
|
30
32
|
}
|
|
@@ -24,8 +24,17 @@
|
|
|
24
24
|
<script lang="ts">
|
|
25
25
|
import type { Component } from 'svelte';
|
|
26
26
|
import { onDestroy, tick } from 'svelte';
|
|
27
|
-
import {
|
|
28
|
-
|
|
27
|
+
import {
|
|
28
|
+
AnimatedValue,
|
|
29
|
+
Dimensions,
|
|
30
|
+
PanResponder,
|
|
31
|
+
dlog,
|
|
32
|
+
timing,
|
|
33
|
+
} from '@symbiote-native/engine';
|
|
34
|
+
import type {
|
|
35
|
+
IPanResponderGestureState,
|
|
36
|
+
ISymbioteEvent,
|
|
37
|
+
} from '@symbiote-native/engine';
|
|
29
38
|
// The ONLY import from the adapter's main barrel: `Animated.View` is a compiled `.svelte`
|
|
30
39
|
// component, so unlike PanResponder/Dimensions/AnimatedValue (pure engine re-exports, taken
|
|
31
40
|
// from the engine directly here) it has no `.svelte`-free home to import from.
|
|
@@ -63,7 +72,11 @@
|
|
|
63
72
|
import type { INavigationScopeValue } from '../navigation-context';
|
|
64
73
|
import NavigationScope from '../navigation-scope.svelte';
|
|
65
74
|
import { SCREEN_REGISTRY_HOST_PROPS } from '../registry-host';
|
|
66
|
-
import {
|
|
75
|
+
import {
|
|
76
|
+
setScreenCollector,
|
|
77
|
+
toRegistry,
|
|
78
|
+
withoutScreen,
|
|
79
|
+
} from '../screen-registry';
|
|
67
80
|
import type { IRegisteredScreen } from '../screen-registry';
|
|
68
81
|
import type { IDrawerScreenProps } from '../drawer-screen-props';
|
|
69
82
|
import type { IDrawerProps } from './drawer-props';
|
|
@@ -103,7 +116,9 @@
|
|
|
103
116
|
swipeMinVelocity,
|
|
104
117
|
});
|
|
105
118
|
|
|
106
|
-
let screens = $state.raw<IRegisteredScreen<IDrawerScreenProps['options']>[]>(
|
|
119
|
+
let screens = $state.raw<IRegisteredScreen<IDrawerScreenProps['options']>[]>(
|
|
120
|
+
[],
|
|
121
|
+
);
|
|
107
122
|
setScreenCollector<IDrawerScreenProps['options']>({
|
|
108
123
|
kind: 'drawer',
|
|
109
124
|
register: screen => {
|
|
@@ -123,10 +138,16 @@
|
|
|
123
138
|
|
|
124
139
|
function seedState(): IDrawerRouterState {
|
|
125
140
|
if (seededState !== undefined) return seededState;
|
|
126
|
-
const routes: IRoute<unknown>[] = [...registry.entries()].map(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
141
|
+
const routes: IRoute<unknown>[] = [...registry.entries()].map(
|
|
142
|
+
([name, entry]) => {
|
|
143
|
+
routeSequence += 1;
|
|
144
|
+
return {
|
|
145
|
+
key: `${routeIdPrefix}-${name}-${routeSequence}`,
|
|
146
|
+
name,
|
|
147
|
+
params: entry.initialParams,
|
|
148
|
+
};
|
|
149
|
+
},
|
|
150
|
+
);
|
|
130
151
|
if (routes.length === 0) {
|
|
131
152
|
dlog('Drawer: no <Drawer.Screen> children registered');
|
|
132
153
|
// Deliberately not memoized - markers may still be registering.
|
|
@@ -164,17 +185,23 @@
|
|
|
164
185
|
}
|
|
165
186
|
|
|
166
187
|
export function openDrawer(): void {
|
|
167
|
-
dlog(
|
|
188
|
+
dlog(
|
|
189
|
+
`Drawer: openDrawer() called, isOpen=${state.isOpen} at t=${Date.now()}`,
|
|
190
|
+
);
|
|
168
191
|
animateProgressTo(true);
|
|
169
192
|
dispatch({ type: 'openDrawer' });
|
|
170
193
|
}
|
|
171
194
|
export function closeDrawer(): void {
|
|
172
|
-
dlog(
|
|
195
|
+
dlog(
|
|
196
|
+
`Drawer: closeDrawer() called, isOpen=${state.isOpen} at t=${Date.now()}`,
|
|
197
|
+
);
|
|
173
198
|
animateProgressTo(false);
|
|
174
199
|
dispatch({ type: 'closeDrawer' });
|
|
175
200
|
}
|
|
176
201
|
export function toggleDrawer(): void {
|
|
177
|
-
dlog(
|
|
202
|
+
dlog(
|
|
203
|
+
`Drawer: toggleDrawer() called, isOpen=${state.isOpen} at t=${Date.now()}`,
|
|
204
|
+
);
|
|
178
205
|
animateProgressTo(!state.isOpen);
|
|
179
206
|
dispatch({ type: 'toggleDrawer' });
|
|
180
207
|
}
|
|
@@ -189,7 +216,12 @@
|
|
|
189
216
|
if (wasOpen && !state.isOpen) animateProgressTo(false);
|
|
190
217
|
}
|
|
191
218
|
|
|
192
|
-
const handle: IDrawerNavigatorHandle = {
|
|
219
|
+
const handle: IDrawerNavigatorHandle = {
|
|
220
|
+
openDrawer,
|
|
221
|
+
closeDrawer,
|
|
222
|
+
toggleDrawer,
|
|
223
|
+
jumpTo,
|
|
224
|
+
};
|
|
193
225
|
|
|
194
226
|
// The window width is read fresh AT GESTURE TIME rather than through a reactive
|
|
195
227
|
// window-dimensions rune: it is only ever consumed inside these PanResponder callbacks (never
|
|
@@ -224,8 +256,13 @@
|
|
|
224
256
|
dlog('Drawer: gesture grant');
|
|
225
257
|
dragStartProgress = state.isOpen ? 1 : 0;
|
|
226
258
|
},
|
|
227
|
-
onPanResponderMove: (
|
|
228
|
-
|
|
259
|
+
onPanResponderMove: (
|
|
260
|
+
_event: ISymbioteEvent,
|
|
261
|
+
gestureState: IPanResponderGestureState,
|
|
262
|
+
): void => {
|
|
263
|
+
progress.setValue(
|
|
264
|
+
resolveDragProgress(gestureState, dragStartProgress, options),
|
|
265
|
+
);
|
|
229
266
|
},
|
|
230
267
|
onPanResponderRelease: (
|
|
231
268
|
_event: ISymbioteEvent,
|
|
@@ -288,12 +325,16 @@
|
|
|
288
325
|
|
|
289
326
|
const panelTranslateX = $derived(
|
|
290
327
|
animated
|
|
291
|
-
? progress.interpolate(
|
|
328
|
+
? progress.interpolate(
|
|
329
|
+
resolveDrawerSlotInterpolation(geometry, 'panel').translateX,
|
|
330
|
+
)
|
|
292
331
|
: undefined,
|
|
293
332
|
);
|
|
294
333
|
const contentTranslateX = $derived(
|
|
295
334
|
animated
|
|
296
|
-
? progress.interpolate(
|
|
335
|
+
? progress.interpolate(
|
|
336
|
+
resolveDrawerSlotInterpolation(geometry, 'content').translateX,
|
|
337
|
+
)
|
|
297
338
|
: undefined,
|
|
298
339
|
);
|
|
299
340
|
// The overlay is a full-screen absolutely-positioned sibling BELOW content in paint order (see
|
|
@@ -310,14 +351,22 @@
|
|
|
310
351
|
? undefined
|
|
311
352
|
: {
|
|
312
353
|
opacity: progress.interpolate(overlayInterpolation.opacity),
|
|
313
|
-
transform: [
|
|
354
|
+
transform: [
|
|
355
|
+
{
|
|
356
|
+
translateX: progress.interpolate(overlayInterpolation.translateX),
|
|
357
|
+
},
|
|
358
|
+
],
|
|
314
359
|
},
|
|
315
360
|
);
|
|
316
361
|
const contentStyle = $derived(
|
|
317
|
-
contentTranslateX === undefined
|
|
362
|
+
contentTranslateX === undefined
|
|
363
|
+
? undefined
|
|
364
|
+
: { transform: [{ translateX: contentTranslateX }] },
|
|
318
365
|
);
|
|
319
366
|
const panelStyle = $derived(
|
|
320
|
-
panelTranslateX === undefined
|
|
367
|
+
panelTranslateX === undefined
|
|
368
|
+
? undefined
|
|
369
|
+
: { transform: [{ translateX: panelTranslateX }] },
|
|
321
370
|
);
|
|
322
371
|
|
|
323
372
|
const focusedRoute = $derived(state.routes[state.index]);
|
|
@@ -327,7 +376,9 @@
|
|
|
327
376
|
if (focusedRoute === undefined) return undefined;
|
|
328
377
|
const entry = registry.get(focusedRoute.name);
|
|
329
378
|
if (entry === undefined) {
|
|
330
|
-
dlog(
|
|
379
|
+
dlog(
|
|
380
|
+
`Drawer: no screen registered for route name "${focusedRoute.name}"`,
|
|
381
|
+
);
|
|
331
382
|
return undefined;
|
|
332
383
|
}
|
|
333
384
|
return {
|
|
@@ -345,7 +396,8 @@
|
|
|
345
396
|
entry: IRegisteredScreen<IDrawerScreenProps['options']>,
|
|
346
397
|
route: IRoute<unknown>,
|
|
347
398
|
): IDrawerScreenOptions {
|
|
348
|
-
if (typeof entry.options === 'function')
|
|
399
|
+
if (typeof entry.options === 'function')
|
|
400
|
+
return entry.options({ route, navigation: handle });
|
|
349
401
|
return entry.options ?? {};
|
|
350
402
|
}
|
|
351
403
|
|
|
@@ -354,7 +406,10 @@
|
|
|
354
406
|
for (const route of state.routes) {
|
|
355
407
|
const entry = registry.get(route.name);
|
|
356
408
|
if (entry === undefined) continue;
|
|
357
|
-
map[route.key] = {
|
|
409
|
+
map[route.key] = {
|
|
410
|
+
options: resolveDrawerScreenOptions(entry, route),
|
|
411
|
+
navigation: handle,
|
|
412
|
+
};
|
|
358
413
|
}
|
|
359
414
|
return map;
|
|
360
415
|
});
|
|
@@ -385,11 +440,14 @@
|
|
|
385
440
|
|
|
386
441
|
// renderDrawer emits its slots as root children IN `drawerChildOrder`'s order, so the two zip
|
|
387
442
|
// together - the same index-pairing Vue's own render loop does.
|
|
388
|
-
const slotDescriptors = $derived.by<
|
|
443
|
+
const slotDescriptors = $derived.by<
|
|
444
|
+
Partial<Record<IDrawerSlot, IDescriptor>>
|
|
445
|
+
>(() => {
|
|
389
446
|
const map: Partial<Record<IDrawerSlot, IDescriptor>> = {};
|
|
390
447
|
order.forEach((slot, index) => {
|
|
391
448
|
const descriptor = root.children[index];
|
|
392
|
-
if (typeof descriptor !== 'string' && descriptor !== undefined)
|
|
449
|
+
if (typeof descriptor !== 'string' && descriptor !== undefined)
|
|
450
|
+
map[slot] = descriptor;
|
|
393
451
|
});
|
|
394
452
|
return map;
|
|
395
453
|
});
|
|
@@ -400,7 +458,87 @@
|
|
|
400
458
|
});
|
|
401
459
|
</script>
|
|
402
460
|
|
|
403
|
-
{#snippet screenContent()}
|
|
461
|
+
{#snippet screenContent()}
|
|
462
|
+
{#if focusedScreen !== undefined}
|
|
463
|
+
{@const FocusedComponent = focusedScreen.component}
|
|
464
|
+
<NavigationScope value={focusedScreen.scope}>
|
|
465
|
+
<FocusedComponent />
|
|
466
|
+
</NavigationScope>
|
|
467
|
+
{/if}
|
|
468
|
+
{/snippet}
|
|
469
|
+
|
|
470
|
+
{#snippet contentSlot()}
|
|
471
|
+
{@const descriptor = slotDescriptors.content}
|
|
472
|
+
{#if descriptor !== undefined}
|
|
473
|
+
{#if contentStyle !== undefined}
|
|
474
|
+
<Animated.View
|
|
475
|
+
{...descriptor.props}
|
|
476
|
+
style={[descriptor.props.style, contentStyle]}
|
|
477
|
+
>
|
|
478
|
+
{@render screenContent()}
|
|
479
|
+
</Animated.View>
|
|
480
|
+
{:else}
|
|
481
|
+
<symbiote-view p={descriptor.props}>
|
|
482
|
+
{@render screenContent()}
|
|
483
|
+
</symbiote-view>
|
|
484
|
+
{/if}
|
|
485
|
+
{/if}
|
|
486
|
+
{/snippet}
|
|
487
|
+
|
|
488
|
+
{#snippet overlaySlot()}
|
|
489
|
+
{@const descriptor = slotDescriptors.overlay}
|
|
490
|
+
{#if descriptor !== undefined}
|
|
491
|
+
{#if overlayStyle !== undefined}
|
|
492
|
+
<Animated.View
|
|
493
|
+
{...descriptor.props}
|
|
494
|
+
style={[descriptor.props.style, overlayStyle]}
|
|
495
|
+
/>
|
|
496
|
+
{:else}
|
|
497
|
+
<symbiote-view p={descriptor.props} />
|
|
498
|
+
{/if}
|
|
499
|
+
{/if}
|
|
500
|
+
{/snippet}
|
|
501
|
+
|
|
502
|
+
{#snippet panelSlot()}
|
|
503
|
+
{@const descriptor = slotDescriptors.panel}
|
|
504
|
+
{#if descriptor !== undefined}
|
|
505
|
+
{#if panelStyle !== undefined}
|
|
506
|
+
<Animated.View
|
|
507
|
+
{...descriptor.props}
|
|
508
|
+
style={[descriptor.props.style, panelStyle]}
|
|
509
|
+
>
|
|
510
|
+
{@render drawerContent?.({
|
|
511
|
+
state,
|
|
512
|
+
descriptors,
|
|
513
|
+
navigation: handle,
|
|
514
|
+
})}
|
|
515
|
+
</Animated.View>
|
|
516
|
+
{:else}
|
|
517
|
+
<symbiote-view p={descriptor.props}>
|
|
518
|
+
{@render drawerContent?.({
|
|
519
|
+
state,
|
|
520
|
+
descriptors,
|
|
521
|
+
navigation: handle,
|
|
522
|
+
})}
|
|
523
|
+
</symbiote-view>
|
|
524
|
+
{/if}
|
|
525
|
+
{/if}
|
|
526
|
+
{/snippet}
|
|
527
|
+
|
|
528
|
+
<symbiote-view p={rootProps}>
|
|
529
|
+
<symbiote-text p={SCREEN_REGISTRY_HOST_PROPS}>
|
|
530
|
+
{@render children?.()}
|
|
531
|
+
</symbiote-text>
|
|
532
|
+
{#each order as slot (slot)}
|
|
533
|
+
{#if slot === 'content'}
|
|
534
|
+
{@render contentSlot()}
|
|
535
|
+
{:else if slot === 'overlay'}
|
|
536
|
+
{@render overlaySlot()}
|
|
537
|
+
{:else}
|
|
538
|
+
{@render panelSlot()}
|
|
539
|
+
{/if}
|
|
540
|
+
{/each}
|
|
541
|
+
</symbiote-view>
|
|
404
542
|
|
|
405
543
|
<!-- --- Explicit gap list vs the real react-native-gesture-handler + react-native-reanimated
|
|
406
544
|
@react-navigation/drawer (confirmed against its current docs, mirrored verbatim from the
|
|
@@ -3,14 +3,20 @@
|
|
|
3
3
|
// same tsc-cannot-see-a-`.svelte`-named-export reason screen-props.ts documents.
|
|
4
4
|
|
|
5
5
|
import type { Component } from 'svelte';
|
|
6
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
IDrawerNavigatorHandle,
|
|
8
|
+
IDrawerScreenOptions,
|
|
9
|
+
IRoute,
|
|
10
|
+
} from '../core';
|
|
7
11
|
|
|
8
12
|
export type IDrawerScreenOptionsArgs = {
|
|
9
13
|
route: IRoute<unknown>;
|
|
10
14
|
navigation: IDrawerNavigatorHandle;
|
|
11
15
|
};
|
|
12
16
|
|
|
13
|
-
export type IDrawerScreenOptionsResolver = (
|
|
17
|
+
export type IDrawerScreenOptionsResolver = (
|
|
18
|
+
args: IDrawerScreenOptionsArgs,
|
|
19
|
+
) => IDrawerScreenOptions;
|
|
14
20
|
|
|
15
21
|
export type IDrawerScreenProps = {
|
|
16
22
|
name: string;
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
import { collectScreen } from './screen-registry';
|
|
6
6
|
import type { IDrawerScreenProps } from './drawer-screen-props';
|
|
7
7
|
|
|
8
|
-
let { name, component, options, initialParams }: IDrawerScreenProps =
|
|
8
|
+
let { name, component, options, initialParams }: IDrawerScreenProps =
|
|
9
|
+
$props();
|
|
9
10
|
|
|
10
11
|
collectScreen('drawer', {
|
|
11
12
|
get name(): IDrawerScreenProps['name'] {
|