@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,95 @@
|
|
|
1
|
+
// Live-tree walkers the smoke tests in this folder share.
|
|
2
|
+
//
|
|
3
|
+
// `fabric.find()` (from @symbiote-native/test-utils) walks the CREATION log, not the current
|
|
4
|
+
// tree: a node it returns stays "found" forever, and its `props` reflect creation time rather
|
|
5
|
+
// than what is currently committed (svelte-adapter-dom-shim skill §15). Every assertion about
|
|
6
|
+
// what is on screen NOW - "this route is gone after a pop", "this header currently carries this
|
|
7
|
+
// title" - therefore walks `fabric.appRoot()` instead.
|
|
8
|
+
|
|
9
|
+
export type IFabricNode = {
|
|
10
|
+
viewName?: string;
|
|
11
|
+
props?: Record<string, unknown>;
|
|
12
|
+
children?: IFabricNode[];
|
|
13
|
+
instanceHandle?: unknown;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function isFabricNode(value: unknown): value is IFabricNode {
|
|
17
|
+
return typeof value === 'object' && value !== null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function walkLive(
|
|
21
|
+
root: unknown,
|
|
22
|
+
visit: (node: IFabricNode) => void,
|
|
23
|
+
): void {
|
|
24
|
+
if (!isFabricNode(root)) return;
|
|
25
|
+
visit(root);
|
|
26
|
+
for (const child of root.children ?? []) walkLive(child, visit);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function findAllLive(root: unknown, viewName: string): IFabricNode[] {
|
|
30
|
+
const found: IFabricNode[] = [];
|
|
31
|
+
walkLive(root, node => {
|
|
32
|
+
if (node.viewName === viewName) found.push(node);
|
|
33
|
+
});
|
|
34
|
+
return found;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function findLive(
|
|
38
|
+
root: unknown,
|
|
39
|
+
viewName: string,
|
|
40
|
+
): IFabricNode | undefined {
|
|
41
|
+
return findAllLive(root, viewName)[0];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function countLive(root: unknown, viewName: string): number {
|
|
45
|
+
return findAllLive(root, viewName).length;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function findLiveByTestId(
|
|
49
|
+
root: unknown,
|
|
50
|
+
testID: string,
|
|
51
|
+
): IFabricNode | undefined {
|
|
52
|
+
let found: IFabricNode | undefined;
|
|
53
|
+
walkLive(root, node => {
|
|
54
|
+
if (found === undefined && node.props?.testID === testID) found = node;
|
|
55
|
+
});
|
|
56
|
+
return found;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const TEXT_CONTAINER_VIEW_NAMES = new Set(['RCTText', 'RCTVirtualText']);
|
|
60
|
+
|
|
61
|
+
// Every raw text committed under a parent that cannot hold one - an invalid Fabric child.
|
|
62
|
+
// The gap Svelte leaves between two sibling tags compiles to a ' ' text node, and the shim
|
|
63
|
+
// drops it exactly when the parent takes no raw text (dom-shim/text.ts, svelte-adapter-dom-shim
|
|
64
|
+
// §16b); inside an RCTText the same string is a real word separator and must survive. So the
|
|
65
|
+
// parent, not the string, is what makes a raw text legal, and this returns the ones that are
|
|
66
|
+
// not. Formatted `viewName > "text"` so a failure names the offender.
|
|
67
|
+
export function rawTextsOutsideTextContainer(root: unknown): string[] {
|
|
68
|
+
const found: string[] = [];
|
|
69
|
+
const visit = (node: unknown, parent: IFabricNode | undefined): void => {
|
|
70
|
+
if (!isFabricNode(node)) return;
|
|
71
|
+
const parentName = parent?.viewName;
|
|
72
|
+
if (
|
|
73
|
+
node.viewName === 'RCTRawText' &&
|
|
74
|
+
(parentName === undefined || !TEXT_CONTAINER_VIEW_NAMES.has(parentName))
|
|
75
|
+
) {
|
|
76
|
+
found.push(`${String(parentName)} > ${JSON.stringify(node.props?.text)}`);
|
|
77
|
+
}
|
|
78
|
+
for (const child of node.children ?? []) visit(child, node);
|
|
79
|
+
};
|
|
80
|
+
visit(root, undefined);
|
|
81
|
+
return found;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// A depth-indented `viewName` outline, for asserting the exact committed SHAPE (which is what
|
|
85
|
+
// catches a stray whitespace text node, an extra wrapper, or a missing header).
|
|
86
|
+
export function outline(root: unknown): string[] {
|
|
87
|
+
const lines: string[] = [];
|
|
88
|
+
const visit = (node: unknown, depth: number): void => {
|
|
89
|
+
if (!isFabricNode(node)) return;
|
|
90
|
+
lines.push(`${' '.repeat(depth)}${String(node.viewName)}`);
|
|
91
|
+
for (const child of node.children ?? []) visit(child, depth + 1);
|
|
92
|
+
};
|
|
93
|
+
visit(root, 0);
|
|
94
|
+
return lines;
|
|
95
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// @symbiote-native/navigation/svelte: the Svelte native-stack navigator over
|
|
2
|
+
// react-native-screens' native view primitives. Importing this barrel first registers the native
|
|
3
|
+
// views' ViewConfigs (../register, a side-effect import of the codegen specs - never
|
|
4
|
+
// react-native-screens' own React components), then exposes Stack (with Stack.Screen attached)
|
|
5
|
+
// and the navigator handle. Mirrors vue/index.ts's barrel shape.
|
|
6
|
+
|
|
7
|
+
import '../register';
|
|
8
|
+
|
|
9
|
+
export { Stack } from './stack';
|
|
10
|
+
export type { INavigatorHandle, IStackProps } from './stack';
|
|
11
|
+
export { default as Screen } from './screen.svelte';
|
|
12
|
+
export type {
|
|
13
|
+
IScreenOptionsArgs,
|
|
14
|
+
IScreenOptionsResolver,
|
|
15
|
+
IScreenProps,
|
|
16
|
+
ISvelteScreenOptions,
|
|
17
|
+
ISvelteSearchBarOptions,
|
|
18
|
+
} from './screen-props';
|
|
19
|
+
export type {
|
|
20
|
+
IScreenOptions,
|
|
21
|
+
IStackAnimation,
|
|
22
|
+
IStackPresentation,
|
|
23
|
+
} from '../core';
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
useNavigation,
|
|
27
|
+
useStackNavigation,
|
|
28
|
+
useTabNavigation,
|
|
29
|
+
useDrawerNavigation,
|
|
30
|
+
useRoute,
|
|
31
|
+
useIsFocused,
|
|
32
|
+
useFocusEffect,
|
|
33
|
+
useNavigationState,
|
|
34
|
+
} from './runes';
|
|
35
|
+
export type {
|
|
36
|
+
INavigationHandle,
|
|
37
|
+
IStackNavigationHandle,
|
|
38
|
+
ITabNavigationHandle,
|
|
39
|
+
IDrawerNavigationHandle,
|
|
40
|
+
} from './runes';
|
|
41
|
+
export type {
|
|
42
|
+
INavigationScopeValue,
|
|
43
|
+
IAnyNavigatorHandle,
|
|
44
|
+
} from './navigation-context';
|
|
45
|
+
|
|
46
|
+
export { useLinkingIntegration } from './linking.svelte';
|
|
47
|
+
export type { ILinkingConfig, IScreenLinkingConfig } from '../core';
|
|
48
|
+
|
|
49
|
+
// Tab: the bottom-tabs navigator, a PURE-JS UI (no react-native-screens views involved, so no
|
|
50
|
+
// extra ViewConfig registration is needed beyond the ../register import above).
|
|
51
|
+
export { Tab } from './tabs';
|
|
52
|
+
export type { ITabNavigatorHandle, ITabProps } from './tabs';
|
|
53
|
+
export { default as TabScreen } from './tab-screen.svelte';
|
|
54
|
+
export type {
|
|
55
|
+
ITabScreenOptionsArgs,
|
|
56
|
+
ITabScreenOptionsResolver,
|
|
57
|
+
ITabScreenProps,
|
|
58
|
+
} from './tab-screen-props';
|
|
59
|
+
export type { ITabOptions, ITabBarIcon } from '../core';
|
|
60
|
+
|
|
61
|
+
// Drawer: the swipeable drawer navigator, a PURE-JS UI (PanResponder + Animated, no
|
|
62
|
+
// react-native-screens views involved, so no extra ViewConfig registration is needed beyond the
|
|
63
|
+
// ../register import above). See drawer/index.svelte's header for the feasibility note re:
|
|
64
|
+
// react-native-gesture-handler / react-native-reanimated parity gaps.
|
|
65
|
+
export { Drawer } from './drawer';
|
|
66
|
+
export type {
|
|
67
|
+
IDrawerContentSlotProps,
|
|
68
|
+
IDrawerNavigatorHandle,
|
|
69
|
+
IDrawerProps,
|
|
70
|
+
IDrawerDescriptorMap,
|
|
71
|
+
} from './drawer';
|
|
72
|
+
export { default as DrawerScreen } from './drawer-screen.svelte';
|
|
73
|
+
export type {
|
|
74
|
+
IDrawerScreenOptionsArgs,
|
|
75
|
+
IDrawerScreenOptionsResolver,
|
|
76
|
+
IDrawerScreenProps,
|
|
77
|
+
} from './drawer-screen-props';
|
|
78
|
+
export type {
|
|
79
|
+
IDrawerOptions,
|
|
80
|
+
IDrawerScreenOptions,
|
|
81
|
+
IDrawerType,
|
|
82
|
+
IDrawerPosition,
|
|
83
|
+
} from '../core';
|
|
@@ -0,0 +1,72 @@
|
|
|
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
|
+
|
|
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 useLinkingIntegration(
|
|
25
|
+
config: ILinkingConfig,
|
|
26
|
+
getNavigatorHandle: () => INavigatorHandle | null,
|
|
27
|
+
): void {
|
|
28
|
+
$effect(() => {
|
|
29
|
+
const handle = getNavigatorHandle();
|
|
30
|
+
if (handle === null) {
|
|
31
|
+
dlog('useLinkingIntegration: navigatorHandle is not available yet');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let cancelled = false;
|
|
36
|
+
|
|
37
|
+
const applyRoute = (
|
|
38
|
+
url: string,
|
|
39
|
+
dispatch: (name: string, params?: unknown) => void,
|
|
40
|
+
): void => {
|
|
41
|
+
const route = resolveRouteFromUrl(config, url);
|
|
42
|
+
if (route === null) {
|
|
43
|
+
dlog(`useLinkingIntegration: no route resolved for "${url}"`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
dispatch(route.name, route.params);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
Linking.getInitialURL()
|
|
50
|
+
.then(url => {
|
|
51
|
+
if (cancelled || url === null) return;
|
|
52
|
+
dlog(`useLinkingIntegration: initial URL "${url}"`);
|
|
53
|
+
applyRoute(url, handle.replace);
|
|
54
|
+
})
|
|
55
|
+
.catch((error: unknown) => {
|
|
56
|
+
dlog(`useLinkingIntegration: getInitialURL failed: ${String(error)}`);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const subscription: IEventSubscription = Linking.addEventListener(
|
|
60
|
+
'url',
|
|
61
|
+
(event: IUrlEvent) => {
|
|
62
|
+
dlog(`useLinkingIntegration: url event "${event.url}"`);
|
|
63
|
+
applyRoute(event.url, handle.push);
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return () => {
|
|
68
|
+
cancelled = true;
|
|
69
|
+
subscription.remove();
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
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
|
+
|
|
23
|
+
import { getContext, setContext } from 'svelte';
|
|
24
|
+
import type { INavigationEmitter, IRoute, IAnyNavigatorHandle } from '../core';
|
|
25
|
+
export type { IAnyNavigatorHandle };
|
|
26
|
+
|
|
27
|
+
export type INavigationScopeValue = {
|
|
28
|
+
route: IRoute<unknown>;
|
|
29
|
+
navigation: IAnyNavigatorHandle;
|
|
30
|
+
emitter: INavigationEmitter;
|
|
31
|
+
parent?: INavigationScopeValue;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type INavigationScope = { readonly current: INavigationScopeValue };
|
|
35
|
+
|
|
36
|
+
const NAVIGATION_SCOPE_KEY = Symbol('symbiote-navigation-scope');
|
|
37
|
+
|
|
38
|
+
// Written only from NavigationScope's own init (navigation-scope.svelte), never by app code.
|
|
39
|
+
export function setNavigationScope(scope: INavigationScope): void {
|
|
40
|
+
setContext(NAVIGATION_SCOPE_KEY, scope);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// undefined simply means "no ancestor NavigationScope", which is a legitimate state (the nesting
|
|
44
|
+
// root a navigator itself reads on mount) rather than an error - callers that require one go
|
|
45
|
+
// through requireNavigationScope below.
|
|
46
|
+
export function getNavigationScope(): INavigationScope | undefined {
|
|
47
|
+
return getContext<INavigationScope | undefined>(NAVIGATION_SCOPE_KEY);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// The "throw if missing" half every rune in ./runes needs (useNavigation, useRoute, useIsFocused,
|
|
51
|
+
// useFocusEffect, useNavigationState) - they all read the scope and threw the same shaped error,
|
|
52
|
+
// differing only in which rune name the message names. Centralized here so a wording change lands
|
|
53
|
+
// once instead of five times.
|
|
54
|
+
export function requireNavigationScope(runeName: string): INavigationScope {
|
|
55
|
+
const scope = getNavigationScope();
|
|
56
|
+
if (scope === undefined) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`${runeName} must be used within a screen rendered by <Stack>, <Tab>, or <Drawer>`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return scope;
|
|
62
|
+
}
|
|
@@ -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,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: Record<string, unknown> = {
|
|
24
|
+
style: { position: 'absolute', width: 0, height: 0, opacity: 0 },
|
|
25
|
+
pointerEvents: 'none',
|
|
26
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
|
|
6
|
+
export { useNavigation } from './use-navigation.svelte';
|
|
7
|
+
export type { INavigationHandle } from './use-navigation.svelte';
|
|
8
|
+
export { useStackNavigation } from './use-stack-navigation.svelte';
|
|
9
|
+
export type { IStackNavigationHandle } from './use-stack-navigation.svelte';
|
|
10
|
+
export { useTabNavigation } from './use-tab-navigation.svelte';
|
|
11
|
+
export type { ITabNavigationHandle } from './use-tab-navigation.svelte';
|
|
12
|
+
export { useDrawerNavigation } from './use-drawer-navigation.svelte';
|
|
13
|
+
export type { IDrawerNavigationHandle } from './use-drawer-navigation.svelte';
|
|
14
|
+
export { useRoute } from './use-route.svelte';
|
|
15
|
+
export { useIsFocused } from './use-is-focused.svelte';
|
|
16
|
+
export { useFocusEffect } from './use-focus-effect.svelte';
|
|
17
|
+
export { useNavigationState } from './use-navigation-state.svelte';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Twin of use-stack-navigation.svelte.ts, narrowing to a Drawer handle instead.
|
|
2
|
+
|
|
3
|
+
import type { IDrawerNavigatorHandle } from '../../core';
|
|
4
|
+
import { isDrawerNavigatorHandle } from '../../core';
|
|
5
|
+
import { useNavigation } from './use-navigation.svelte';
|
|
6
|
+
import type { INavigationHandle } from './use-navigation.svelte';
|
|
7
|
+
|
|
8
|
+
export type IDrawerNavigationHandle = IDrawerNavigatorHandle &
|
|
9
|
+
Pick<INavigationHandle, 'addListener' | 'getParent'>;
|
|
10
|
+
|
|
11
|
+
export function useDrawerNavigation(): {
|
|
12
|
+
readonly current: IDrawerNavigationHandle;
|
|
13
|
+
} {
|
|
14
|
+
const navigation = useNavigation();
|
|
15
|
+
const narrowed = $derived.by<IDrawerNavigationHandle>(() => {
|
|
16
|
+
const value = navigation.current;
|
|
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
|
+
|
|
26
|
+
return {
|
|
27
|
+
get current(): IDrawerNavigationHandle {
|
|
28
|
+
return narrowed;
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
|
|
7
|
+
import { untrack } from 'svelte';
|
|
8
|
+
import { NAVIGATION_EVENT_BLUR, NAVIGATION_EVENT_FOCUS } from '../../core';
|
|
9
|
+
import { requireNavigationScope } from '../navigation-context';
|
|
10
|
+
|
|
11
|
+
export function useFocusEffect(effect: () => (() => void) | void): void {
|
|
12
|
+
const scope = requireNavigationScope('useFocusEffect');
|
|
13
|
+
|
|
14
|
+
let cleanup: (() => void) | void;
|
|
15
|
+
const runEffect = (): void => {
|
|
16
|
+
cleanup = effect();
|
|
17
|
+
};
|
|
18
|
+
const runCleanup = (): void => {
|
|
19
|
+
cleanup?.();
|
|
20
|
+
cleanup = undefined;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Empty dependency set (see use-is-focused.svelte.ts) so this subscribes once and tears down
|
|
24
|
+
// once. The teardown also runs any still-pending cleanup, matching Vue's onUnmounted.
|
|
25
|
+
$effect(() => {
|
|
26
|
+
const { emitter } = untrack(() => scope.current);
|
|
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
|
+
return () => {
|
|
36
|
+
unsubscribeFocus();
|
|
37
|
+
unsubscribeBlur();
|
|
38
|
+
runCleanup();
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
|
|
7
|
+
import { untrack } from 'svelte';
|
|
8
|
+
import { NAVIGATION_EVENT_BLUR, NAVIGATION_EVENT_FOCUS } from '../../core';
|
|
9
|
+
import { requireNavigationScope } from '../navigation-context';
|
|
10
|
+
|
|
11
|
+
export function useIsFocused(): { readonly current: boolean } {
|
|
12
|
+
const scope = requireNavigationScope('useIsFocused');
|
|
13
|
+
let isFocused = $state(false);
|
|
14
|
+
|
|
15
|
+
// `untrack` keeps this effect's dependency set EMPTY, so it subscribes exactly once on mount
|
|
16
|
+
// and cleans up exactly once on unmount - matching Vue's onMounted/onUnmounted pair. Without
|
|
17
|
+
// it the effect would re-read `scope.current` and re-subscribe on every setParams (which
|
|
18
|
+
// produces a new route object for the same key, and therefore the same emitter).
|
|
19
|
+
$effect(() => {
|
|
20
|
+
const { emitter } = untrack(() => scope.current);
|
|
21
|
+
const unsubscribeFocus = emitter.addListener(NAVIGATION_EVENT_FOCUS, () => {
|
|
22
|
+
isFocused = true;
|
|
23
|
+
});
|
|
24
|
+
const unsubscribeBlur = emitter.addListener(NAVIGATION_EVENT_BLUR, () => {
|
|
25
|
+
isFocused = false;
|
|
26
|
+
});
|
|
27
|
+
return () => {
|
|
28
|
+
unsubscribeFocus();
|
|
29
|
+
unsubscribeBlur();
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
get current(): boolean {
|
|
35
|
+
return isFocused;
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
|
|
17
|
+
import { untrack } from 'svelte';
|
|
18
|
+
import type { INavigatorState } from '../../core';
|
|
19
|
+
import { NAVIGATION_EVENT_STATE, isRecord } from '../../core';
|
|
20
|
+
import { requireNavigationScope } from '../navigation-context';
|
|
21
|
+
|
|
22
|
+
function isNavigatorState(value: unknown): value is INavigatorState {
|
|
23
|
+
return isRecord(value) && Array.isArray(value.routes);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function useNavigationState<TResult>(
|
|
27
|
+
selector: (state: INavigatorState) => TResult,
|
|
28
|
+
): {
|
|
29
|
+
readonly current: TResult;
|
|
30
|
+
} {
|
|
31
|
+
const scope = requireNavigationScope('useNavigationState');
|
|
32
|
+
let result = $state.raw<TResult>(selector({ routes: [scope.current.route] }));
|
|
33
|
+
|
|
34
|
+
// Empty dependency set (see use-is-focused.svelte.ts): subscribe once, tear down once.
|
|
35
|
+
$effect(() => {
|
|
36
|
+
const { emitter } = untrack(() => scope.current);
|
|
37
|
+
return emitter.addListener(NAVIGATION_EVENT_STATE, (state: unknown) => {
|
|
38
|
+
if (!isNavigatorState(state)) return;
|
|
39
|
+
result = selector(state);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
get current(): TResult {
|
|
45
|
+
return result;
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
|
|
11
|
+
import type {
|
|
12
|
+
INavigationEventListener,
|
|
13
|
+
INavigationEventName,
|
|
14
|
+
} from '../../core';
|
|
15
|
+
import { requireNavigationScope } from '../navigation-context';
|
|
16
|
+
import type { IAnyNavigatorHandle } from '../navigation-context';
|
|
17
|
+
|
|
18
|
+
export type INavigationHandle = IAnyNavigatorHandle & {
|
|
19
|
+
addListener: (
|
|
20
|
+
event: INavigationEventName,
|
|
21
|
+
listener: INavigationEventListener,
|
|
22
|
+
) => () => void;
|
|
23
|
+
// Walks exactly ONE hop up the scope's `parent` chain to the enclosing navigator's handle -
|
|
24
|
+
// e.g. a Tab screen nested inside a Stack screen calling getParent() to push a new Stack route.
|
|
25
|
+
// Callers narrow the union themselves ('push' in parent, etc.). Deliberately NOT
|
|
26
|
+
// react-navigation's getParent(id) (named/targeted ancestor lookup) or a target-based dispatch
|
|
27
|
+
// to a specific nested navigator by name - plain immediate-parent walking is v1 scope; multi-hop
|
|
28
|
+
// ancestry would need each returned handle to carry its own getParent, which the plain
|
|
29
|
+
// per-navigator handle types (INavigatorHandle/ITabNavigatorHandle/IDrawerNavigatorHandle) don't.
|
|
30
|
+
getParent: () => IAnyNavigatorHandle | undefined;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function useNavigation(): { readonly current: INavigationHandle } {
|
|
34
|
+
const scope = requireNavigationScope('useNavigation');
|
|
35
|
+
const handle = $derived.by<INavigationHandle>(() => {
|
|
36
|
+
const { navigation, emitter, parent } = scope.current;
|
|
37
|
+
return {
|
|
38
|
+
...navigation,
|
|
39
|
+
addListener: emitter.addListener,
|
|
40
|
+
getParent: () => parent?.navigation,
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
get current(): INavigationHandle {
|
|
46
|
+
return handle;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
|
|
5
|
+
import type { IRoute } from '../../core';
|
|
6
|
+
import { requireNavigationScope } from '../navigation-context';
|
|
7
|
+
|
|
8
|
+
export function useRoute(): { readonly current: IRoute<unknown> } {
|
|
9
|
+
const scope = requireNavigationScope('useRoute');
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
get current(): IRoute<unknown> {
|
|
13
|
+
return scope.current.route;
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
|
|
6
|
+
import type { INavigatorHandle } from '../../core';
|
|
7
|
+
import { isStackNavigatorHandle } from '../../core';
|
|
8
|
+
import { useNavigation } from './use-navigation.svelte';
|
|
9
|
+
import type { INavigationHandle } from './use-navigation.svelte';
|
|
10
|
+
|
|
11
|
+
export type IStackNavigationHandle = INavigatorHandle &
|
|
12
|
+
Pick<INavigationHandle, 'addListener' | 'getParent'>;
|
|
13
|
+
|
|
14
|
+
export function useStackNavigation(): {
|
|
15
|
+
readonly current: IStackNavigationHandle;
|
|
16
|
+
} {
|
|
17
|
+
const navigation = useNavigation();
|
|
18
|
+
const narrowed = $derived.by<IStackNavigationHandle>(() => {
|
|
19
|
+
const value = navigation.current;
|
|
20
|
+
if (!isStackNavigatorHandle(value)) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
'useStackNavigation() was called from a component whose nearest navigator is not a Stack. ' +
|
|
23
|
+
'Use useNavigation() instead if this component can render under more than one navigator kind.',
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
get current(): IStackNavigationHandle {
|
|
31
|
+
return narrowed;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|