@symbiote-native/navigation 2.0.3 → 2.2.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 +42 -12
- package/build/angular/drawer/index.js +29 -13
- package/build/angular/index.d.ts +4 -4
- 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.d.ts +2 -0
- package/build/angular/stack/index.js +43 -9
- package/build/angular/tabs/index.d.ts +1 -1
- package/build/angular/tabs/index.js +40 -13
- 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 +6 -5
- package/build/core/index.js +7 -3
- 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/stack-router-state/index.d.ts +2 -0
- package/build/core/stack-router-state/index.js +44 -0
- package/build/core/state-persistence/index.js +6 -2
- package/build/core/tab-router-state/index.d.ts +1 -0
- package/build/core/tab-router-state/index.js +27 -2
- package/build/react/drawer/index.js +59 -26
- package/build/react/index.d.ts +7 -7
- package/build/react/index.js +2 -2
- package/build/react/stack/index.js +47 -10
- package/build/react/tabs/index.js +36 -12
- 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/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 +39 -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 +559 -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 +25 -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 +27 -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 +26 -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 +261 -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 +205 -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 +269 -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/composables/use-navigation.js +5 -1
- package/build/vue/drawer/index.js +36 -13
- package/build/vue/index.d.ts +3 -3
- package/build/vue/navigation-context.js +1 -1
- package/build/vue/stack/index.js +43 -18
- package/build/vue/tabs/index.js +65 -39
- package/build-ngc/angular/drawer/index.js +40 -18
- package/build-ngc/angular/drawer/index.js.map +1 -1
- package/build-ngc/angular/drawer-screen.directive.js +3 -3
- 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 +3 -3
- package/build-ngc/angular/navigation-context.service.js.map +1 -1
- package/build-ngc/angular/navigation-scope.directive.js +4 -4
- package/build-ngc/angular/navigation-scope.directive.js.map +1 -1
- package/build-ngc/angular/screen.directive.js +3 -3
- package/build-ngc/angular/screen.directive.js.map +1 -1
- package/build-ngc/angular/search-bar-ref.directive.js +4 -4
- package/build-ngc/angular/search-bar-ref.directive.js.map +1 -1
- package/build-ngc/angular/stack/index.d.ts +2 -0
- package/build-ngc/angular/stack/index.js +52 -14
- package/build-ngc/angular/stack/index.js.map +1 -1
- package/build-ngc/angular/tab-screen.directive.js +3 -3
- package/build-ngc/angular/tab-screen.directive.js.map +1 -1
- package/build-ngc/angular/tabs/index.d.ts +1 -1
- package/build-ngc/angular/tabs/index.js +43 -16
- 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 +6 -5
- package/build-ngc/core/index.js +7 -3
- 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.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/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.d.ts +1 -0
- package/build-ngc/core/tab-router-state/index.js +27 -2
- package/build-ngc/core/tab-router-state/index.js.map +1 -1
- package/package.json +44 -16
- package/src/angular/drawer/index.ts +75 -29
- 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 +91 -24
- package/src/angular/tab-screen.directive.ts +3 -1
- package/src/angular/tabs/index.ts +54 -16
- 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 +29 -5
- 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 +52 -0
- package/src/core/state-persistence/index.ts +13 -3
- package/src/core/tab-router-state/index.ts +35 -3
- package/src/react/drawer/index.ts +409 -330
- 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 +297 -220
- package/src/react/tab-screen.ts +3 -1
- package/src/react/tabs/index.ts +202 -139
- 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 +70 -0
- package/src/svelte/descriptor-subtree.ts +51 -0
- package/src/svelte/drawer/drawer-props.ts +28 -0
- package/src/svelte/drawer/index.svelte +559 -0
- package/src/svelte/drawer/index.ts +10 -0
- package/src/svelte/drawer-screen-props.ts +26 -0
- package/src/svelte/drawer-screen.svelte +25 -0
- package/src/svelte/fabric-tree.test-helper.ts +95 -0
- package/src/svelte/index.ts +83 -0
- package/src/svelte/linking.svelte.ts +72 -0
- package/src/svelte/navigation-context.ts +62 -0
- package/src/svelte/navigation-scope.svelte +27 -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 +31 -0
- package/src/svelte/runes/use-focus-effect.svelte.ts +41 -0
- package/src/svelte/runes/use-is-focused.svelte.ts +38 -0
- package/src/svelte/runes/use-navigation-state.svelte.ts +48 -0
- package/src/svelte/runes/use-navigation.svelte.ts +49 -0
- package/src/svelte/runes/use-route.svelte.ts +16 -0
- package/src/svelte/runes/use-stack-navigation.svelte.ts +34 -0
- package/src/svelte/runes/use-tab-navigation.svelte.ts +29 -0
- package/src/svelte/screen-props.ts +50 -0
- package/src/svelte/screen-registry.ts +98 -0
- package/src/svelte/screen.svelte +28 -0
- package/src/svelte/stack/index.svelte +261 -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 +205 -0
- package/src/svelte/svelte-ambient.d.ts +6 -0
- package/src/svelte/svelte-compile.test-helper.ts +147 -0
- package/src/svelte/tab-screen-props.ts +22 -0
- package/src/svelte/tab-screen.svelte +26 -0
- package/src/svelte/tabs/index.svelte +269 -0
- package/src/svelte/tabs/index.ts +10 -0
- package/src/svelte/tabs/tab-props.ts +13 -0
- 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 +128 -41
- 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 +108 -43
- package/src/vue/tab-screen.ts +3 -1
- package/src/vue/tabs/index.ts +109 -68
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// useLinkingIntegration: wires the framework-agnostic linking config (../core's
|
|
2
|
+
// resolveRouteFromUrl) onto an existing Stack's INavigatorHandle. Deliberately NOT called from
|
|
3
|
+
// inside stack/index.svelte - app code owns the wiring, the same top-level-prop shape
|
|
4
|
+
// @react-navigation keeps `linking` in on NavigationContainer rather than baking it into the
|
|
5
|
+
// router itself.
|
|
6
|
+
//
|
|
7
|
+
// Svelte-shape difference from React and Vue: React's twin takes the RESOLVED
|
|
8
|
+
// `navigatorHandle: INavigatorHandle` (a React hook re-runs on every render with the caller's
|
|
9
|
+
// current value); Vue's takes the `Ref` object and reads `.value` inside onMounted. A Svelte
|
|
10
|
+
// component's script runs exactly ONCE and its `bind:this` target is only assigned during mount,
|
|
11
|
+
// so this takes a GETTER - the boxed-getter convention this adapter uses everywhere - and reads
|
|
12
|
+
// it INSIDE the effect, i.e. after mount. The read is deliberately TRACKED (no `untrack`, unlike
|
|
13
|
+
// the ./runes files): when the app declares its handle with `$state.raw`, the effect re-runs the
|
|
14
|
+
// moment `bind:this` lands, so the wiring never depends on a mount-ordering guarantee at all.
|
|
15
|
+
// Re-running is safe because the previous run's cleanup tears the subscription down first.
|
|
16
|
+
//
|
|
17
|
+
// `.svelte.ts`: `$effect` is only usable outside a component in a file with this extension.
|
|
18
|
+
import { Linking, dlog } from '@symbiote-native/engine';
|
|
19
|
+
import { resolveRouteFromUrl } from '../core/index.js';
|
|
20
|
+
export function useLinkingIntegration(config, getNavigatorHandle) {
|
|
21
|
+
$effect(() => {
|
|
22
|
+
const handle = getNavigatorHandle();
|
|
23
|
+
if (handle === null) {
|
|
24
|
+
dlog('useLinkingIntegration: navigatorHandle is not available yet');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
let cancelled = false;
|
|
28
|
+
const applyRoute = (url, dispatch) => {
|
|
29
|
+
const route = resolveRouteFromUrl(config, url);
|
|
30
|
+
if (route === null) {
|
|
31
|
+
dlog(`useLinkingIntegration: no route resolved for "${url}"`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
dispatch(route.name, route.params);
|
|
35
|
+
};
|
|
36
|
+
Linking.getInitialURL()
|
|
37
|
+
.then(url => {
|
|
38
|
+
if (cancelled || url === null)
|
|
39
|
+
return;
|
|
40
|
+
dlog(`useLinkingIntegration: initial URL "${url}"`);
|
|
41
|
+
applyRoute(url, handle.replace);
|
|
42
|
+
})
|
|
43
|
+
.catch((error) => {
|
|
44
|
+
dlog(`useLinkingIntegration: getInitialURL failed: ${String(error)}`);
|
|
45
|
+
});
|
|
46
|
+
const subscription = Linking.addEventListener('url', (event) => {
|
|
47
|
+
dlog(`useLinkingIntegration: url event "${event.url}"`);
|
|
48
|
+
applyRoute(event.url, handle.push);
|
|
49
|
+
});
|
|
50
|
+
return () => {
|
|
51
|
+
cancelled = true;
|
|
52
|
+
subscription.remove();
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { INavigationEmitter, IRoute, IAnyNavigatorHandle } from '../core';
|
|
2
|
+
export type { IAnyNavigatorHandle };
|
|
3
|
+
export type INavigationScopeValue = {
|
|
4
|
+
route: IRoute<unknown>;
|
|
5
|
+
navigation: IAnyNavigatorHandle;
|
|
6
|
+
emitter: INavigationEmitter;
|
|
7
|
+
parent?: INavigationScopeValue;
|
|
8
|
+
};
|
|
9
|
+
export type INavigationScope = {
|
|
10
|
+
readonly current: INavigationScopeValue;
|
|
11
|
+
};
|
|
12
|
+
export declare function setNavigationScope(scope: INavigationScope): void;
|
|
13
|
+
export declare function getNavigationScope(): INavigationScope | undefined;
|
|
14
|
+
export declare function requireNavigationScope(runeName: string): INavigationScope;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// The lifecycle half of the framework-agnostic emitter (../core/navigation-events). Svelte's twin
|
|
2
|
+
// of React's Context / Vue's provide-inject is setContext/getContext over one shared key, so a
|
|
3
|
+
// screen's subtree can read its own route, navigator handle, and per-route emitter without
|
|
4
|
+
// prop-drilling.
|
|
5
|
+
//
|
|
6
|
+
// Context in Svelte follows the RUNTIME render tree, not the lexical definition site: a component
|
|
7
|
+
// instantiated inside a snippet the app passed down to <Stack> still sees the context <Stack> (or
|
|
8
|
+
// the NavigationScope it renders) set - verified against the real compiler before this was built
|
|
9
|
+
// on. That is what lets a navigator hand each mounted route its OWN scope: Stack/Tab/Drawer mount
|
|
10
|
+
// one NavigationScope per route (keyed by route.key, exactly like React's per-route
|
|
11
|
+
// `<NavigationContext.Provider>`), and each instance's own init calls the setter once.
|
|
12
|
+
//
|
|
13
|
+
// The value is a BOXED GETTER (`{ get current() }`), never a bare value: Svelte 5 reactivity does
|
|
14
|
+
// not survive being handed out of the declaring scope as a plain value (svelte-adapter-dom-shim
|
|
15
|
+
// skill §21), and consumers must keep seeing the CURRENT route (a setParams that produces a new
|
|
16
|
+
// route object for the same key still has to reach them).
|
|
17
|
+
//
|
|
18
|
+
// `parent` threads the ambient value a navigator read on ITS OWN mount (undefined at the root)
|
|
19
|
+
// into the value it provides to its own screens, forming a linked list - this is what lets a
|
|
20
|
+
// screen nested inside e.g. a Stack-screen-renders-a-Tab composition reach the enclosing Stack via
|
|
21
|
+
// useNavigation().getParent().
|
|
22
|
+
import { getContext, setContext } from 'svelte';
|
|
23
|
+
const NAVIGATION_SCOPE_KEY = Symbol('symbiote-navigation-scope');
|
|
24
|
+
// Written only from NavigationScope's own init (navigation-scope.svelte), never by app code.
|
|
25
|
+
export function setNavigationScope(scope) {
|
|
26
|
+
setContext(NAVIGATION_SCOPE_KEY, scope);
|
|
27
|
+
}
|
|
28
|
+
// undefined simply means "no ancestor NavigationScope", which is a legitimate state (the nesting
|
|
29
|
+
// root a navigator itself reads on mount) rather than an error - callers that require one go
|
|
30
|
+
// through requireNavigationScope below.
|
|
31
|
+
export function getNavigationScope() {
|
|
32
|
+
return getContext(NAVIGATION_SCOPE_KEY);
|
|
33
|
+
}
|
|
34
|
+
// The "throw if missing" half every rune in ./runes needs (useNavigation, useRoute, useIsFocused,
|
|
35
|
+
// useFocusEffect, useNavigationState) - they all read the scope and threw the same shaped error,
|
|
36
|
+
// differing only in which rune name the message names. Centralized here so a wording change lands
|
|
37
|
+
// once instead of five times.
|
|
38
|
+
export function requireNavigationScope(runeName) {
|
|
39
|
+
const scope = getNavigationScope();
|
|
40
|
+
if (scope === undefined) {
|
|
41
|
+
throw new Error(`${runeName} must be used within a screen rendered by <Stack>, <Tab>, or <Drawer>`);
|
|
42
|
+
}
|
|
43
|
+
return scope;
|
|
44
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// The per-route setup boundary: one instance per mounted route, so each route subtree reads its
|
|
3
|
+
// OWN scope (its own route/emitter) invisible to its siblings. Stack keeps every pushed route
|
|
4
|
+
// mounted as a sibling subtree; Tab/Drawer mount only the focused one - either way each needs a
|
|
5
|
+
// distinct provided value, which is an inherent property of any hierarchical
|
|
6
|
+
// dependency-injection scheme, Svelte's context included.
|
|
7
|
+
//
|
|
8
|
+
// `value` is a $props() field, so reading it inside the boxed getter below is a LIVE read: a
|
|
9
|
+
// later change (setParams producing a new route object for the same key) still reaches every
|
|
10
|
+
// consumer without re-running setContext, which Svelte only honours during init anyway.
|
|
11
|
+
import type { Snippet } from 'svelte';
|
|
12
|
+
import { setNavigationScope } from './navigation-context';
|
|
13
|
+
import type { INavigationScopeValue } from './navigation-context';
|
|
14
|
+
|
|
15
|
+
let {
|
|
16
|
+
value,
|
|
17
|
+
children,
|
|
18
|
+
}: { value: INavigationScopeValue; children?: Snippet } = $props();
|
|
19
|
+
|
|
20
|
+
setNavigationScope({
|
|
21
|
+
get current(): INavigationScopeValue {
|
|
22
|
+
return value;
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
{@render children?.()}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { INavigationScopeValue } from './navigation-context';
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
value: INavigationScopeValue;
|
|
5
|
+
children?: Snippet;
|
|
6
|
+
};
|
|
7
|
+
declare const NavigationScope: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
8
|
+
type NavigationScope = ReturnType<typeof NavigationScope>;
|
|
9
|
+
export default NavigationScope;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const SCREEN_REGISTRY_HOST_PROPS: Record<string, unknown>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Where a navigator renders its `<*.Screen>` markers so they can register themselves.
|
|
2
|
+
//
|
|
3
|
+
// The markers have to be RENDERED for their init to run (screen-registry.ts explains why
|
|
4
|
+
// registration is inverted on Svelte), and Svelte turns whitespace between two sibling non-text
|
|
5
|
+
// nodes into a REAL text node - svelte-adapter-dom-shim skill §16, a correctness bug rather than
|
|
6
|
+
// a formatting one. Naturally formatted app markup
|
|
7
|
+
//
|
|
8
|
+
// <Stack>
|
|
9
|
+
// <Stack.Screen name="home" component={Home} />
|
|
10
|
+
// <Stack.Screen name="details" component={Details} />
|
|
11
|
+
// </Stack>
|
|
12
|
+
//
|
|
13
|
+
// therefore compiles to `from_tree([marker, ' ', marker])`, and each ' ' becomes an RCTRawText
|
|
14
|
+
// engine node parented to whatever host tag holds the `{@render children?.()}` call. Inside a
|
|
15
|
+
// plain `symbiote-view` that is the invalid "text outside a <Text> component" shape; inside
|
|
16
|
+
// RNSScreenStack it is worse, since react-native-screens' native side expects only RNSScreen
|
|
17
|
+
// children there.
|
|
18
|
+
//
|
|
19
|
+
// So the markers are rendered inside a zero-size `symbiote-text` instead: raw text inside an
|
|
20
|
+
// RCTText is LEGAL, which turns the hazard from a device crash into a no-op and makes it
|
|
21
|
+
// structurally impossible rather than a rule app authors have to remember. The cost is one
|
|
22
|
+
// collapsed, non-interactive RCTText per navigator.
|
|
23
|
+
export const SCREEN_REGISTRY_HOST_PROPS = {
|
|
24
|
+
style: { position: 'absolute', width: 0, height: 0, opacity: 0 },
|
|
25
|
+
pointerEvents: 'none',
|
|
26
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { useNavigation } from './use-navigation.svelte';
|
|
2
|
+
export type { INavigationHandle } from './use-navigation.svelte';
|
|
3
|
+
export { useStackNavigation } from './use-stack-navigation.svelte';
|
|
4
|
+
export type { IStackNavigationHandle } from './use-stack-navigation.svelte';
|
|
5
|
+
export { useTabNavigation } from './use-tab-navigation.svelte';
|
|
6
|
+
export type { ITabNavigationHandle } from './use-tab-navigation.svelte';
|
|
7
|
+
export { useDrawerNavigation } from './use-drawer-navigation.svelte';
|
|
8
|
+
export type { IDrawerNavigationHandle } from './use-drawer-navigation.svelte';
|
|
9
|
+
export { useRoute } from './use-route.svelte';
|
|
10
|
+
export { useIsFocused } from './use-is-focused.svelte';
|
|
11
|
+
export { useFocusEffect } from './use-focus-effect.svelte';
|
|
12
|
+
export { useNavigationState } from './use-navigation-state.svelte';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// The lifecycle bucket. `runes/` (with `*.svelte.ts` files) is Svelte's own term for
|
|
2
|
+
// $state/$effect, parallel to React's `hooks/` and Vue's `composables/` - per CLAUDE.md's
|
|
3
|
+
// <adapter_src_follows_framework_idioms>, every framework entry names this bucket after ITS
|
|
4
|
+
// framework's word for the concept, never a borrowed one.
|
|
5
|
+
export { useNavigation } from './use-navigation.svelte';
|
|
6
|
+
export { useStackNavigation } from './use-stack-navigation.svelte';
|
|
7
|
+
export { useTabNavigation } from './use-tab-navigation.svelte';
|
|
8
|
+
export { useDrawerNavigation } from './use-drawer-navigation.svelte';
|
|
9
|
+
export { useRoute } from './use-route.svelte';
|
|
10
|
+
export { useIsFocused } from './use-is-focused.svelte';
|
|
11
|
+
export { useFocusEffect } from './use-focus-effect.svelte';
|
|
12
|
+
export { useNavigationState } from './use-navigation-state.svelte';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { IDrawerNavigatorHandle } from '../../core';
|
|
2
|
+
import type { INavigationHandle } from './use-navigation.svelte';
|
|
3
|
+
export type IDrawerNavigationHandle = IDrawerNavigatorHandle & Pick<INavigationHandle, 'addListener' | 'getParent'>;
|
|
4
|
+
export declare function useDrawerNavigation(): {
|
|
5
|
+
readonly current: IDrawerNavigationHandle;
|
|
6
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Twin of use-stack-navigation.svelte.ts, narrowing to a Drawer handle instead.
|
|
2
|
+
import { isDrawerNavigatorHandle } from '../../core/index.js';
|
|
3
|
+
import { useNavigation } from './use-navigation.svelte';
|
|
4
|
+
export function useDrawerNavigation() {
|
|
5
|
+
const navigation = useNavigation();
|
|
6
|
+
const narrowed = $derived.by(() => {
|
|
7
|
+
const value = navigation.current;
|
|
8
|
+
if (!isDrawerNavigatorHandle(value)) {
|
|
9
|
+
throw new Error('useDrawerNavigation() was called from a component whose nearest navigator is not a Drawer. ' +
|
|
10
|
+
'Use useNavigation() instead if this component can render under more than one navigator kind.');
|
|
11
|
+
}
|
|
12
|
+
return value;
|
|
13
|
+
});
|
|
14
|
+
return {
|
|
15
|
+
get current() {
|
|
16
|
+
return narrowed;
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useFocusEffect(effect: () => (() => void) | void): void;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Mirrors @react-navigation's useFocusEffect; Svelte twin of vue/composables/use-focus-effect.ts.
|
|
2
|
+
// React's version requires memoizing `effect` (useCallback) since a new identity re-subscribes it
|
|
3
|
+
// like any other useEffect dependency. That doesn't apply here: a Svelte component's script runs
|
|
4
|
+
// once, so `effect` is read once at call time and closed over directly - there is no dependency
|
|
5
|
+
// array to go stale.
|
|
6
|
+
import { untrack } from 'svelte';
|
|
7
|
+
import { NAVIGATION_EVENT_BLUR, NAVIGATION_EVENT_FOCUS } from '../../core/index.js';
|
|
8
|
+
import { requireNavigationScope } from '../navigation-context.js';
|
|
9
|
+
export function useFocusEffect(effect) {
|
|
10
|
+
const scope = requireNavigationScope('useFocusEffect');
|
|
11
|
+
let cleanup;
|
|
12
|
+
const runEffect = () => {
|
|
13
|
+
cleanup = effect();
|
|
14
|
+
};
|
|
15
|
+
const runCleanup = () => {
|
|
16
|
+
cleanup?.();
|
|
17
|
+
cleanup = undefined;
|
|
18
|
+
};
|
|
19
|
+
// Empty dependency set (see use-is-focused.svelte.ts) so this subscribes once and tears down
|
|
20
|
+
// once. The teardown also runs any still-pending cleanup, matching Vue's onUnmounted.
|
|
21
|
+
$effect(() => {
|
|
22
|
+
const { emitter } = untrack(() => scope.current);
|
|
23
|
+
const unsubscribeFocus = emitter.addListener(NAVIGATION_EVENT_FOCUS, runEffect);
|
|
24
|
+
const unsubscribeBlur = emitter.addListener(NAVIGATION_EVENT_BLUR, runCleanup);
|
|
25
|
+
return () => {
|
|
26
|
+
unsubscribeFocus();
|
|
27
|
+
unsubscribeBlur();
|
|
28
|
+
runCleanup();
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Mirrors @react-navigation's useIsFocused; Svelte twin of vue/composables/use-is-focused.ts.
|
|
2
|
+
// Starts `false` rather than guessing from stack position: the route's emitter only fires 'focus'
|
|
3
|
+
// once RNSScreen's native onAppear lands (stack) or the screen mounts focused (tabs/drawer) - a
|
|
4
|
+
// screen genuinely isn't focused at the instant it mounts, the same async gap real native
|
|
5
|
+
// transitions have.
|
|
6
|
+
import { untrack } from 'svelte';
|
|
7
|
+
import { NAVIGATION_EVENT_BLUR, NAVIGATION_EVENT_FOCUS } from '../../core/index.js';
|
|
8
|
+
import { requireNavigationScope } from '../navigation-context.js';
|
|
9
|
+
export function useIsFocused() {
|
|
10
|
+
const scope = requireNavigationScope('useIsFocused');
|
|
11
|
+
let isFocused = $state(false);
|
|
12
|
+
// `untrack` keeps this effect's dependency set EMPTY, so it subscribes exactly once on mount
|
|
13
|
+
// and cleans up exactly once on unmount - matching Vue's onMounted/onUnmounted pair. Without
|
|
14
|
+
// it the effect would re-read `scope.current` and re-subscribe on every setParams (which
|
|
15
|
+
// produces a new route object for the same key, and therefore the same emitter).
|
|
16
|
+
$effect(() => {
|
|
17
|
+
const { emitter } = untrack(() => scope.current);
|
|
18
|
+
const unsubscribeFocus = emitter.addListener(NAVIGATION_EVENT_FOCUS, () => {
|
|
19
|
+
isFocused = true;
|
|
20
|
+
});
|
|
21
|
+
const unsubscribeBlur = emitter.addListener(NAVIGATION_EVENT_BLUR, () => {
|
|
22
|
+
isFocused = false;
|
|
23
|
+
});
|
|
24
|
+
return () => {
|
|
25
|
+
unsubscribeFocus();
|
|
26
|
+
unsubscribeBlur();
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
return {
|
|
30
|
+
get current() {
|
|
31
|
+
return isFocused;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Mirrors @react-navigation's useNavigationState; Svelte twin of
|
|
2
|
+
// vue/composables/use-navigation-state.ts. The reducer/dispatch machinery lives in
|
|
3
|
+
// core/navigator-state.ts (tab-router-state.ts / drawer-router-state.ts for the other two
|
|
4
|
+
// navigators) - this rune only wires the subscription to the `state` broadcast each navigator
|
|
5
|
+
// re-emits after commit.
|
|
6
|
+
//
|
|
7
|
+
// Seeded from a single-route snapshot ({ routes: [route] }) rather than left undefined: the real
|
|
8
|
+
// broadcast lands after mount, so a selector reading e.g. `state.routes.at(-1)?.name` still
|
|
9
|
+
// resolves correctly on first paint for the common single-route case - the same async gap
|
|
10
|
+
// useIsFocused documents.
|
|
11
|
+
//
|
|
12
|
+
// `$state.raw`, not `$state`: TResult is arbitrary app data (often the router state itself, whose
|
|
13
|
+
// route objects the reducer compares by identity), and a deep reactive proxy would hand callers
|
|
14
|
+
// a different object than the navigator holds - the same identity rule Vue's shallowRef enforces
|
|
15
|
+
// here.
|
|
16
|
+
import { untrack } from 'svelte';
|
|
17
|
+
import { NAVIGATION_EVENT_STATE, isRecord } from '../../core/index.js';
|
|
18
|
+
import { requireNavigationScope } from '../navigation-context.js';
|
|
19
|
+
function isNavigatorState(value) {
|
|
20
|
+
return isRecord(value) && Array.isArray(value.routes);
|
|
21
|
+
}
|
|
22
|
+
export function useNavigationState(selector) {
|
|
23
|
+
const scope = requireNavigationScope('useNavigationState');
|
|
24
|
+
let result = $state.raw(selector({ routes: [scope.current.route] }));
|
|
25
|
+
// Empty dependency set (see use-is-focused.svelte.ts): subscribe once, tear down once.
|
|
26
|
+
$effect(() => {
|
|
27
|
+
const { emitter } = untrack(() => scope.current);
|
|
28
|
+
return emitter.addListener(NAVIGATION_EVENT_STATE, (state) => {
|
|
29
|
+
if (!isNavigatorState(state))
|
|
30
|
+
return;
|
|
31
|
+
result = selector(state);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
get current() {
|
|
36
|
+
return result;
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { INavigationEventListener, INavigationEventName } from '../../core';
|
|
2
|
+
import type { IAnyNavigatorHandle } from '../navigation-context';
|
|
3
|
+
export type INavigationHandle = IAnyNavigatorHandle & {
|
|
4
|
+
addListener: (event: INavigationEventName, listener: INavigationEventListener) => () => void;
|
|
5
|
+
getParent: () => IAnyNavigatorHandle | undefined;
|
|
6
|
+
};
|
|
7
|
+
export declare function useNavigation(): {
|
|
8
|
+
readonly current: INavigationHandle;
|
|
9
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Mirrors @react-navigation's `navigation.addListener('focus', cb)` surface; the Svelte twin of
|
|
2
|
+
// vue/composables/use-navigation.ts. All pub/sub logic lives in ../../core/navigation-events -
|
|
3
|
+
// this rune only reads the navigation scope off the context and binds identity.
|
|
4
|
+
//
|
|
5
|
+
// `.svelte.ts`, and a BOXED GETTER return (`{ get current() }`) rather than a bare value: runes
|
|
6
|
+
// are only usable outside a component in a `.svelte.ts` file, and Svelte 5 reactivity does not
|
|
7
|
+
// survive being returned as a plain value from a plain function (svelte-adapter-dom-shim skill
|
|
8
|
+
// §21). A caller reads `useNavigation().current` inside a `$derived`/template/`$effect`, exactly
|
|
9
|
+
// like unwrapping Vue's `ComputedRef` via `.value`.
|
|
10
|
+
import { requireNavigationScope } from '../navigation-context.js';
|
|
11
|
+
export function useNavigation() {
|
|
12
|
+
const scope = requireNavigationScope('useNavigation');
|
|
13
|
+
const handle = $derived.by(() => {
|
|
14
|
+
const { navigation, emitter, parent } = scope.current;
|
|
15
|
+
return {
|
|
16
|
+
...navigation,
|
|
17
|
+
addListener: emitter.addListener,
|
|
18
|
+
getParent: () => parent?.navigation,
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
return {
|
|
22
|
+
get current() {
|
|
23
|
+
return handle;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Mirrors @react-navigation's useRoute; Svelte twin of vue/composables/use-route.ts. Zero logic
|
|
2
|
+
// of its own - the route object itself is built by Stack's/Tab's/Drawer's render loop from the
|
|
3
|
+
// shared core reducer's state.
|
|
4
|
+
import { requireNavigationScope } from '../navigation-context.js';
|
|
5
|
+
export function useRoute() {
|
|
6
|
+
const scope = requireNavigationScope('useRoute');
|
|
7
|
+
return {
|
|
8
|
+
get current() {
|
|
9
|
+
return scope.current.route;
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { INavigatorHandle } from '../../core';
|
|
2
|
+
import type { INavigationHandle } from './use-navigation.svelte';
|
|
3
|
+
export type IStackNavigationHandle = INavigatorHandle & Pick<INavigationHandle, 'addListener' | 'getParent'>;
|
|
4
|
+
export declare function useStackNavigation(): {
|
|
5
|
+
readonly current: IStackNavigationHandle;
|
|
6
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// useNavigation() returns a union (IAnyNavigatorHandle) since it doesn't know which navigator
|
|
2
|
+
// mounted the calling component. When a component genuinely knows it only ever renders under a
|
|
3
|
+
// Stack, this rune narrows that union ONCE, here, so call sites never write their own
|
|
4
|
+
// 'push' in navigation check. Svelte twin of vue/composables/use-stack-navigation.ts.
|
|
5
|
+
import { isStackNavigatorHandle } from '../../core/index.js';
|
|
6
|
+
import { useNavigation } from './use-navigation.svelte';
|
|
7
|
+
export function useStackNavigation() {
|
|
8
|
+
const navigation = useNavigation();
|
|
9
|
+
const narrowed = $derived.by(() => {
|
|
10
|
+
const value = navigation.current;
|
|
11
|
+
if (!isStackNavigatorHandle(value)) {
|
|
12
|
+
throw new Error('useStackNavigation() was called from a component whose nearest navigator is not a Stack. ' +
|
|
13
|
+
'Use useNavigation() instead if this component can render under more than one navigator kind.');
|
|
14
|
+
}
|
|
15
|
+
return value;
|
|
16
|
+
});
|
|
17
|
+
return {
|
|
18
|
+
get current() {
|
|
19
|
+
return narrowed;
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ITabNavigatorHandle } from '../../core';
|
|
2
|
+
import type { INavigationHandle } from './use-navigation.svelte';
|
|
3
|
+
export type ITabNavigationHandle = ITabNavigatorHandle & Pick<INavigationHandle, 'addListener' | 'getParent'>;
|
|
4
|
+
export declare function useTabNavigation(): {
|
|
5
|
+
readonly current: ITabNavigationHandle;
|
|
6
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Twin of use-stack-navigation.svelte.ts, narrowing to a Tab handle instead.
|
|
2
|
+
import { isTabNavigatorHandle } from '../../core/index.js';
|
|
3
|
+
import { useNavigation } from './use-navigation.svelte';
|
|
4
|
+
export function useTabNavigation() {
|
|
5
|
+
const navigation = useNavigation();
|
|
6
|
+
const narrowed = $derived.by(() => {
|
|
7
|
+
const value = navigation.current;
|
|
8
|
+
if (!isTabNavigatorHandle(value)) {
|
|
9
|
+
throw new Error('useTabNavigation() was called from a component whose nearest navigator is not a Tab. ' +
|
|
10
|
+
'Use useNavigation() instead if this component can render under more than one navigator kind.');
|
|
11
|
+
}
|
|
12
|
+
return value;
|
|
13
|
+
});
|
|
14
|
+
return {
|
|
15
|
+
get current() {
|
|
16
|
+
return narrowed;
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Component } from 'svelte';
|
|
2
|
+
import type { INavigatorHandle, IRoute, IScreenOptions, ISearchBarCommands, ISearchBarOptions } from '../core';
|
|
3
|
+
export type ISvelteSearchBarOptions = ISearchBarOptions & {
|
|
4
|
+
ref?: {
|
|
5
|
+
current: ISearchBarCommands | null;
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
export type ISvelteScreenOptions = Omit<IScreenOptions, 'headerSearchBarOptions'> & {
|
|
9
|
+
headerSearchBarOptions?: ISvelteSearchBarOptions;
|
|
10
|
+
};
|
|
11
|
+
export type IScreenOptionsArgs = {
|
|
12
|
+
route: IRoute<unknown>;
|
|
13
|
+
navigation: INavigatorHandle;
|
|
14
|
+
};
|
|
15
|
+
export type IScreenOptionsResolver = (args: IScreenOptionsArgs) => ISvelteScreenOptions;
|
|
16
|
+
export type IScreenProps = {
|
|
17
|
+
name: string;
|
|
18
|
+
component: Component;
|
|
19
|
+
options?: ISvelteScreenOptions | IScreenOptionsResolver;
|
|
20
|
+
initialParams?: unknown;
|
|
21
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Stack.Screen's public prop surface. Split out of `screen.svelte` because plain `tsc --build`
|
|
2
|
+
// resolves an import of a `.svelte` file through svelte's own ambient `declare module '*.svelte'`
|
|
3
|
+
// fallback (a bare default export only) and never parses the real `<script module>` block - so a
|
|
4
|
+
// NAMED type re-exported from a `.svelte` file is invisible to `tsc`. Same rule the Svelte
|
|
5
|
+
// adapter's own `view-props.ts` / `switch-props.ts` follow.
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Component } from 'svelte';
|
|
2
|
+
export type INavigatorKind = 'stack' | 'tab' | 'drawer';
|
|
3
|
+
export type IRegisteredScreen<TOptions> = {
|
|
4
|
+
readonly name: string;
|
|
5
|
+
readonly component: Component;
|
|
6
|
+
readonly options: TOptions | undefined;
|
|
7
|
+
readonly initialParams: unknown;
|
|
8
|
+
};
|
|
9
|
+
export type IScreenCollector<TOptions> = {
|
|
10
|
+
readonly kind: INavigatorKind;
|
|
11
|
+
register(screen: IRegisteredScreen<TOptions>): void;
|
|
12
|
+
unregister(screen: IRegisteredScreen<TOptions>): void;
|
|
13
|
+
};
|
|
14
|
+
export declare function setScreenCollector<TOptions>(collector: IScreenCollector<TOptions>): void;
|
|
15
|
+
export declare function collectScreen<TOptions>(kind: INavigatorKind, screen: IRegisteredScreen<TOptions>): void;
|
|
16
|
+
export declare function withoutScreen<TOptions>(screens: readonly IRegisteredScreen<TOptions>[], screen: IRegisteredScreen<TOptions>): IRegisteredScreen<TOptions>[];
|
|
17
|
+
export declare function toRegistry<TOptions>(screens: readonly IRegisteredScreen<TOptions>[]): Map<string, IRegisteredScreen<TOptions>>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// How a navigator learns about its `<Stack.Screen>` / `<Tab.Screen>` / `<Drawer.Screen>` markers.
|
|
2
|
+
//
|
|
3
|
+
// React reads `children` as an inspectable array; Vue scans the default slot's vnodes
|
|
4
|
+
// (`vnode.type === Screen`); Angular queries `@ContentChildren`. Svelte has NO equivalent: a
|
|
5
|
+
// component receives its children as an opaque `Snippet`, with no way to enumerate or inspect
|
|
6
|
+
// what is inside it (the same wall the Svelte adapter's ScrollView hit for
|
|
7
|
+
// `stickyHeaderIndices`, svelte-adapter-dom-shim skill §18). So registration is INVERTED here:
|
|
8
|
+
// the navigator publishes a collector on the context, each marker registers ITSELF during its own
|
|
9
|
+
// init, and the navigator's route list derives from what came back.
|
|
10
|
+
//
|
|
11
|
+
// Ordering works out because Svelte's initial render is synchronous and top-down: a navigator
|
|
12
|
+
// renders the children snippet BEFORE the `{#each}` that paints its routes, so by the time the
|
|
13
|
+
// route list is first read every marker has already registered. Verified against the real
|
|
14
|
+
// compiler, not assumed.
|
|
15
|
+
//
|
|
16
|
+
// ONE key, not one per navigator kind: nearest-navigator-wins shadowing falls out of Svelte's own
|
|
17
|
+
// context lookup, so a Stack nested inside a Tab screen automatically captures its own markers.
|
|
18
|
+
// The `kind` tag then rejects a MISMATCHED marker (a `<Tab.Screen>` written inside a `<Stack>`)
|
|
19
|
+
// instead of letting it silently register with some far-away ancestor Tab - the failure mode a
|
|
20
|
+
// per-kind key would have.
|
|
21
|
+
import { getContext, onDestroy, setContext } from 'svelte';
|
|
22
|
+
import { dlog } from '@symbiote-native/engine';
|
|
23
|
+
const SCREEN_COLLECTOR_KEY = Symbol('symbiote-navigation-screen-collector');
|
|
24
|
+
// Called once from a navigator's own init, before it renders the children snippet.
|
|
25
|
+
export function setScreenCollector(collector) {
|
|
26
|
+
setContext(SCREEN_COLLECTOR_KEY, collector);
|
|
27
|
+
}
|
|
28
|
+
// Called once from a marker's own init. Registration is undone on destroy, so a marker behind an
|
|
29
|
+
// `{#if}` disappears from the registry exactly like a Vue slot scan stops seeing a removed vnode.
|
|
30
|
+
export function collectScreen(kind, screen) {
|
|
31
|
+
const collector = getContext(SCREEN_COLLECTOR_KEY);
|
|
32
|
+
if (collector === undefined) {
|
|
33
|
+
dlog(`navigation: <${kind} screen "${screen.name}"> rendered outside any navigator, ignored`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (collector.kind !== kind) {
|
|
37
|
+
dlog(`navigation: a ${kind} screen marker ("${screen.name}") was rendered inside a ` +
|
|
38
|
+
`${collector.kind} navigator, ignored`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
collector.register(screen);
|
|
42
|
+
onDestroy(() => collector.unregister(screen));
|
|
43
|
+
}
|
|
44
|
+
// The navigator side of the pair: a plain, non-reactive list plus the two mutators the collector
|
|
45
|
+
// exposes. The navigator holds the list in `$state.raw` and reassigns it (never mutates in place)
|
|
46
|
+
// so a component reference - a plain function - is never wrapped in a deep reactive proxy, which
|
|
47
|
+
// would break both the getters above and Svelte's own component identity checks.
|
|
48
|
+
export function withoutScreen(screens, screen) {
|
|
49
|
+
return screens.filter(candidate => candidate !== screen);
|
|
50
|
+
}
|
|
51
|
+
// name -> entry, last registration wins, exactly like Vue's collectRegistry Map.
|
|
52
|
+
export function toRegistry(screens) {
|
|
53
|
+
const registry = new Map();
|
|
54
|
+
for (const screen of screens)
|
|
55
|
+
registry.set(screen.name, screen);
|
|
56
|
+
return registry;
|
|
57
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// Stack.Screen: a declarative marker that paints nothing. Stack reads it through the
|
|
3
|
+
// context-based collector (screen-registry.ts) rather than by scanning children the way React
|
|
4
|
+
// and Vue can, then mounts the registered component itself for each pushed route.
|
|
5
|
+
//
|
|
6
|
+
// Every field is handed over as a GETTER over this component's own `$props()` bindings, so the
|
|
7
|
+
// navigator always sees the CURRENT value - reassigning `options` on an already-mounted marker
|
|
8
|
+
// reaches the next options fold without re-registering.
|
|
9
|
+
import { collectScreen } from './screen-registry';
|
|
10
|
+
import type { IScreenProps } from './screen-props';
|
|
11
|
+
|
|
12
|
+
let { name, component, options, initialParams }: IScreenProps = $props();
|
|
13
|
+
|
|
14
|
+
collectScreen('stack', {
|
|
15
|
+
get name(): IScreenProps['name'] {
|
|
16
|
+
return name;
|
|
17
|
+
},
|
|
18
|
+
get component(): IScreenProps['component'] {
|
|
19
|
+
return component;
|
|
20
|
+
},
|
|
21
|
+
get options(): IScreenProps['options'] {
|
|
22
|
+
return options;
|
|
23
|
+
},
|
|
24
|
+
get initialParams(): unknown {
|
|
25
|
+
return initialParams;
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
</script>
|