@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,269 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
// Tab, the Svelte lifecycle half. The focused-index router (tab-router-state) and the tab-bar
|
|
3
|
+
// Descriptor builder (render-tabs) live in @symbiote-native/navigation core, shared verbatim
|
|
4
|
+
// with the React/Vue/Angular entries; here Svelte supplies the lifecycle - `$state.raw` for the
|
|
5
|
+
// router (its twin of useReducer), a module counter for route-key generation, `export
|
|
6
|
+
// function`s for the jumpTo/setParams handle - plus the Descriptor bridge for the tab-bar leaf.
|
|
7
|
+
// Unlike Stack, a bottom-tabs bar is a PURE-JS UI: it paints ordinary `symbiote-view`/
|
|
8
|
+
// `symbiote-text` primitives via the shared render fn, so there is no react-native-screens
|
|
9
|
+
// ViewConfig to register here - Tab needs no `../../register` import.
|
|
10
|
+
//
|
|
11
|
+
// Screens are discovered through the context collector (../screen-registry.ts), not by reading
|
|
12
|
+
// `children` - see stack/index.svelte's header for why, and ../registry-host.ts for where the
|
|
13
|
+
// markers are rendered.
|
|
14
|
+
let tabInstanceCounter = 0;
|
|
15
|
+
|
|
16
|
+
const TAB_ROOT_PROPS: Record<string, unknown> = { style: { flex: 1 } };
|
|
17
|
+
const TAB_CONTENT_PROPS: Record<string, unknown> = { style: { flex: 1 } };
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<script lang="ts">
|
|
21
|
+
import type { Component } from 'svelte';
|
|
22
|
+
import { onDestroy, tick } from 'svelte';
|
|
23
|
+
import { dlog } from '@symbiote-native/engine';
|
|
24
|
+
import type { ShimElement } from '@symbiote-native/svelte/native-view-bridge';
|
|
25
|
+
import {
|
|
26
|
+
NAVIGATION_EVENT_BLUR,
|
|
27
|
+
NAVIGATION_EVENT_FOCUS,
|
|
28
|
+
createInitialTabState,
|
|
29
|
+
createNavigationEmitter,
|
|
30
|
+
diffFocusedRoute,
|
|
31
|
+
isFocusedRoute,
|
|
32
|
+
reconcileTabRoutes,
|
|
33
|
+
renderTabBar,
|
|
34
|
+
tabRouterReducer,
|
|
35
|
+
} from '../../core';
|
|
36
|
+
import type {
|
|
37
|
+
IDescriptor,
|
|
38
|
+
INavigationEmitter,
|
|
39
|
+
IRoute,
|
|
40
|
+
ITabBarItemView,
|
|
41
|
+
ITabNavigatorHandle,
|
|
42
|
+
ITabOptions,
|
|
43
|
+
ITabRouterAction,
|
|
44
|
+
ITabRouterState,
|
|
45
|
+
} from '../../core';
|
|
46
|
+
import { createDescriptorSubtreeSync } from '../descriptor-subtree';
|
|
47
|
+
import { getNavigationScope } from '../navigation-context';
|
|
48
|
+
import type { INavigationScopeValue } from '../navigation-context';
|
|
49
|
+
import NavigationScope from '../navigation-scope.svelte';
|
|
50
|
+
import { SCREEN_REGISTRY_HOST_PROPS } from '../registry-host';
|
|
51
|
+
import {
|
|
52
|
+
setScreenCollector,
|
|
53
|
+
toRegistry,
|
|
54
|
+
withoutScreen,
|
|
55
|
+
} from '../screen-registry';
|
|
56
|
+
import type { IRegisteredScreen } from '../screen-registry';
|
|
57
|
+
import type { ITabScreenProps } from '../tab-screen-props';
|
|
58
|
+
import type { ITabProps } from './tab-props';
|
|
59
|
+
|
|
60
|
+
let { initialRouteName, screenOptions, children }: ITabProps = $props();
|
|
61
|
+
|
|
62
|
+
// Read BEFORE this Tab establishes its own per-screen NavigationScope - becomes the `parent`
|
|
63
|
+
// link a nested screen's useNavigation().getParent() walks. undefined at the nesting root.
|
|
64
|
+
const parentScope = getNavigationScope();
|
|
65
|
+
|
|
66
|
+
const routeIdPrefix = `tab-${(tabInstanceCounter += 1)}`;
|
|
67
|
+
|
|
68
|
+
let screens = $state.raw<IRegisteredScreen<ITabScreenProps['options']>[]>([]);
|
|
69
|
+
setScreenCollector<ITabScreenProps['options']>({
|
|
70
|
+
kind: 'tab',
|
|
71
|
+
register: screen => {
|
|
72
|
+
screens = [...screens, screen];
|
|
73
|
+
},
|
|
74
|
+
unregister: screen => {
|
|
75
|
+
screens = withoutScreen(screens, screen);
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const registry = $derived(toRegistry(screens));
|
|
80
|
+
|
|
81
|
+
// A tab's route list is a PROJECTION of the registry, not navigation history the way Stack's
|
|
82
|
+
// is: a marker registering or unregistering must add or drop its tab. Unlike Stack, a route key
|
|
83
|
+
// here is derived from the route NAME rather than an incrementing sequence, so re-deriving this
|
|
84
|
+
// list on every registry change re-keys nothing.
|
|
85
|
+
const registeredRoutes = $derived.by<IRoute<unknown>[]>(() =>
|
|
86
|
+
[...registry.entries()].map(([name, entry]) => ({
|
|
87
|
+
key: `${routeIdPrefix}-${name}`,
|
|
88
|
+
name,
|
|
89
|
+
params: entry.initialParams,
|
|
90
|
+
})),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// The dispatched half is the only STATE here; the registry half is re-derived and reconciled
|
|
94
|
+
// against it on every change (reconcileTabRoutes, core - it is what preserves each surviving
|
|
95
|
+
// route's key/params and keeps the focus on the same route NAME). Nothing needs memoizing
|
|
96
|
+
// inside the derivation, so there is no state write during a derivation to avoid in the first
|
|
97
|
+
// place. Until something dispatches there is nothing to reconcile against, hence the seed.
|
|
98
|
+
let dispatchedState = $state.raw<ITabRouterState | null>(null);
|
|
99
|
+
|
|
100
|
+
const state = $derived.by<ITabRouterState>(() => {
|
|
101
|
+
if (registeredRoutes.length === 0)
|
|
102
|
+
dlog('Tab: no <Tab.Screen> children registered');
|
|
103
|
+
const dispatched = dispatchedState;
|
|
104
|
+
return dispatched === null
|
|
105
|
+
? createInitialTabState(registeredRoutes, initialRouteName)
|
|
106
|
+
: reconcileTabRoutes(dispatched, registeredRoutes);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
function dispatch(action: ITabRouterAction): void {
|
|
110
|
+
dispatchedState = tabRouterReducer(state, action);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function jumpTo(name: string, params?: unknown): void {
|
|
114
|
+
dispatch({ type: 'jumpTo', name, params });
|
|
115
|
+
}
|
|
116
|
+
export function setParams(params: unknown, key: string): void {
|
|
117
|
+
dispatch({ type: 'setParams', key, params });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const handle: ITabNavigatorHandle = { jumpTo, setParams };
|
|
121
|
+
|
|
122
|
+
// One emitter per route.key, created lazily and cached for the navigator's whole lifetime -
|
|
123
|
+
// mirrors Stack's own `emitters` map. This decouples emitter IDENTITY (stable, looked up by
|
|
124
|
+
// key, read below when a route's NavigationScope is built) from emit TIMING (must wait until
|
|
125
|
+
// the focused screen has actually mounted and subscribed).
|
|
126
|
+
const emitters = new Map<string, INavigationEmitter>();
|
|
127
|
+
function emitterFor(routeKey: string): INavigationEmitter {
|
|
128
|
+
let emitter = emitters.get(routeKey);
|
|
129
|
+
if (emitter === undefined) {
|
|
130
|
+
emitter = createNavigationEmitter();
|
|
131
|
+
emitters.set(routeKey, emitter);
|
|
132
|
+
}
|
|
133
|
+
return emitter;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Tab paints its own bar in pure JS - there is no native onAppear/onDisappear to hook (unlike
|
|
137
|
+
// Stack's RNSScreen), so focus/blur is synthesized here. Keyed on the route KEY (not the route
|
|
138
|
+
// object) so a setParams-only change doesn't spuriously re-fire focus/blur.
|
|
139
|
+
//
|
|
140
|
+
// The bookkeeping updates immediately, but the actual emit is deferred to `tick()` - Svelte's
|
|
141
|
+
// twin of Vue's nextTick, and needed for the same reason: the newly-focused screen's own
|
|
142
|
+
// `$effect`s (its useIsFocused/useFocusEffect subscriptions) are created during THIS flush,
|
|
143
|
+
// after this effect was, so emitting synchronously here would reach zero subscribers.
|
|
144
|
+
let previousFocusedKey: string | undefined;
|
|
145
|
+
$effect(() => {
|
|
146
|
+
const nextKey = state.routes[state.index]?.key;
|
|
147
|
+
const { blurKey, focusKey } = diffFocusedRoute(previousFocusedKey, nextKey);
|
|
148
|
+
if (blurKey === undefined && focusKey === undefined) return;
|
|
149
|
+
previousFocusedKey = nextKey;
|
|
150
|
+
void tick().then(() => {
|
|
151
|
+
if (blurKey !== undefined) {
|
|
152
|
+
dlog(`Tab: route "${blurKey}" blurred at t=${Date.now()}`);
|
|
153
|
+
emitterFor(blurKey).emit(NAVIGATION_EVENT_BLUR);
|
|
154
|
+
}
|
|
155
|
+
if (focusKey !== undefined) {
|
|
156
|
+
dlog(`Tab: route "${focusKey}" focused at t=${Date.now()}`);
|
|
157
|
+
emitterFor(focusKey).emit(NAVIGATION_EVENT_FOCUS);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
onDestroy(() => {
|
|
163
|
+
if (previousFocusedKey !== undefined) {
|
|
164
|
+
emitterFor(previousFocusedKey).emit(NAVIGATION_EVENT_BLUR);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
function resolveTabOptions(
|
|
169
|
+
entry: IRegisteredScreen<ITabScreenProps['options']> | undefined,
|
|
170
|
+
route: IRoute<unknown>,
|
|
171
|
+
): ITabOptions {
|
|
172
|
+
const own =
|
|
173
|
+
entry === undefined
|
|
174
|
+
? undefined
|
|
175
|
+
: typeof entry.options === 'function'
|
|
176
|
+
? entry.options({ route, navigation: handle })
|
|
177
|
+
: entry.options;
|
|
178
|
+
return { ...screenOptions, ...own };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const items = $derived.by<ITabBarItemView[]>(() =>
|
|
182
|
+
state.routes.map((route, index) => {
|
|
183
|
+
const entry = registry.get(route.name);
|
|
184
|
+
const focused = isFocusedRoute(index, state.index);
|
|
185
|
+
if (entry === undefined) {
|
|
186
|
+
dlog(`Tab: no screen registered for route name "${route.name}"`);
|
|
187
|
+
return { key: route.key, focused, label: route.name, passthrough: {} };
|
|
188
|
+
}
|
|
189
|
+
const options = resolveTabOptions(entry, route);
|
|
190
|
+
return {
|
|
191
|
+
key: route.key,
|
|
192
|
+
focused,
|
|
193
|
+
label: options.tabBarLabel ?? options.title ?? route.name,
|
|
194
|
+
icon: options.tabBarIcon,
|
|
195
|
+
badge: options.tabBarBadge,
|
|
196
|
+
activeTintColor: options.tabBarActiveTintColor,
|
|
197
|
+
inactiveTintColor: options.tabBarInactiveTintColor,
|
|
198
|
+
passthrough: {
|
|
199
|
+
onPress: () => jumpTo(route.name),
|
|
200
|
+
accessibilityRole: 'tab',
|
|
201
|
+
accessibilityState: { selected: focused },
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}),
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
const focusedRoute = $derived(state.routes[state.index]);
|
|
208
|
+
const focusedOptions = $derived(
|
|
209
|
+
focusedRoute === undefined
|
|
210
|
+
? screenOptions
|
|
211
|
+
: resolveTabOptions(registry.get(focusedRoute.name), focusedRoute),
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
const tabBar = $derived.by<IDescriptor>(() =>
|
|
215
|
+
renderTabBar({
|
|
216
|
+
items,
|
|
217
|
+
style: focusedOptions?.tabBarStyle,
|
|
218
|
+
passthrough: {},
|
|
219
|
+
}),
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
// Only the focused route's screen is ever mounted (unlike Stack, which keeps every pushed route
|
|
223
|
+
// alive), so a fresh NavigationScope per focus change is sufficient - the previous screen's
|
|
224
|
+
// whole subtree (and any listeners it registered) is torn down by an ordinary unmount when
|
|
225
|
+
// focus moves on.
|
|
226
|
+
const focusedScreen = $derived.by<
|
|
227
|
+
{ scope: INavigationScopeValue; component: Component } | undefined
|
|
228
|
+
>(() => {
|
|
229
|
+
if (focusedRoute === undefined) return undefined;
|
|
230
|
+
const entry = registry.get(focusedRoute.name);
|
|
231
|
+
if (entry === undefined) return undefined;
|
|
232
|
+
return {
|
|
233
|
+
component: entry.component,
|
|
234
|
+
scope: {
|
|
235
|
+
route: focusedRoute,
|
|
236
|
+
navigation: handle,
|
|
237
|
+
emitter: emitterFor(focusedRoute.key),
|
|
238
|
+
parent: parentScope?.current,
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// The bar's ROOT stays a literal template tag (so `bind:this` has a statically known tag) and
|
|
244
|
+
// only its children go through the Descriptor bridge - the uniform shape every Svelte component
|
|
245
|
+
// consuming a `render-*.ts` uses (svelte-adapter-dom-shim skill §19). The item count varies with
|
|
246
|
+
// the registry, hence the shape-change-tolerant wrapper rather than the raw bridge.
|
|
247
|
+
let tabBarHost = $state.raw<ShimElement | null>(null);
|
|
248
|
+
const syncTabBarChildren = createDescriptorSubtreeSync();
|
|
249
|
+
$effect(() => {
|
|
250
|
+
const host = tabBarHost;
|
|
251
|
+
const barChildren = tabBar.children;
|
|
252
|
+
syncTabBarChildren(host, barChildren);
|
|
253
|
+
});
|
|
254
|
+
</script>
|
|
255
|
+
|
|
256
|
+
<symbiote-view p={TAB_ROOT_PROPS}>
|
|
257
|
+
<symbiote-text p={SCREEN_REGISTRY_HOST_PROPS}>
|
|
258
|
+
{@render children?.()}
|
|
259
|
+
</symbiote-text>
|
|
260
|
+
<symbiote-view p={TAB_CONTENT_PROPS}>
|
|
261
|
+
{#if focusedScreen !== undefined}
|
|
262
|
+
{@const FocusedComponent = focusedScreen.component}
|
|
263
|
+
<NavigationScope value={focusedScreen.scope}>
|
|
264
|
+
<FocusedComponent />
|
|
265
|
+
</NavigationScope>
|
|
266
|
+
{/if}
|
|
267
|
+
</symbiote-view>
|
|
268
|
+
<symbiote-view p={tabBar.props} bind:this={tabBarHost} />
|
|
269
|
+
</symbiote-view>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Component } from 'svelte';
|
|
2
|
+
import type { ITabProps } from './tab-props';
|
|
3
|
+
declare const Index: Component<ITabProps, {
|
|
4
|
+
jumpTo: (name: string, params?: unknown) => void;
|
|
5
|
+
setParams: (params: unknown, key: string) => void;
|
|
6
|
+
}, "">;
|
|
7
|
+
type Index = ReturnType<typeof Index>;
|
|
8
|
+
export default Index;
|
|
@@ -8,6 +8,10 @@ export function useNavigation() {
|
|
|
8
8
|
const scope = requireNavigationScope('useNavigation');
|
|
9
9
|
return computed(() => {
|
|
10
10
|
const { navigation, emitter, parent } = scope.value;
|
|
11
|
-
return {
|
|
11
|
+
return {
|
|
12
|
+
...navigation,
|
|
13
|
+
addListener: emitter.addListener,
|
|
14
|
+
getParent: () => parent?.navigation,
|
|
15
|
+
};
|
|
12
16
|
});
|
|
13
17
|
}
|
|
@@ -35,7 +35,12 @@ function asBoolean(value) {
|
|
|
35
35
|
function isStyleProp(value) {
|
|
36
36
|
return isRecord(value);
|
|
37
37
|
}
|
|
38
|
-
const DRAWER_TYPES = [
|
|
38
|
+
const DRAWER_TYPES = [
|
|
39
|
+
'front',
|
|
40
|
+
'back',
|
|
41
|
+
'slide',
|
|
42
|
+
'permanent',
|
|
43
|
+
];
|
|
39
44
|
// .some() itself doesn't narrow `value`'s type back at the call site - the `value is IDrawerType`
|
|
40
45
|
// predicate on THIS function is what lets asDrawerType's ternary return `value` typed, mirroring
|
|
41
46
|
// Modal's isOrientation guard (adapters/vue/src/components/modal.ts).
|
|
@@ -47,7 +52,8 @@ function asDrawerType(value) {
|
|
|
47
52
|
}
|
|
48
53
|
const DRAWER_POSITIONS = ['left', 'right'];
|
|
49
54
|
function isDrawerPosition(value) {
|
|
50
|
-
return typeof value === 'string' &&
|
|
55
|
+
return (typeof value === 'string' &&
|
|
56
|
+
DRAWER_POSITIONS.some(position => position === value));
|
|
51
57
|
}
|
|
52
58
|
function asDrawerPosition(value) {
|
|
53
59
|
return isDrawerPosition(value) ? value : undefined;
|
|
@@ -55,7 +61,9 @@ function asDrawerPosition(value) {
|
|
|
55
61
|
function asComponent(value) {
|
|
56
62
|
if (typeof value === 'function')
|
|
57
63
|
return value;
|
|
58
|
-
return isRecord(value)
|
|
64
|
+
return isRecord(value)
|
|
65
|
+
? value
|
|
66
|
+
: undefined;
|
|
59
67
|
}
|
|
60
68
|
// IDrawerScreenOptions carries only `title`/`drawerLabel`, small enough for direct field guards -
|
|
61
69
|
// kept as a single cast-at-the-edge helper anyway for symmetry with stack.ts/tabs.ts's
|
|
@@ -166,13 +174,14 @@ const DrawerImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose })
|
|
|
166
174
|
dispatch({ type: 'toggleDrawer' });
|
|
167
175
|
},
|
|
168
176
|
jumpTo: (name) => {
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
//
|
|
177
|
+
// Both sides of the dispatch, because an unregistered name is a documented reducer no-op
|
|
178
|
+
// that hands the SAME state back: animating off the pre-dispatch snapshot alone would
|
|
179
|
+
// slide the panel shut while the router still says isOpen. The ref's `.value` mutates
|
|
180
|
+
// SYNCHRONOUSLY inside dispatch (unlike React's isOpenRef, refreshed only at the top of
|
|
181
|
+
// the next render), so the second read is already the reducer's own answer.
|
|
173
182
|
const wasOpen = state.value.isOpen;
|
|
174
183
|
dispatch({ type: 'jumpTo', name });
|
|
175
|
-
if (wasOpen)
|
|
184
|
+
if (wasOpen && !state.value.isOpen)
|
|
176
185
|
animateProgressTo(false);
|
|
177
186
|
},
|
|
178
187
|
};
|
|
@@ -256,7 +265,9 @@ const DrawerImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose })
|
|
|
256
265
|
return () => {
|
|
257
266
|
const registry = collectRegistry(slots.default?.() ?? []);
|
|
258
267
|
const options = currentOptions();
|
|
259
|
-
const drawerStyle = isStyleProp(attrs.drawerStyle)
|
|
268
|
+
const drawerStyle = isStyleProp(attrs.drawerStyle)
|
|
269
|
+
? attrs.drawerStyle
|
|
270
|
+
: undefined;
|
|
260
271
|
if (registry.size === 0)
|
|
261
272
|
dlog('Drawer: no <Drawer.Screen> children registered');
|
|
262
273
|
const animated = isDrawerAnimated(options);
|
|
@@ -283,7 +294,9 @@ const DrawerImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose })
|
|
|
283
294
|
? progress.interpolate(overlayInterpolation.translateX)
|
|
284
295
|
: undefined;
|
|
285
296
|
const focusedRoute = state.value.routes[state.value.index];
|
|
286
|
-
const focusedEntry = focusedRoute
|
|
297
|
+
const focusedEntry = focusedRoute
|
|
298
|
+
? registry.get(focusedRoute.name)
|
|
299
|
+
: undefined;
|
|
287
300
|
if (focusedRoute && !focusedEntry) {
|
|
288
301
|
dlog(`Drawer: no screen registered for route name "${focusedRoute.name}"`);
|
|
289
302
|
}
|
|
@@ -306,11 +319,18 @@ const DrawerImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose })
|
|
|
306
319
|
if (entry === undefined)
|
|
307
320
|
continue;
|
|
308
321
|
descriptors[route.key] = {
|
|
309
|
-
options: resolveDrawerScreenOptions(entry, {
|
|
322
|
+
options: resolveDrawerScreenOptions(entry, {
|
|
323
|
+
route,
|
|
324
|
+
navigation: handle,
|
|
325
|
+
}),
|
|
310
326
|
navigation: handle,
|
|
311
327
|
};
|
|
312
328
|
}
|
|
313
|
-
const drawerContent = slots.drawerContent?.({
|
|
329
|
+
const drawerContent = slots.drawerContent?.({
|
|
330
|
+
state: state.value,
|
|
331
|
+
descriptors,
|
|
332
|
+
navigation: handle,
|
|
333
|
+
}) ?? null;
|
|
314
334
|
const root = renderDrawer({
|
|
315
335
|
overlayColor: asString(attrs.overlayColor) ?? DRAWER_DEFAULT_OVERLAY_COLOR,
|
|
316
336
|
drawerStyle,
|
|
@@ -348,7 +368,10 @@ const DrawerImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose })
|
|
|
348
368
|
: { transform: [{ translateX: contentTranslateX }] },
|
|
349
369
|
overlay: overlayOpacity === undefined
|
|
350
370
|
? undefined
|
|
351
|
-
: {
|
|
371
|
+
: {
|
|
372
|
+
opacity: overlayOpacity,
|
|
373
|
+
transform: [{ translateX: overlayTranslateX }],
|
|
374
|
+
},
|
|
352
375
|
panel: panelTranslateX === undefined
|
|
353
376
|
? undefined
|
|
354
377
|
: { transform: [{ translateX: panelTranslateX }] },
|
package/build/vue/index.d.ts
CHANGED
|
@@ -3,10 +3,10 @@ export { Stack } from './stack';
|
|
|
3
3
|
export type { INavigatorHandle, IStackProps } from './stack';
|
|
4
4
|
export { Screen } from './screen';
|
|
5
5
|
export type { IScreenOptionsArgs, IScreenOptionsResolver, IScreenProps, IVueScreenOptions, IVueSearchBarOptions, } from './screen';
|
|
6
|
-
export type { IScreenOptions, IStackAnimation, IStackPresentation } from '../core';
|
|
6
|
+
export type { IScreenOptions, IStackAnimation, IStackPresentation, } from '../core';
|
|
7
7
|
export { useNavigation, useStackNavigation, useTabNavigation, useDrawerNavigation, useRoute, useIsFocused, useFocusEffect, useNavigationState, } from './composables';
|
|
8
8
|
export type { INavigationHandle, IStackNavigationHandle, ITabNavigationHandle, IDrawerNavigationHandle, } from './composables';
|
|
9
|
-
export type { INavigationScopeValue, IAnyNavigatorHandle } from './navigation-context';
|
|
9
|
+
export type { INavigationScopeValue, IAnyNavigatorHandle, } from './navigation-context';
|
|
10
10
|
export { useLinkingIntegration } from './linking';
|
|
11
11
|
export type { ILinkingConfig, IScreenLinkingConfig } from '../core';
|
|
12
12
|
export { Tab } from './tabs';
|
|
@@ -18,4 +18,4 @@ export { Drawer } from './drawer';
|
|
|
18
18
|
export type { IDrawerContentSlotProps, IDrawerNavigatorHandle, IDrawerProps, IDrawerDescriptorMap, } from './drawer';
|
|
19
19
|
export { DrawerScreen } from './drawer-screen';
|
|
20
20
|
export type { IDrawerScreenOptionsArgs, IDrawerScreenOptionsResolver, IDrawerScreenProps, } from './drawer-screen';
|
|
21
|
-
export type { IDrawerOptions, IDrawerScreenOptions, IDrawerType, IDrawerPosition } from '../core';
|
|
21
|
+
export type { IDrawerOptions, IDrawerScreenOptions, IDrawerType, IDrawerPosition, } from '../core';
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
// into the value it provides to its own screens, forming a linked list - this is what lets a
|
|
27
27
|
// screen nested inside e.g. a Stack-screen-renders-a-Tab composition reach the enclosing Stack via
|
|
28
28
|
// useNavigation().getParent().
|
|
29
|
-
import { defineComponent, inject, provide, shallowRef } from '@vue/runtime-core';
|
|
29
|
+
import { defineComponent, inject, provide, shallowRef, } from '@vue/runtime-core';
|
|
30
30
|
const NAVIGATION_SCOPE_KEY = Symbol('navigation-scope');
|
|
31
31
|
// The setter half of the pair - writes the CURRENT route's scope onto the shared key. Called only
|
|
32
32
|
// from NavigationScope's own setup below (never directly by app code).
|
package/build/vue/stack/index.js
CHANGED
|
@@ -11,14 +11,16 @@
|
|
|
11
11
|
// registers. See CLAUDE.md <third_party_rn_packages_are_react_only>.
|
|
12
12
|
import { defineComponent, h, shallowRef, useId } from '@vue/runtime-core';
|
|
13
13
|
import { descriptorToVue, normalizeVueAttrs } from '@symbiote-native/vue';
|
|
14
|
-
import { Platform, debugNodeId, dlog, isSymbioteNode } from '@symbiote-native/engine';
|
|
15
|
-
import { NAVIGATION_EVENT_BLUR, NAVIGATION_EVENT_FOCUS, NAVIGATION_EVENT_STATE, RNS_SCREEN_CONTENT_WRAPPER_VIEW_NAME, RNS_SCREEN_STACK_VIEW_NAME, RNS_SCREEN_VIEW_NAME, SCREEN_ON_APPEAR, SCREEN_ON_DISAPPEAR, SCREEN_ON_DISMISSED, SCREEN_ON_HEADER_BACK_BUTTON_CLICKED, SCREEN_ON_WILL_APPEAR, SCREEN_ON_WILL_DISAPPEAR, STACK_ON_FINISH_TRANSITIONING, buildSearchBarHandle, buildSearchBarPassthrough, computeActivityState, createInitialNavigatorState, createNavigationEmitter, isRecord, navigatorReducer, resolveScreenRenderPlan, resolveStackProps, } from '../../core/index.js';
|
|
14
|
+
import { Platform, debugNodeId, dlog, isSymbioteNode, } from '@symbiote-native/engine';
|
|
15
|
+
import { NAVIGATION_EVENT_BLUR, NAVIGATION_EVENT_FOCUS, NAVIGATION_EVENT_STATE, RNS_SCREEN_CONTENT_WRAPPER_VIEW_NAME, RNS_SCREEN_STACK_VIEW_NAME, RNS_SCREEN_VIEW_NAME, SCREEN_ON_APPEAR, SCREEN_ON_DISAPPEAR, SCREEN_ON_DISMISSED, SCREEN_ON_HEADER_BACK_BUTTON_CLICKED, SCREEN_ON_WILL_APPEAR, SCREEN_ON_WILL_DISAPPEAR, STACK_ON_FINISH_TRANSITIONING, buildSearchBarHandle, buildSearchBarPassthrough, computeActivityState, createInitialNavigatorState, createNavigationEmitter, isRecord, navigatorReducer, reconcileStackRoutes, resolveScreenRenderPlan, resolveStackProps, } from '../../core/index.js';
|
|
16
16
|
import { NavigationScope, injectNavigationScope } from '../navigation-context.js';
|
|
17
17
|
import { Screen } from '../screen.js';
|
|
18
18
|
// backTitleVisible defaults to `true` on both platforms per the codegen spec's own default
|
|
19
19
|
// (CT.WithDefault<boolean, 'true'>) - no ios/android divergence in v1 scope, so a single constant
|
|
20
20
|
// stands in for the per-platform injection point ISliderPlatform-style adapters use elsewhere.
|
|
21
|
-
const NAVIGATOR_PLATFORM = {
|
|
21
|
+
const NAVIGATOR_PLATFORM = {
|
|
22
|
+
defaultHeaderBackTitleVisible: true,
|
|
23
|
+
};
|
|
22
24
|
function asString(value) {
|
|
23
25
|
return typeof value === 'string' ? value : undefined;
|
|
24
26
|
}
|
|
@@ -58,7 +60,9 @@ function collectRegistry(vnodes) {
|
|
|
58
60
|
return registry;
|
|
59
61
|
}
|
|
60
62
|
function resolveScreenOptions(entry, screenOptionsArgs, screenOptions) {
|
|
61
|
-
const own = typeof entry.options === 'function'
|
|
63
|
+
const own = typeof entry.options === 'function'
|
|
64
|
+
? entry.options(screenOptionsArgs)
|
|
65
|
+
: entry.options;
|
|
62
66
|
return { ...screenOptions, ...own };
|
|
63
67
|
}
|
|
64
68
|
const StackImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose }) => {
|
|
@@ -106,8 +110,20 @@ const StackImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose }) =
|
|
|
106
110
|
initialState = createInitialNavigatorState(createRoute(initialRouteName, initialRegistry.get(initialRouteName)?.initialParams));
|
|
107
111
|
}
|
|
108
112
|
const state = shallowRef(initialState);
|
|
113
|
+
// The screen names the render pass below last saw in the slot. A PLAIN local, not a ref: it is
|
|
114
|
+
// a cache of what was just rendered, never a reactive source, so refreshing it from inside the
|
|
115
|
+
// render closure is not a state write during render (which would re-trigger the very render
|
|
116
|
+
// effect that produced it).
|
|
117
|
+
let registeredNames = [];
|
|
118
|
+
// A <Stack.Screen> marker can vanish from the slot while its route is still in the pushed
|
|
119
|
+
// history, which would leave that entry with nothing to render (reconcileStackRoutes' header).
|
|
120
|
+
// Reconciling on READ rather than writing the ref keeps the repair out of the render pass; the
|
|
121
|
+
// dispatch below then persists it, so the next push builds on the pruned history.
|
|
122
|
+
function currentState() {
|
|
123
|
+
return reconcileStackRoutes(state.value, registeredNames);
|
|
124
|
+
}
|
|
109
125
|
function dispatch(action) {
|
|
110
|
-
state.value = navigatorReducer(
|
|
126
|
+
state.value = navigatorReducer(currentState(), action);
|
|
111
127
|
}
|
|
112
128
|
const handle = {
|
|
113
129
|
push: (name, params) => dispatch({ type: 'push', route: createRoute(name, params) }),
|
|
@@ -117,7 +133,7 @@ const StackImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose }) =
|
|
|
117
133
|
replace: (name, params) => dispatch({ type: 'replace', route: createRoute(name, params) }),
|
|
118
134
|
setParams: (params, key) => dispatch({ type: 'setParams', key, params }),
|
|
119
135
|
reset: nextState => dispatch({ type: 'reset', state: nextState }),
|
|
120
|
-
canGoBack: () =>
|
|
136
|
+
canGoBack: () => currentState().routes.length > 1,
|
|
121
137
|
};
|
|
122
138
|
expose(handle);
|
|
123
139
|
// Broadcasts the router state to every still-live route's emitter (useNavigationState's
|
|
@@ -126,18 +142,20 @@ const StackImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose }) =
|
|
|
126
142
|
// useEffect, which must be deferred past render to avoid a descendant setState-during-render):
|
|
127
143
|
// Vue's provide/inject + shallowRef.value writes are safe to perform directly inside the
|
|
128
144
|
// render closure below, so the broadcast happens right there, once per render.
|
|
129
|
-
function broadcastState() {
|
|
130
|
-
for (const route of
|
|
131
|
-
emitterFor(route.key).emit(NAVIGATION_EVENT_STATE,
|
|
145
|
+
function broadcastState(current) {
|
|
146
|
+
for (const route of current.routes) {
|
|
147
|
+
emitterFor(route.key).emit(NAVIGATION_EVENT_STATE, current);
|
|
132
148
|
}
|
|
133
149
|
for (const routeKey of emitters.keys()) {
|
|
134
|
-
if (!
|
|
150
|
+
if (!current.routes.some(route => route.key === routeKey))
|
|
135
151
|
emitters.delete(routeKey);
|
|
136
152
|
}
|
|
137
153
|
}
|
|
138
154
|
return () => {
|
|
139
|
-
broadcastState();
|
|
140
155
|
const registry = collectRegistry(slots.default?.() ?? []);
|
|
156
|
+
registeredNames = [...registry.keys()];
|
|
157
|
+
const current = currentState();
|
|
158
|
+
broadcastState(current);
|
|
141
159
|
const screenOptions = asScreenOptions(attrs.screenOptions);
|
|
142
160
|
// Investigation instrumentation (flicker-on-focus bug): STACK_ON_FINISH_TRANSITIONING is
|
|
143
161
|
// the native signal that the WHOLE push/pop animation has finished (as opposed to
|
|
@@ -149,26 +167,29 @@ const StackImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose }) =
|
|
|
149
167
|
[STACK_ON_FINISH_TRANSITIONING]: () => dlog(`Stack: onFinishTransitioning at t=${Date.now()}`),
|
|
150
168
|
},
|
|
151
169
|
});
|
|
152
|
-
const children =
|
|
170
|
+
const children = current.routes.map((route, index) => {
|
|
153
171
|
const entry = registry.get(route.name);
|
|
154
172
|
if (entry === undefined) {
|
|
155
173
|
dlog(`Stack: no screen registered for route name "${route.name}"`);
|
|
156
174
|
return null;
|
|
157
175
|
}
|
|
158
|
-
const screenOptionsArgs = {
|
|
176
|
+
const screenOptionsArgs = {
|
|
177
|
+
route,
|
|
178
|
+
navigation: handle,
|
|
179
|
+
};
|
|
159
180
|
const mergedOptions = resolveScreenOptions(entry, screenOptionsArgs, screenOptions);
|
|
160
|
-
const activityState = computeActivityState(index,
|
|
181
|
+
const activityState = computeActivityState(index, current.routes.length);
|
|
161
182
|
// Investigation instrumentation (flicker-on-focus bug): fires on EVERY Stack render, not
|
|
162
183
|
// just on transitions, so the log stream shows whether a route's activityState/index ever
|
|
163
184
|
// changes outside of a push/pop dispatch. Kept behind DEBUG, never removed.
|
|
164
|
-
dlog(`Stack: render route "${route.name}" index=${index}/${
|
|
185
|
+
dlog(`Stack: render route "${route.name}" index=${index}/${current.routes.length - 1} ` +
|
|
165
186
|
`activityState=${activityState} at t=${Date.now()}`);
|
|
166
187
|
const routeEmitter = emitterFor(route.key);
|
|
167
188
|
const searchBarOptions = mergedOptions.headerSearchBarOptions;
|
|
168
189
|
const plan = resolveScreenRenderPlan({
|
|
169
190
|
screenId: route.key,
|
|
170
191
|
index,
|
|
171
|
-
routeCount:
|
|
192
|
+
routeCount: current.routes.length,
|
|
172
193
|
options: mergedOptions,
|
|
173
194
|
platform: NAVIGATOR_PLATFORM,
|
|
174
195
|
isAndroid: Platform.OS === 'android',
|
|
@@ -210,7 +231,8 @@ const StackImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose }) =
|
|
|
210
231
|
const appRef = searchBarOptions.ref;
|
|
211
232
|
if (appRef === undefined)
|
|
212
233
|
return;
|
|
213
|
-
appRef.value =
|
|
234
|
+
appRef.value =
|
|
235
|
+
node === null ? null : buildSearchBarHandle(() => node);
|
|
214
236
|
},
|
|
215
237
|
}
|
|
216
238
|
: undefined,
|
|
@@ -265,7 +287,10 @@ const StackImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose }) =
|
|
|
265
287
|
// RNSScreen.mm treats an unset/inactive nested screen as not yet pushed,
|
|
266
288
|
// leaving it parked at its pre-push transition position (off past the bottom
|
|
267
289
|
// edge) instead of its real, presented frame.
|
|
268
|
-
{
|
|
290
|
+
{
|
|
291
|
+
style: plan.innerScreenStyle,
|
|
292
|
+
activityState: plan.activityState,
|
|
293
|
+
}, [descriptorToVue(plan.headerConfig), content]),
|
|
269
294
|
]),
|
|
270
295
|
])
|
|
271
296
|
: h(plan.screenViewName, { key: route.key, ...plan.screenProps }, [
|