@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,87 @@
|
|
|
1
|
+
// @symbiote-native/navigation/solid: the Solid native-stack navigator over react-native-screens'
|
|
2
|
+
// native view primitives. Importing this barrel first registers the native views' ViewConfigs
|
|
3
|
+
// (../register, a side-effect import of the codegen specs - never react-native-screens' own React
|
|
4
|
+
// components), then exposes Stack (with Stack.Screen attached) and the navigator handle. Mirrors
|
|
5
|
+
// vue/index.ts's and svelte/index.ts's barrel shape.
|
|
6
|
+
//
|
|
7
|
+
// The lifecycle helpers split by what they DO, not by a blanket prefix: `use*` for the ones that
|
|
8
|
+
// only consume the navigation scope already on the owner chain (useNavigation / useRoute and the
|
|
9
|
+
// three narrowing variants - Solid's own `useContext` sense), `create*` for the ones that own a
|
|
10
|
+
// signal, an effect or a subscription (createIsFocused / createFocusEffect / createNavigationState
|
|
11
|
+
// / createLinkingIntegration). Same rule the adapter's `createColorScheme` follows; a blanket `use`
|
|
12
|
+
// would read as a foreign ecosystem's convention (<adapter_src_follows_framework_idioms>).
|
|
13
|
+
|
|
14
|
+
import '../register';
|
|
15
|
+
|
|
16
|
+
export { Stack } from './stack';
|
|
17
|
+
export type { INavigatorHandle, IStackProps } from './stack';
|
|
18
|
+
export { Screen, TabScreen, DrawerScreen } from './screen';
|
|
19
|
+
export type {
|
|
20
|
+
IScreenOptionsArgs,
|
|
21
|
+
IScreenOptionsResolver,
|
|
22
|
+
IScreenProps,
|
|
23
|
+
ISolidScreenOptions,
|
|
24
|
+
ISolidSearchBarOptions,
|
|
25
|
+
} from './screen-props';
|
|
26
|
+
export type {
|
|
27
|
+
IScreenOptions,
|
|
28
|
+
IStackAnimation,
|
|
29
|
+
IStackPresentation,
|
|
30
|
+
} from '../core';
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
useNavigation,
|
|
34
|
+
useStackNavigation,
|
|
35
|
+
useTabNavigation,
|
|
36
|
+
useDrawerNavigation,
|
|
37
|
+
useRoute,
|
|
38
|
+
createIsFocused,
|
|
39
|
+
createFocusEffect,
|
|
40
|
+
createNavigationState,
|
|
41
|
+
} from './primitives';
|
|
42
|
+
export type {
|
|
43
|
+
INavigationHandle,
|
|
44
|
+
IStackNavigationHandle,
|
|
45
|
+
ITabNavigationHandle,
|
|
46
|
+
IDrawerNavigationHandle,
|
|
47
|
+
} from './primitives';
|
|
48
|
+
export type {
|
|
49
|
+
INavigationScopeValue,
|
|
50
|
+
IAnyNavigatorHandle,
|
|
51
|
+
} from './navigation-context';
|
|
52
|
+
|
|
53
|
+
export { createLinkingIntegration } from './linking';
|
|
54
|
+
export type { ILinkingConfig, IScreenLinkingConfig } from '../core';
|
|
55
|
+
|
|
56
|
+
// Tab: the bottom-tabs navigator, a PURE-JS UI (no react-native-screens views involved, so no extra
|
|
57
|
+
// ViewConfig registration is needed beyond the ../register import above).
|
|
58
|
+
export { Tab } from './tabs';
|
|
59
|
+
export type { ITabNavigatorHandle, ITabProps } from './tabs';
|
|
60
|
+
export type {
|
|
61
|
+
ITabScreenOptionsArgs,
|
|
62
|
+
ITabScreenOptionsResolver,
|
|
63
|
+
ITabScreenProps,
|
|
64
|
+
} from './tab-screen-props';
|
|
65
|
+
export type { ITabOptions, ITabBarIcon } from '../core';
|
|
66
|
+
|
|
67
|
+
// Drawer: the swipeable drawer navigator, a PURE-JS UI (PanResponder + Animated). See
|
|
68
|
+
// drawer/index.ts's header for the feasibility note re: react-native-gesture-handler /
|
|
69
|
+
// react-native-reanimated parity gaps.
|
|
70
|
+
export { Drawer } from './drawer';
|
|
71
|
+
export type {
|
|
72
|
+
IDrawerContentSlotProps,
|
|
73
|
+
IDrawerNavigatorHandle,
|
|
74
|
+
IDrawerProps,
|
|
75
|
+
IDrawerDescriptorMap,
|
|
76
|
+
} from './drawer';
|
|
77
|
+
export type {
|
|
78
|
+
IDrawerScreenOptionsArgs,
|
|
79
|
+
IDrawerScreenOptionsResolver,
|
|
80
|
+
IDrawerScreenProps,
|
|
81
|
+
} from './drawer-screen-props';
|
|
82
|
+
export type {
|
|
83
|
+
IDrawerOptions,
|
|
84
|
+
IDrawerScreenOptions,
|
|
85
|
+
IDrawerType,
|
|
86
|
+
IDrawerPosition,
|
|
87
|
+
} from '../core';
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// createLinkingIntegration: wires the framework-agnostic linking config (../core's
|
|
2
|
+
// resolveRouteFromUrl) onto an existing Stack's INavigatorHandle. Deliberately NOT called from
|
|
3
|
+
// inside ./stack - app code owns the wiring, the same top-level-prop shape @react-navigation keeps
|
|
4
|
+
// `linking` in on NavigationContainer rather than baking it into the router itself.
|
|
5
|
+
//
|
|
6
|
+
// `create*`, not `use*`: it owns a Linking subscription and its teardown. (Vue and Svelte spell it
|
|
7
|
+
// `useLinkingIntegration` because their ecosystems put every lifecycle helper behind `use`; Solid
|
|
8
|
+
// reserves that prefix for consuming something that already exists -
|
|
9
|
+
// <adapter_src_follows_framework_idioms>.)
|
|
10
|
+
//
|
|
11
|
+
// Takes an ACCESSOR of the handle, not the handle: `<Stack ref={setHandle}>` resolves after the
|
|
12
|
+
// navigator has been created, so an eagerly-passed value would be `null` at the call site. Reading
|
|
13
|
+
// it lazily inside onMount gives the same "already populated" guarantee React's layout-effect
|
|
14
|
+
// ordering does - a Solid `ref` on a component is called during that component's own creation,
|
|
15
|
+
// which is strictly before any effect runs.
|
|
16
|
+
|
|
17
|
+
import { onCleanup, onMount } from 'solid-js';
|
|
18
|
+
import type { Accessor } from 'solid-js';
|
|
19
|
+
import { Linking, dlog } from '@symbiote-native/engine';
|
|
20
|
+
import type { IEventSubscription, IUrlEvent } from '@symbiote-native/engine';
|
|
21
|
+
import { resolveRouteFromUrl } from '../core';
|
|
22
|
+
import type { ILinkingConfig, INavigatorHandle } from '../core';
|
|
23
|
+
|
|
24
|
+
export function createLinkingIntegration(
|
|
25
|
+
config: ILinkingConfig,
|
|
26
|
+
navigatorHandle: Accessor<INavigatorHandle | null>,
|
|
27
|
+
): void {
|
|
28
|
+
let cancelled = false;
|
|
29
|
+
let subscription: IEventSubscription | undefined;
|
|
30
|
+
|
|
31
|
+
function applyRoute(
|
|
32
|
+
url: string,
|
|
33
|
+
dispatch: (name: string, params?: unknown) => void,
|
|
34
|
+
): void {
|
|
35
|
+
const route = resolveRouteFromUrl(config, url);
|
|
36
|
+
if (route === null) {
|
|
37
|
+
dlog(`createLinkingIntegration: no route resolved for "${url}"`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
dispatch(route.name, route.params);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
onMount(() => {
|
|
44
|
+
const handle = navigatorHandle();
|
|
45
|
+
if (handle === null) {
|
|
46
|
+
dlog(
|
|
47
|
+
'createLinkingIntegration: navigatorHandle is not yet available at mount time',
|
|
48
|
+
);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
Linking.getInitialURL()
|
|
53
|
+
.then(url => {
|
|
54
|
+
if (cancelled || url === null) return;
|
|
55
|
+
dlog(`createLinkingIntegration: initial URL "${url}"`);
|
|
56
|
+
applyRoute(url, handle.replace);
|
|
57
|
+
})
|
|
58
|
+
.catch((error: unknown) => {
|
|
59
|
+
dlog(
|
|
60
|
+
`createLinkingIntegration: getInitialURL failed: ${String(error)}`,
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
subscription = Linking.addEventListener('url', (event: IUrlEvent) => {
|
|
65
|
+
dlog(`createLinkingIntegration: url event "${event.url}"`);
|
|
66
|
+
applyRoute(event.url, handle.push);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
onCleanup(() => {
|
|
71
|
+
cancelled = true;
|
|
72
|
+
subscription?.remove();
|
|
73
|
+
});
|
|
74
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// The lifecycle half of the framework-agnostic emitter (../core/navigation-events). Solid's twin of
|
|
2
|
+
// React's Context / Vue's provide-inject / Svelte's setContext is `createContext` + a Provider over
|
|
3
|
+
// one shared key, resolved along the OWNER chain - so a screen's subtree reads its own route,
|
|
4
|
+
// navigator handle and per-route emitter without prop-drilling.
|
|
5
|
+
//
|
|
6
|
+
// THE VALUE IS AN ACCESSOR, never a route object. A Solid component body runs once and a context
|
|
7
|
+
// value is captured once, so handing over `{ route, ... }` directly would freeze every consumer at
|
|
8
|
+
// the route as it looked when the screen mounted - `setParams` produces a NEW route object under
|
|
9
|
+
// the same key and would never reach `useRoute()`. This is the same accessors-not-values rule
|
|
10
|
+
// descriptorToSolid is built on, one layer up (.claude/rules/solid-descriptor-bridge.md).
|
|
11
|
+
//
|
|
12
|
+
// Each navigator mounts ONE Provider per live route (keyed by route.key, exactly like React's
|
|
13
|
+
// per-route `<NavigationContext.Provider>`), so sibling screens never see each other's scope -
|
|
14
|
+
// an inherent property of any hierarchical dependency-injection scheme, not a React artifact.
|
|
15
|
+
//
|
|
16
|
+
// `parent` threads the ambient value a navigator read on ITS OWN mount (undefined at the root) into
|
|
17
|
+
// the value it provides to its own screens, forming a linked list - this is what lets a screen
|
|
18
|
+
// nested inside e.g. a Stack-screen-renders-a-Tab composition reach the enclosing Stack via
|
|
19
|
+
// useNavigation().getParent().
|
|
20
|
+
|
|
21
|
+
import { createContext, useContext } from 'solid-js';
|
|
22
|
+
import type { Accessor } from 'solid-js';
|
|
23
|
+
import type { IAnyNavigatorHandle, INavigationEmitter, IRoute } from '../core';
|
|
24
|
+
export type { IAnyNavigatorHandle };
|
|
25
|
+
|
|
26
|
+
export type INavigationScopeValue = {
|
|
27
|
+
route: IRoute<unknown>;
|
|
28
|
+
navigation: IAnyNavigatorHandle;
|
|
29
|
+
emitter: INavigationEmitter;
|
|
30
|
+
parent?: INavigationScopeValue;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type INavigationScope = Accessor<INavigationScopeValue>;
|
|
34
|
+
|
|
35
|
+
const NavigationScopeContext = createContext<INavigationScope>();
|
|
36
|
+
|
|
37
|
+
export const NavigationScopeProvider = NavigationScopeContext.Provider;
|
|
38
|
+
|
|
39
|
+
// undefined simply means "no ancestor scope", which is a legitimate state (the nesting root a
|
|
40
|
+
// navigator itself reads on its own mount) rather than an error - callers that require one go
|
|
41
|
+
// through requireNavigationScope below.
|
|
42
|
+
export function useNavigationScope(): INavigationScope | undefined {
|
|
43
|
+
return useContext(NavigationScopeContext);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// The "throw if missing" half every primitive in ./primitives needs - they all read the scope and
|
|
47
|
+
// threw the same shaped error, differing only in which function name the message names.
|
|
48
|
+
export function requireNavigationScope(functionName: string): INavigationScope {
|
|
49
|
+
const scope = useNavigationScope();
|
|
50
|
+
if (scope === undefined) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`${functionName} must be used within a screen rendered by <Stack>, <Tab>, or <Drawer>`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return scope;
|
|
56
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Mirrors @react-navigation's useFocusEffect: run `effect` when the screen gains focus, run its
|
|
2
|
+
// returned cleanup when it loses focus (and once more on teardown).
|
|
3
|
+
//
|
|
4
|
+
// `create*`: it owns two subscriptions and the effect's cleanup slot.
|
|
5
|
+
//
|
|
6
|
+
// React's version requires memoizing `effect` (useCallback), since a new identity re-subscribes it
|
|
7
|
+
// like any other useEffect dependency. That does not apply here: a Solid body runs once, so
|
|
8
|
+
// `effect` is read once and closed over directly - there is no dependency array to go stale.
|
|
9
|
+
|
|
10
|
+
import { onCleanup } from 'solid-js';
|
|
11
|
+
import { NAVIGATION_EVENT_BLUR, NAVIGATION_EVENT_FOCUS } from '../../core';
|
|
12
|
+
import { requireNavigationScope } from '../navigation-context';
|
|
13
|
+
|
|
14
|
+
export function createFocusEffect(effect: () => (() => void) | void): void {
|
|
15
|
+
const scope = requireNavigationScope('createFocusEffect');
|
|
16
|
+
|
|
17
|
+
let cleanup: (() => void) | void;
|
|
18
|
+
const runEffect = (): void => {
|
|
19
|
+
cleanup = effect();
|
|
20
|
+
};
|
|
21
|
+
const runCleanup = (): void => {
|
|
22
|
+
cleanup?.();
|
|
23
|
+
cleanup = undefined;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const { emitter } = scope();
|
|
27
|
+
const unsubscribeFocus = emitter.addListener(
|
|
28
|
+
NAVIGATION_EVENT_FOCUS,
|
|
29
|
+
runEffect,
|
|
30
|
+
);
|
|
31
|
+
const unsubscribeBlur = emitter.addListener(
|
|
32
|
+
NAVIGATION_EVENT_BLUR,
|
|
33
|
+
runCleanup,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
onCleanup(() => {
|
|
37
|
+
unsubscribeFocus();
|
|
38
|
+
unsubscribeBlur();
|
|
39
|
+
runCleanup();
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Mirrors @react-navigation's useIsFocused.
|
|
2
|
+
//
|
|
3
|
+
// `create*`, not `use*`: this OWNS a signal and two emitter subscriptions. Solid reserves `use*`
|
|
4
|
+
// for consuming something that already exists, so a primitive that creates its own state and
|
|
5
|
+
// lifecycle is `createX` (<adapter_src_follows_framework_idioms>; the same reason the adapter
|
|
6
|
+
// spells its colour-scheme primitive `createColorScheme`).
|
|
7
|
+
//
|
|
8
|
+
// Starts `false` rather than guessing from stack position: the route's emitter only fires 'focus'
|
|
9
|
+
// once RNSScreen's native onAppear lands (../stack) or the screen mounts focused (../tabs,
|
|
10
|
+
// ../drawer) - a screen genuinely isn't focused at the instant it mounts, the same async gap real
|
|
11
|
+
// native transitions have.
|
|
12
|
+
//
|
|
13
|
+
// Subscribing SYNCHRONOUSLY in the body rather than from onMount, unlike React/Vue/Svelte: those
|
|
14
|
+
// have to defer past render, and Solid does not. It also matters here - the navigators emit the
|
|
15
|
+
// first 'focus' from a queued microtask after the screen subtree is built, and an onMount-based
|
|
16
|
+
// subscription would still be in the same effect queue, i.e. a race this simply does not enter.
|
|
17
|
+
|
|
18
|
+
import { createSignal, onCleanup } from 'solid-js';
|
|
19
|
+
import type { Accessor } from 'solid-js';
|
|
20
|
+
import { NAVIGATION_EVENT_BLUR, NAVIGATION_EVENT_FOCUS } from '../../core';
|
|
21
|
+
import { requireNavigationScope } from '../navigation-context';
|
|
22
|
+
|
|
23
|
+
export function createIsFocused(): Accessor<boolean> {
|
|
24
|
+
const scope = requireNavigationScope('createIsFocused');
|
|
25
|
+
const [isFocused, setIsFocused] = createSignal(false);
|
|
26
|
+
|
|
27
|
+
const { emitter } = scope();
|
|
28
|
+
const unsubscribeFocus = emitter.addListener(NAVIGATION_EVENT_FOCUS, () =>
|
|
29
|
+
setIsFocused(true),
|
|
30
|
+
);
|
|
31
|
+
const unsubscribeBlur = emitter.addListener(NAVIGATION_EVENT_BLUR, () =>
|
|
32
|
+
setIsFocused(false),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
onCleanup(() => {
|
|
36
|
+
unsubscribeFocus();
|
|
37
|
+
unsubscribeBlur();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return isFocused;
|
|
41
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Mirrors @react-navigation's useNavigationState. The reducer/dispatch machinery lives in
|
|
2
|
+
// core/navigator-state.ts (tab-router-state.ts / drawer-router-state.ts for the other two
|
|
3
|
+
// navigators) - this only wires the subscription to the `state` broadcast each navigator re-emits
|
|
4
|
+
// after its router state changes.
|
|
5
|
+
//
|
|
6
|
+
// `create*`: it owns a signal and a subscription.
|
|
7
|
+
//
|
|
8
|
+
// Seeded from a single-route snapshot ({ routes: [route] }) rather than left undefined: the real
|
|
9
|
+
// broadcast lands after the screen is built, so a selector reading e.g. `state.routes.at(-1)?.name`
|
|
10
|
+
// still resolves correctly on first paint for the common single-route case - the same async gap
|
|
11
|
+
// createIsFocused documents.
|
|
12
|
+
|
|
13
|
+
import { createSignal, onCleanup } from 'solid-js';
|
|
14
|
+
import type { Accessor } from 'solid-js';
|
|
15
|
+
import { NAVIGATION_EVENT_STATE, isRecord } from '../../core';
|
|
16
|
+
import type { INavigatorState } from '../../core';
|
|
17
|
+
import { requireNavigationScope } from '../navigation-context';
|
|
18
|
+
|
|
19
|
+
function isNavigatorState(value: unknown): value is INavigatorState {
|
|
20
|
+
return isRecord(value) && Array.isArray(value.routes);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function createNavigationState<TResult>(
|
|
24
|
+
selector: (state: INavigatorState) => TResult,
|
|
25
|
+
): Accessor<TResult> {
|
|
26
|
+
const scope = requireNavigationScope('createNavigationState');
|
|
27
|
+
const [result, setResult] = createSignal<TResult>(
|
|
28
|
+
selector({ routes: [scope().route] }),
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const unsubscribe = scope().emitter.addListener(
|
|
32
|
+
NAVIGATION_EVENT_STATE,
|
|
33
|
+
(state: unknown) => {
|
|
34
|
+
if (!isNavigatorState(state)) return;
|
|
35
|
+
// The updater form, not a bare value: a selector legitimately returns a FUNCTION for a
|
|
36
|
+
// navigation-driven callback, and `setResult(fn)` would otherwise be read as an updater.
|
|
37
|
+
setResult(() => selector(state));
|
|
38
|
+
},
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
onCleanup(unsubscribe);
|
|
42
|
+
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { useNavigation } from './use-navigation';
|
|
2
|
+
export type { INavigationHandle } from './use-navigation';
|
|
3
|
+
export { useStackNavigation } from './use-stack-navigation';
|
|
4
|
+
export type { IStackNavigationHandle } from './use-stack-navigation';
|
|
5
|
+
export { useTabNavigation } from './use-tab-navigation';
|
|
6
|
+
export type { ITabNavigationHandle } from './use-tab-navigation';
|
|
7
|
+
export { useDrawerNavigation } from './use-drawer-navigation';
|
|
8
|
+
export type { IDrawerNavigationHandle } from './use-drawer-navigation';
|
|
9
|
+
export { useRoute } from './use-route';
|
|
10
|
+
export { createIsFocused } from './create-is-focused';
|
|
11
|
+
export { createFocusEffect } from './create-focus-effect';
|
|
12
|
+
export { createNavigationState } from './create-navigation-state';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Twin of use-stack-navigation.ts, narrowing to a Drawer handle instead.
|
|
2
|
+
|
|
3
|
+
import { createMemo } from 'solid-js';
|
|
4
|
+
import type { Accessor } from 'solid-js';
|
|
5
|
+
import { isDrawerNavigatorHandle } from '../../core';
|
|
6
|
+
import type { IDrawerNavigatorHandle } from '../../core';
|
|
7
|
+
import { useNavigation } from './use-navigation';
|
|
8
|
+
import type { INavigationHandle } from './use-navigation';
|
|
9
|
+
|
|
10
|
+
export type IDrawerNavigationHandle = IDrawerNavigatorHandle &
|
|
11
|
+
Pick<INavigationHandle, 'addListener' | 'getParent'>;
|
|
12
|
+
|
|
13
|
+
export function useDrawerNavigation(): Accessor<IDrawerNavigationHandle> {
|
|
14
|
+
const navigation = useNavigation();
|
|
15
|
+
return createMemo(() => {
|
|
16
|
+
const value = navigation();
|
|
17
|
+
if (!isDrawerNavigatorHandle(value)) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
'useDrawerNavigation() was called from a component whose nearest navigator is not a Drawer. ' +
|
|
20
|
+
'Use useNavigation() instead if this component can render under more than one navigator kind.',
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
return value;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Mirrors @react-navigation's `navigation.addListener('focus', cb)` surface. All pub/sub logic
|
|
2
|
+
// lives in ../../core/navigation-events; this only reads the injected scope and binds identity.
|
|
3
|
+
//
|
|
4
|
+
// `use*`, not `create*`: it CONSUMES an existing context and owns no state or subscription of its
|
|
5
|
+
// own - the same distinction Solid itself draws between `useContext`/`useTransition` and
|
|
6
|
+
// `createSignal`/`createEffect` (adapters/solid/src/primitives/create-color-scheme.ts's header).
|
|
7
|
+
//
|
|
8
|
+
// Returns an ACCESSOR for the reason every value crossing a boundary in this adapter does: a Solid
|
|
9
|
+
// component body runs once, so a returned object would freeze at the scope the screen mounted with
|
|
10
|
+
// and `getParent()` would keep answering with a stale ancestor after a nested navigator re-scoped.
|
|
11
|
+
|
|
12
|
+
import { createMemo } from 'solid-js';
|
|
13
|
+
import type { Accessor } from 'solid-js';
|
|
14
|
+
import type {
|
|
15
|
+
INavigationEventListener,
|
|
16
|
+
INavigationEventName,
|
|
17
|
+
} from '../../core';
|
|
18
|
+
import { requireNavigationScope } from '../navigation-context';
|
|
19
|
+
import type { IAnyNavigatorHandle } from '../navigation-context';
|
|
20
|
+
|
|
21
|
+
export type INavigationHandle = IAnyNavigatorHandle & {
|
|
22
|
+
addListener: (
|
|
23
|
+
event: INavigationEventName,
|
|
24
|
+
listener: INavigationEventListener,
|
|
25
|
+
) => () => void;
|
|
26
|
+
// Walks exactly ONE hop up the scope's `parent` chain to the enclosing navigator's handle - e.g.
|
|
27
|
+
// a Tab screen nested inside a Stack screen calling getParent() to push a new Stack route.
|
|
28
|
+
// Callers narrow the union themselves ('push' in parent, etc.). Deliberately NOT
|
|
29
|
+
// react-navigation's getParent(id) or a target-based dispatch by navigator name - plain
|
|
30
|
+
// immediate-parent walking is v1 scope on every adapter.
|
|
31
|
+
getParent: () => IAnyNavigatorHandle | undefined;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function useNavigation(): Accessor<INavigationHandle> {
|
|
35
|
+
const scope = requireNavigationScope('useNavigation');
|
|
36
|
+
return createMemo(() => {
|
|
37
|
+
const { navigation, emitter, parent } = scope();
|
|
38
|
+
return {
|
|
39
|
+
...navigation,
|
|
40
|
+
addListener: emitter.addListener,
|
|
41
|
+
getParent: () => parent?.navigation,
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Mirrors @react-navigation's useRoute. Zero logic of its own - the route object is built by the
|
|
2
|
+
// navigators' route derivation from the shared core reducer's state.
|
|
3
|
+
//
|
|
4
|
+
// THE ACCESSOR IS THE WHOLE POINT HERE. React re-runs a screen's body on every navigation, so its
|
|
5
|
+
// useRoute() can hand back a plain object; Solid runs the body ONCE. A `useRoute()` returning
|
|
6
|
+
// `IRoute` would therefore paint the params the screen was pushed with and never update again -
|
|
7
|
+
// `navigation.setParams({...})` produces a new route object under the SAME key, and nothing would
|
|
8
|
+
// re-read it. Pinned by "setParams reaches a mounted screen" in ../solid.test.tsx.
|
|
9
|
+
|
|
10
|
+
import { createMemo } from 'solid-js';
|
|
11
|
+
import type { Accessor } from 'solid-js';
|
|
12
|
+
import type { IRoute } from '../../core';
|
|
13
|
+
import { requireNavigationScope } from '../navigation-context';
|
|
14
|
+
|
|
15
|
+
export function useRoute(): Accessor<IRoute<unknown>> {
|
|
16
|
+
const scope = requireNavigationScope('useRoute');
|
|
17
|
+
return createMemo(() => scope().route);
|
|
18
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// useNavigation() returns a union (IAnyNavigatorHandle), since it does not know which navigator
|
|
2
|
+
// mounted the calling component. When a component genuinely only ever renders under a Stack, this
|
|
3
|
+
// narrows that union ONCE, here, so call sites never write their own `'push' in navigation` check.
|
|
4
|
+
//
|
|
5
|
+
// `use*`: a pure consumer of useNavigation()'s accessor, no state of its own.
|
|
6
|
+
|
|
7
|
+
import { createMemo } from 'solid-js';
|
|
8
|
+
import type { Accessor } from 'solid-js';
|
|
9
|
+
import { isStackNavigatorHandle } from '../../core';
|
|
10
|
+
import type { INavigatorHandle } from '../../core';
|
|
11
|
+
import { useNavigation } from './use-navigation';
|
|
12
|
+
import type { INavigationHandle } from './use-navigation';
|
|
13
|
+
|
|
14
|
+
export type IStackNavigationHandle = INavigatorHandle &
|
|
15
|
+
Pick<INavigationHandle, 'addListener' | 'getParent'>;
|
|
16
|
+
|
|
17
|
+
export function useStackNavigation(): Accessor<IStackNavigationHandle> {
|
|
18
|
+
const navigation = useNavigation();
|
|
19
|
+
return createMemo(() => {
|
|
20
|
+
const value = navigation();
|
|
21
|
+
if (!isStackNavigatorHandle(value)) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
'useStackNavigation() was called from a component whose nearest navigator is not a Stack. ' +
|
|
24
|
+
'Use useNavigation() instead if this component can render under more than one navigator kind.',
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Twin of use-stack-navigation.ts, narrowing to a Tab handle instead.
|
|
2
|
+
|
|
3
|
+
import { createMemo } from 'solid-js';
|
|
4
|
+
import type { Accessor } from 'solid-js';
|
|
5
|
+
import { isTabNavigatorHandle } from '../../core';
|
|
6
|
+
import type { ITabNavigatorHandle } from '../../core';
|
|
7
|
+
import { useNavigation } from './use-navigation';
|
|
8
|
+
import type { INavigationHandle } from './use-navigation';
|
|
9
|
+
|
|
10
|
+
export type ITabNavigationHandle = ITabNavigatorHandle &
|
|
11
|
+
Pick<INavigationHandle, 'addListener' | 'getParent'>;
|
|
12
|
+
|
|
13
|
+
export function useTabNavigation(): Accessor<ITabNavigationHandle> {
|
|
14
|
+
const navigation = useNavigation();
|
|
15
|
+
return createMemo(() => {
|
|
16
|
+
const value = navigation();
|
|
17
|
+
if (!isTabNavigatorHandle(value)) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
'useTabNavigation() was called from a component whose nearest navigator is not a Tab. ' +
|
|
20
|
+
'Use useNavigation() instead if this component can render under more than one navigator kind.',
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
return value;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Stack.Screen's public prop types, split out of the marker component itself so screen-registry.ts
|
|
2
|
+
// can name `IScreenProps['options']` without importing a component (which would make the two
|
|
3
|
+
// modules circular).
|
|
4
|
+
//
|
|
5
|
+
// Declared here rather than re-exported from another adapter: `component` is a Solid `Component`
|
|
6
|
+
// and the search-bar `ref` is a Solid callback ref, both framework values, which is exactly the
|
|
7
|
+
// test <prop_types_split_agnostic_vs_per_adapter> applies. The agnostic FIELD BASE (IScreenOptions,
|
|
8
|
+
// ISearchBarOptions, IRoute, INavigatorHandle) is shared from ../core verbatim.
|
|
9
|
+
|
|
10
|
+
import type { Component } from 'solid-js';
|
|
11
|
+
import type {
|
|
12
|
+
INavigatorHandle,
|
|
13
|
+
IRoute,
|
|
14
|
+
IScreenOptions,
|
|
15
|
+
ISearchBarCommands,
|
|
16
|
+
ISearchBarOptions,
|
|
17
|
+
} from '../core';
|
|
18
|
+
|
|
19
|
+
// The imperative search-bar handle (focus/blur/clearText/setText/cancelSearch/
|
|
20
|
+
// toggleCancelButton). React hands it back through a `RefObject`, Vue through a `Ref`; Solid's own
|
|
21
|
+
// spelling of "give me the thing when it exists" is a CALLBACK, which is also what
|
|
22
|
+
// `ref={setCommands}` compiles to. `null` arrives when the host element goes away.
|
|
23
|
+
export type ISolidSearchBarOptions = ISearchBarOptions & {
|
|
24
|
+
ref?: (commands: ISearchBarCommands | null) => void;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type ISolidScreenOptions = Omit<
|
|
28
|
+
IScreenOptions,
|
|
29
|
+
'headerSearchBarOptions'
|
|
30
|
+
> & {
|
|
31
|
+
headerSearchBarOptions?: ISolidSearchBarOptions;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// The options resolver runs OUTSIDE any component scope (during the options fold), so it still
|
|
35
|
+
// receives the route + navigator handle explicitly - screens read those through the primitives in
|
|
36
|
+
// ./primitives, but a resolver has no owner to read a context from.
|
|
37
|
+
export type IScreenOptionsArgs = {
|
|
38
|
+
route: IRoute<unknown>;
|
|
39
|
+
navigation: INavigatorHandle;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type IScreenOptionsResolver = (
|
|
43
|
+
args: IScreenOptionsArgs,
|
|
44
|
+
) => ISolidScreenOptions;
|
|
45
|
+
|
|
46
|
+
export type IScreenProps = {
|
|
47
|
+
name: string;
|
|
48
|
+
component: Component;
|
|
49
|
+
options?: ISolidScreenOptions | IScreenOptionsResolver;
|
|
50
|
+
initialParams?: unknown;
|
|
51
|
+
};
|