@symbiote-native/navigation 2.1.0 → 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 +23 -9
- 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.js +20 -6
- package/build/angular/tabs/index.js +21 -7
- package/build/core/drawer-options/index.js +19 -5
- package/build/core/drawer-router-state/index.js +8 -2
- package/build/core/guards.js +3 -1
- package/build/core/header-bar-buttons.js +14 -4
- package/build/core/index.d.ts +4 -4
- package/build/core/index.js +2 -2
- package/build/core/navigator-state/index.js +12 -4
- package/build/core/render-drawer.js +4 -1
- package/build/core/render-stack.js +13 -4
- package/build/core/screen-options.js +1 -1
- package/build/core/search-bar-commands.js +1 -1
- package/build/core/state-persistence/index.js +6 -2
- package/build/core/tab-router-state/index.js +8 -2
- package/build/react/drawer/index.js +36 -13
- package/build/react/index.d.ts +7 -7
- package/build/react/index.js +2 -2
- package/build/react/stack/index.js +34 -8
- package/build/react/tabs/index.js +14 -4
- package/build/solid/drawer/index.d.ts +22 -0
- package/build/solid/drawer/index.js +394 -0
- package/build/solid/drawer-screen-props.d.ts +13 -0
- package/build/solid/drawer-screen-props.js +2 -0
- package/build/solid/host.d.ts +2 -0
- package/build/solid/host.js +23 -0
- package/build/solid/index.d.ts +19 -0
- package/build/solid/index.js +24 -0
- package/build/solid/linking.d.ts +3 -0
- package/build/solid/linking.js +55 -0
- package/build/solid/navigation-context.d.ts +13 -0
- package/build/solid/navigation-context.js +37 -0
- package/build/solid/primitives/create-focus-effect.d.ts +1 -0
- package/build/solid/primitives/create-focus-effect.js +30 -0
- package/build/solid/primitives/create-is-focused.d.ts +2 -0
- package/build/solid/primitives/create-is-focused.js +31 -0
- package/build/solid/primitives/create-navigation-state.d.ts +3 -0
- package/build/solid/primitives/create-navigation-state.js +30 -0
- package/build/solid/primitives/index.d.ts +12 -0
- package/build/solid/primitives/index.js +8 -0
- package/build/solid/primitives/use-drawer-navigation.d.ts +5 -0
- package/build/solid/primitives/use-drawer-navigation.js +15 -0
- package/build/solid/primitives/use-navigation.d.ts +8 -0
- package/build/solid/primitives/use-navigation.js +23 -0
- package/build/solid/primitives/use-route.d.ts +3 -0
- package/build/solid/primitives/use-route.js +14 -0
- package/build/solid/primitives/use-stack-navigation.d.ts +5 -0
- package/build/solid/primitives/use-stack-navigation.js +19 -0
- package/build/solid/primitives/use-tab-navigation.d.ts +5 -0
- package/build/solid/primitives/use-tab-navigation.js +15 -0
- package/build/solid/screen-props.d.ts +19 -0
- package/build/solid/screen-props.js +9 -0
- package/build/solid/screen-registry.d.ts +31 -0
- package/build/solid/screen-registry.js +86 -0
- package/build/solid/screen.d.ts +6 -0
- package/build/solid/screen.js +69 -0
- package/build/solid/stack/index.d.ts +15 -0
- package/build/solid/stack/index.js +348 -0
- package/build/solid/tab-screen-props.d.ts +13 -0
- package/build/solid/tab-screen-props.js +3 -0
- package/build/solid/tabs/index.d.ts +14 -0
- package/build/solid/tabs/index.js +228 -0
- package/build/svelte/descriptor-subtree.js +3 -1
- package/build/svelte/drawer/index.svelte +163 -25
- package/build/svelte/drawer-screen.svelte +2 -1
- package/build/svelte/index.d.ts +3 -3
- package/build/svelte/navigation-scope.svelte +4 -1
- package/build/svelte/runes/use-navigation.svelte.js +5 -1
- package/build/svelte/stack/index.svelte +43 -6
- package/build/svelte/stack/stack-screen.svelte +66 -9
- package/build/svelte/tabs/index.svelte +42 -20
- package/build/vue/composables/use-navigation.js +5 -1
- package/build/vue/drawer/index.js +30 -8
- package/build/vue/index.d.ts +3 -3
- package/build/vue/navigation-context.js +1 -1
- package/build/vue/stack/index.js +17 -6
- package/build/vue/tabs/index.js +18 -5
- package/build-ngc/angular/drawer/index.js +31 -11
- package/build-ngc/angular/drawer/index.js.map +1 -1
- package/build-ngc/angular/index.d.ts +4 -4
- package/build-ngc/angular/index.js.map +1 -1
- package/build-ngc/angular/injectors/inject-focus-effect.js.map +1 -1
- package/build-ngc/angular/injectors/inject-is-focused.js.map +1 -1
- package/build-ngc/angular/injectors/inject-navigation-state.js.map +1 -1
- package/build-ngc/angular/injectors/inject-navigation.js +1 -1
- package/build-ngc/angular/injectors/inject-navigation.js.map +1 -1
- package/build-ngc/angular/linking.js.map +1 -1
- package/build-ngc/angular/navigation-context.service.js.map +1 -1
- package/build-ngc/angular/navigation-scope.directive.js +1 -1
- package/build-ngc/angular/navigation-scope.directive.js.map +1 -1
- package/build-ngc/angular/screen.directive.js.map +1 -1
- package/build-ngc/angular/search-bar-ref.directive.js +1 -1
- package/build-ngc/angular/search-bar-ref.directive.js.map +1 -1
- package/build-ngc/angular/stack/index.js +26 -8
- package/build-ngc/angular/stack/index.js.map +1 -1
- package/build-ngc/angular/tab-screen.directive.js.map +1 -1
- package/build-ngc/angular/tabs/index.js +21 -7
- package/build-ngc/angular/tabs/index.js.map +1 -1
- package/build-ngc/core/constants.js.map +1 -1
- package/build-ngc/core/drawer-options/index.js +19 -5
- package/build-ngc/core/drawer-options/index.js.map +1 -1
- package/build-ngc/core/drawer-router-state/index.js +8 -2
- package/build-ngc/core/drawer-router-state/index.js.map +1 -1
- package/build-ngc/core/guards.js +3 -1
- package/build-ngc/core/guards.js.map +1 -1
- package/build-ngc/core/header-bar-buttons.js +14 -4
- package/build-ngc/core/header-bar-buttons.js.map +1 -1
- package/build-ngc/core/index.d.ts +4 -4
- package/build-ngc/core/index.js +2 -2
- package/build-ngc/core/index.js.map +1 -1
- package/build-ngc/core/linking-config/index.js.map +1 -1
- package/build-ngc/core/navigation-events/index.js.map +1 -1
- package/build-ngc/core/navigator-handles/index.js.map +1 -1
- package/build-ngc/core/navigator-state/index.js +12 -4
- package/build-ngc/core/navigator-state/index.js.map +1 -1
- package/build-ngc/core/render-drawer.js +4 -1
- package/build-ngc/core/render-drawer.js.map +1 -1
- package/build-ngc/core/render-stack.js +13 -4
- package/build-ngc/core/render-stack.js.map +1 -1
- package/build-ngc/core/render-tabs.js.map +1 -1
- package/build-ngc/core/screen-options.js +1 -1
- package/build-ngc/core/screen-options.js.map +1 -1
- package/build-ngc/core/search-bar-commands.js +1 -1
- package/build-ngc/core/search-bar-commands.js.map +1 -1
- package/build-ngc/core/stack-router-state/index.js.map +1 -1
- package/build-ngc/core/state-persistence/index.js +6 -2
- package/build-ngc/core/state-persistence/index.js.map +1 -1
- package/build-ngc/core/tab-router-state/index.js +8 -2
- package/build-ngc/core/tab-router-state/index.js.map +1 -1
- package/package.json +23 -9
- package/src/angular/drawer/index.ts +69 -25
- package/src/angular/index.ts +20 -4
- package/src/angular/injectors/inject-focus-effect.ts +8 -2
- package/src/angular/injectors/inject-is-focused.ts +6 -4
- package/src/angular/injectors/inject-navigation-state.ts +7 -4
- package/src/angular/injectors/inject-navigation.ts +12 -3
- package/src/angular/linking.ts +4 -1
- package/src/angular/navigation-context.service.ts +5 -2
- package/src/angular/navigation-scope.directive.ts +6 -2
- package/src/angular/screen.directive.ts +13 -3
- package/src/angular/search-bar-ref.directive.ts +10 -2
- package/src/angular/stack/index.ts +68 -22
- package/src/angular/tab-screen.directive.ts +3 -1
- package/src/angular/tabs/index.ts +36 -12
- package/src/core/constants.ts +6 -3
- package/src/core/drawer-options/index.ts +40 -13
- package/src/core/drawer-router-state/index.ts +11 -3
- package/src/core/guards.ts +3 -1
- package/src/core/header-bar-buttons.ts +41 -12
- package/src/core/index.ts +18 -4
- package/src/core/linking-config/index.ts +31 -10
- package/src/core/navigation-events/index.ts +8 -2
- package/src/core/navigator-handles/index.ts +8 -3
- package/src/core/navigator-props.ts +13 -4
- package/src/core/navigator-state/index.ts +24 -7
- package/src/core/render-drawer.ts +24 -9
- package/src/core/render-stack.ts +52 -13
- package/src/core/render-tabs.ts +24 -6
- package/src/core/screen-options.ts +15 -5
- package/src/core/search-bar-commands.ts +12 -3
- package/src/core/stack-router-state/index.ts +5 -2
- package/src/core/state-persistence/index.ts +13 -3
- package/src/core/tab-router-state/index.ts +10 -3
- package/src/react/drawer/index.ts +408 -344
- package/src/react/drawer-screen.ts +8 -2
- package/src/react/hooks/use-focus-effect.ts +8 -2
- package/src/react/hooks/use-is-focused.ts +6 -2
- package/src/react/hooks/use-navigation.ts +8 -2
- package/src/react/index.ts +37 -7
- package/src/react/linking.ts +4 -1
- package/src/react/navigation-context.ts +6 -2
- package/src/react/screen.ts +12 -3
- package/src/react/stack/index.ts +294 -231
- package/src/react/tab-screen.ts +3 -1
- package/src/react/tabs/index.ts +188 -158
- package/src/solid/drawer/index.ts +570 -0
- package/src/solid/drawer-screen-props.ts +24 -0
- package/src/solid/host.ts +29 -0
- package/src/solid/index.ts +87 -0
- package/src/solid/linking.ts +74 -0
- package/src/solid/navigation-context.ts +56 -0
- package/src/solid/primitives/create-focus-effect.ts +41 -0
- package/src/solid/primitives/create-is-focused.ts +41 -0
- package/src/solid/primitives/create-navigation-state.ts +44 -0
- package/src/solid/primitives/index.ts +12 -0
- package/src/solid/primitives/use-drawer-navigation.ts +25 -0
- package/src/solid/primitives/use-navigation.ts +44 -0
- package/src/solid/primitives/use-route.ts +18 -0
- package/src/solid/primitives/use-stack-navigation.ts +29 -0
- package/src/solid/primitives/use-tab-navigation.ts +25 -0
- package/src/solid/screen-props.ts +51 -0
- package/src/solid/screen-registry.ts +159 -0
- package/src/solid/screen.ts +80 -0
- package/src/solid/stack/index.ts +528 -0
- package/src/solid/tab-screen-props.ts +21 -0
- package/src/solid/tabs/index.ts +327 -0
- package/src/svelte/attachments.ts +3 -1
- package/src/svelte/descriptor-subtree.ts +3 -1
- package/src/svelte/drawer/index.svelte +163 -25
- package/src/svelte/drawer-screen-props.ts +8 -2
- package/src/svelte/drawer-screen.svelte +2 -1
- package/src/svelte/fabric-tree.test-helper.ts +37 -3
- package/src/svelte/index.ts +15 -3
- package/src/svelte/linking.svelte.ts +11 -5
- package/src/svelte/navigation-scope.svelte +4 -1
- package/src/svelte/runes/use-drawer-navigation.svelte.ts +3 -1
- package/src/svelte/runes/use-focus-effect.svelte.ts +8 -2
- package/src/svelte/runes/use-navigation-state.svelte.ts +3 -1
- package/src/svelte/runes/use-navigation.svelte.ts +13 -3
- package/src/svelte/runes/use-stack-navigation.svelte.ts +3 -1
- package/src/svelte/screen-props.ts +7 -2
- package/src/svelte/screen-registry.ts +9 -3
- package/src/svelte/stack/index.svelte +43 -6
- package/src/svelte/stack/stack-screen.svelte +66 -9
- package/src/svelte/svelte-compile.test-helper.ts +16 -12
- package/src/svelte/tab-screen-props.ts +3 -1
- package/src/svelte/tabs/index.svelte +42 -20
- package/src/vue/composables/use-navigation-state.ts +7 -4
- package/src/vue/composables/use-navigation.ts +13 -3
- package/src/vue/drawer/index.ts +122 -36
- package/src/vue/drawer-screen.ts +12 -5
- package/src/vue/index.ts +15 -3
- package/src/vue/linking.ts +7 -2
- package/src/vue/navigation-context.ts +17 -5
- package/src/vue/screen.ts +7 -2
- package/src/vue/stack/index.ts +82 -34
- package/src/vue/tab-screen.ts +3 -1
- package/src/vue/tabs/index.ts +51 -13
package/src/angular/index.ts
CHANGED
|
@@ -15,7 +15,11 @@ export type {
|
|
|
15
15
|
IScreenOptionsArgs,
|
|
16
16
|
IScreenOptionsResolver,
|
|
17
17
|
} from './screen.directive';
|
|
18
|
-
export type {
|
|
18
|
+
export type {
|
|
19
|
+
IScreenOptions,
|
|
20
|
+
IStackAnimation,
|
|
21
|
+
IStackPresentation,
|
|
22
|
+
} from '../core';
|
|
19
23
|
|
|
20
24
|
export {
|
|
21
25
|
injectNavigation,
|
|
@@ -46,7 +50,10 @@ export type { ILinkingConfig, IScreenLinkingConfig } from '../core';
|
|
|
46
50
|
export { Tab } from './tabs';
|
|
47
51
|
export type { ITabNavigatorHandle } from './tabs';
|
|
48
52
|
export { TabScreenDirective } from './tab-screen.directive';
|
|
49
|
-
export type {
|
|
53
|
+
export type {
|
|
54
|
+
ITabScreenOptionsArgs,
|
|
55
|
+
ITabScreenOptionsResolver,
|
|
56
|
+
} from './tab-screen.directive';
|
|
50
57
|
export type { ITabOptions, ITabBarIcon } from '../core';
|
|
51
58
|
|
|
52
59
|
// Drawer: the swipeable drawer navigator, a PURE-JS UI (PanResponder + Animated, no
|
|
@@ -54,10 +61,19 @@ export type { ITabOptions, ITabBarIcon } from '../core';
|
|
|
54
61
|
// ../register import above). See drawer.ts's header for the feasibility note re:
|
|
55
62
|
// react-native-gesture-handler / react-native-reanimated parity gaps.
|
|
56
63
|
export { Drawer } from './drawer';
|
|
57
|
-
export type {
|
|
64
|
+
export type {
|
|
65
|
+
IDrawerNavigatorHandle,
|
|
66
|
+
IDrawerDescriptorMap,
|
|
67
|
+
IDrawerContentContext,
|
|
68
|
+
} from './drawer';
|
|
58
69
|
export { DrawerScreenDirective } from './drawer-screen.directive';
|
|
59
70
|
export type {
|
|
60
71
|
IDrawerScreenOptionsArgs,
|
|
61
72
|
IDrawerScreenOptionsResolver,
|
|
62
73
|
} from './drawer-screen.directive';
|
|
63
|
-
export type {
|
|
74
|
+
export type {
|
|
75
|
+
IDrawerOptions,
|
|
76
|
+
IDrawerScreenOptions,
|
|
77
|
+
IDrawerType,
|
|
78
|
+
IDrawerPosition,
|
|
79
|
+
} from '../core';
|
|
@@ -26,8 +26,14 @@ export function injectFocusEffect(effect: IFocusEffectCallback): void {
|
|
|
26
26
|
cleanup = undefined;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
const unsubscribeFocus = context.emitter.addListener(
|
|
30
|
-
|
|
29
|
+
const unsubscribeFocus = context.emitter.addListener(
|
|
30
|
+
NAVIGATION_EVENT_FOCUS,
|
|
31
|
+
runEffect,
|
|
32
|
+
);
|
|
33
|
+
const unsubscribeBlur = context.emitter.addListener(
|
|
34
|
+
NAVIGATION_EVENT_BLUR,
|
|
35
|
+
runCleanup,
|
|
36
|
+
);
|
|
31
37
|
|
|
32
38
|
destroyRef.onDestroy(() => {
|
|
33
39
|
unsubscribeFocus();
|
|
@@ -13,11 +13,13 @@ export function injectIsFocused(): Signal<boolean> {
|
|
|
13
13
|
const destroyRef = inject(DestroyRef);
|
|
14
14
|
const isFocused = signal(false);
|
|
15
15
|
|
|
16
|
-
const unsubscribeFocus = context.emitter.addListener(
|
|
17
|
-
|
|
16
|
+
const unsubscribeFocus = context.emitter.addListener(
|
|
17
|
+
NAVIGATION_EVENT_FOCUS,
|
|
18
|
+
() => isFocused.set(true),
|
|
18
19
|
);
|
|
19
|
-
const unsubscribeBlur = context.emitter.addListener(
|
|
20
|
-
|
|
20
|
+
const unsubscribeBlur = context.emitter.addListener(
|
|
21
|
+
NAVIGATION_EVENT_BLUR,
|
|
22
|
+
() => isFocused.set(false),
|
|
21
23
|
);
|
|
22
24
|
destroyRef.onDestroy(() => {
|
|
23
25
|
unsubscribeFocus();
|
|
@@ -36,10 +36,13 @@ export function injectNavigationState<TResult>(
|
|
|
36
36
|
|
|
37
37
|
const result = signal(selector({ routes: [route] }));
|
|
38
38
|
|
|
39
|
-
const unsubscribe = context.emitter.addListener(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
const unsubscribe = context.emitter.addListener(
|
|
40
|
+
NAVIGATION_EVENT_STATE,
|
|
41
|
+
(state: unknown) => {
|
|
42
|
+
if (!isNavigatorState(state)) return;
|
|
43
|
+
result.set(selector(state));
|
|
44
|
+
},
|
|
45
|
+
);
|
|
43
46
|
destroyRef.onDestroy(unsubscribe);
|
|
44
47
|
|
|
45
48
|
return result.asReadonly();
|
|
@@ -6,11 +6,20 @@
|
|
|
6
6
|
// surface. All pub/sub logic lives in ../../core/navigation-events; this function only reads
|
|
7
7
|
// NavigationContextService and binds identity.
|
|
8
8
|
|
|
9
|
-
import type {
|
|
10
|
-
|
|
9
|
+
import type {
|
|
10
|
+
INavigationEventListener,
|
|
11
|
+
INavigationEventName,
|
|
12
|
+
} from '../../core';
|
|
13
|
+
import {
|
|
14
|
+
requireNavigationContext,
|
|
15
|
+
type IAnyNavigatorHandle,
|
|
16
|
+
} from '../navigation-context.service';
|
|
11
17
|
|
|
12
18
|
export type INavigationHandle = IAnyNavigatorHandle & {
|
|
13
|
-
addListener: (
|
|
19
|
+
addListener: (
|
|
20
|
+
event: INavigationEventName,
|
|
21
|
+
listener: INavigationEventListener,
|
|
22
|
+
) => () => void;
|
|
14
23
|
// Walks exactly ONE hop up NavigationContextService's `parent` chain to the enclosing
|
|
15
24
|
// navigator's handle (plain immediate-parent walking, no named/targeted ancestor lookup).
|
|
16
25
|
getParent: () => IAnyNavigatorHandle | undefined;
|
package/src/angular/linking.ts
CHANGED
|
@@ -22,7 +22,10 @@ export function injectLinkingIntegration(
|
|
|
22
22
|
): void {
|
|
23
23
|
const destroyRef = inject(DestroyRef);
|
|
24
24
|
|
|
25
|
-
function applyRoute(
|
|
25
|
+
function applyRoute(
|
|
26
|
+
url: string,
|
|
27
|
+
dispatch: (name: string, params?: unknown) => void,
|
|
28
|
+
): void {
|
|
26
29
|
const route = resolveRouteFromUrl(config, url);
|
|
27
30
|
if (route === null) {
|
|
28
31
|
dlog(`injectLinkingIntegration: no route resolved for "${url}"`);
|
|
@@ -38,7 +38,8 @@ export class NavigationContextService {
|
|
|
38
38
|
// package's zoneless/signals-first convention. `navigation`/`emitter` are stable for a screen's
|
|
39
39
|
// whole lifetime, so they stay plain fields - no reactivity needed there.
|
|
40
40
|
private readonly routeSignal = signal<IRoute<unknown> | undefined>(undefined);
|
|
41
|
-
readonly route: Signal<IRoute<unknown> | undefined> =
|
|
41
|
+
readonly route: Signal<IRoute<unknown> | undefined> =
|
|
42
|
+
this.routeSignal.asReadonly();
|
|
42
43
|
|
|
43
44
|
navigation!: IAnyNavigatorHandle;
|
|
44
45
|
emitter!: INavigationEmitter;
|
|
@@ -57,7 +58,9 @@ export class NavigationContextService {
|
|
|
57
58
|
// identical everywhere instead of six independently-typed copies. Must be called from an
|
|
58
59
|
// injection context, same requirement `inject()` itself has - every call site is the first line
|
|
59
60
|
// of an injectX function, which callers already only invoke from their own injection context.
|
|
60
|
-
export function requireNavigationContext(
|
|
61
|
+
export function requireNavigationContext(
|
|
62
|
+
injectorName: string,
|
|
63
|
+
): NavigationContextService {
|
|
61
64
|
const context = inject(NavigationContextService, { optional: true });
|
|
62
65
|
if (!context) {
|
|
63
66
|
throw new Error(
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
|
|
10
10
|
import { Directive, Input, inject, type OnChanges } from '@angular/core';
|
|
11
11
|
import type { INavigationEmitter, IRoute } from '../core';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
NavigationContextService,
|
|
14
|
+
type IAnyNavigatorHandle,
|
|
15
|
+
} from './navigation-context.service';
|
|
13
16
|
|
|
14
17
|
@Directive({
|
|
15
18
|
selector: '[symbioteNavigationScope]',
|
|
@@ -17,7 +20,8 @@ import { NavigationContextService, type IAnyNavigatorHandle } from './navigation
|
|
|
17
20
|
providers: [NavigationContextService],
|
|
18
21
|
})
|
|
19
22
|
export class NavigationScopeDirective implements OnChanges {
|
|
20
|
-
@Input({ required: true, alias: 'symbioteNavigationScope' })
|
|
23
|
+
@Input({ required: true, alias: 'symbioteNavigationScope' })
|
|
24
|
+
route!: IRoute<unknown>;
|
|
21
25
|
@Input({ required: true }) navigation!: IAnyNavigatorHandle;
|
|
22
26
|
@Input({ required: true }) emitter!: INavigationEmitter;
|
|
23
27
|
|
|
@@ -7,7 +7,12 @@
|
|
|
7
7
|
// independent of whether the template is ever instantiated).
|
|
8
8
|
|
|
9
9
|
import { Directive, Input, type Type } from '@angular/core';
|
|
10
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
ISearchBarCommands,
|
|
12
|
+
ISearchBarOptions,
|
|
13
|
+
IScreenOptions,
|
|
14
|
+
IRoute,
|
|
15
|
+
} from '../core';
|
|
11
16
|
import type { INavigatorHandle } from '../core';
|
|
12
17
|
|
|
13
18
|
// The imperative ref (focus/blur/clearText/setText/cancelSearch/toggleCancelButton) carries a
|
|
@@ -21,7 +26,10 @@ export type IAngularSearchBarOptions = ISearchBarOptions & {
|
|
|
21
26
|
ref?: { current: ISearchBarCommands | null };
|
|
22
27
|
};
|
|
23
28
|
|
|
24
|
-
export type IAngularScreenOptions = Omit<
|
|
29
|
+
export type IAngularScreenOptions = Omit<
|
|
30
|
+
IScreenOptions,
|
|
31
|
+
'headerSearchBarOptions'
|
|
32
|
+
> & {
|
|
25
33
|
headerSearchBarOptions?: IAngularSearchBarOptions;
|
|
26
34
|
};
|
|
27
35
|
|
|
@@ -34,7 +42,9 @@ export type IScreenOptionsArgs = {
|
|
|
34
42
|
navigation: INavigatorHandle;
|
|
35
43
|
};
|
|
36
44
|
|
|
37
|
-
export type IScreenOptionsResolver = (
|
|
45
|
+
export type IScreenOptionsResolver = (
|
|
46
|
+
args: IScreenOptionsArgs,
|
|
47
|
+
) => IAngularScreenOptions;
|
|
38
48
|
|
|
39
49
|
@Directive({
|
|
40
50
|
selector: 'ng-template[symbioteScreen]',
|
|
@@ -7,7 +7,14 @@
|
|
|
7
7
|
// needed here: buildSearchBarHandle's own methods are already a LAZY getter over the node,
|
|
8
8
|
// tolerant of the node existing-but-not-yet-committed, same contract every adapter shares).
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
Directive,
|
|
12
|
+
Input,
|
|
13
|
+
inject,
|
|
14
|
+
type OnDestroy,
|
|
15
|
+
type OnInit,
|
|
16
|
+
ElementRef,
|
|
17
|
+
} from '@angular/core';
|
|
11
18
|
import { isSymbioteNode, dlog, debugNodeId } from '@symbiote-native/engine';
|
|
12
19
|
import { buildSearchBarHandle } from '../core';
|
|
13
20
|
import type { ISearchBarCommands } from '../core';
|
|
@@ -17,7 +24,8 @@ import type { ISearchBarCommands } from '../core';
|
|
|
17
24
|
standalone: true,
|
|
18
25
|
})
|
|
19
26
|
export class SearchBarRefDirective implements OnInit, OnDestroy {
|
|
20
|
-
@Input('symbioteSearchBarRef') ref:
|
|
27
|
+
@Input('symbioteSearchBarRef') ref:
|
|
28
|
+
{ current: ISearchBarCommands | null } | undefined;
|
|
21
29
|
|
|
22
30
|
private readonly elementRef = inject<ElementRef<unknown>>(ElementRef);
|
|
23
31
|
|
|
@@ -91,13 +91,18 @@ import type {
|
|
|
91
91
|
import { NavigationScopeDirective } from '../navigation-scope.directive';
|
|
92
92
|
import { SearchBarRefDirective } from '../search-bar-ref.directive';
|
|
93
93
|
import { ScreenDirective } from '../screen.directive';
|
|
94
|
-
import type {
|
|
94
|
+
import type {
|
|
95
|
+
IAngularScreenOptions,
|
|
96
|
+
IScreenOptionsArgs,
|
|
97
|
+
} from '../screen.directive';
|
|
95
98
|
|
|
96
99
|
export type { INavigatorHandle } from '../../core';
|
|
97
100
|
|
|
98
101
|
// backTitleVisible defaults to `true` on both platforms per the codegen spec's own default - no
|
|
99
102
|
// ios/android divergence in v1 scope (mirrors react/stack.ts's own constant exactly).
|
|
100
|
-
const NAVIGATOR_PLATFORM: INavigatorPlatform = {
|
|
103
|
+
const NAVIGATOR_PLATFORM: INavigatorPlatform = {
|
|
104
|
+
defaultHeaderBackTitleVisible: true,
|
|
105
|
+
};
|
|
101
106
|
|
|
102
107
|
let stackInstanceCounter = 0;
|
|
103
108
|
|
|
@@ -162,7 +167,9 @@ let stackInstanceCounter = 0;
|
|
|
162
167
|
</ng-template>
|
|
163
168
|
|
|
164
169
|
<ng-template #headerAndContentTpl let-route let-index="index">
|
|
165
|
-
<RNSScreenStackHeaderConfig
|
|
170
|
+
<RNSScreenStackHeaderConfig
|
|
171
|
+
[symbioteHostProps]="headerConfigProps(route, index)"
|
|
172
|
+
>
|
|
166
173
|
@if (hasSearchBar(route)) {
|
|
167
174
|
<RNSScreenStackHeaderSubview [symbioteHostProps]="headerSubviewProps">
|
|
168
175
|
<RNSSearchBar
|
|
@@ -172,7 +179,9 @@ let stackInstanceCounter = 0;
|
|
|
172
179
|
</RNSScreenStackHeaderSubview>
|
|
173
180
|
}
|
|
174
181
|
</RNSScreenStackHeaderConfig>
|
|
175
|
-
<RNSScreenContentWrapper
|
|
182
|
+
<RNSScreenContentWrapper
|
|
183
|
+
[symbioteHostProps]="contentWrapperProps(route, index)"
|
|
184
|
+
>
|
|
176
185
|
<ng-container
|
|
177
186
|
[symbioteNavigationScope]="route"
|
|
178
187
|
[navigation]="this"
|
|
@@ -185,7 +194,8 @@ let stackInstanceCounter = 0;
|
|
|
185
194
|
`,
|
|
186
195
|
})
|
|
187
196
|
export class Stack implements AfterContentInit, OnDestroy, INavigatorHandle {
|
|
188
|
-
@ContentChildren(ScreenDirective)
|
|
197
|
+
@ContentChildren(ScreenDirective)
|
|
198
|
+
private readonly screenChildren!: QueryList<ScreenDirective>;
|
|
189
199
|
|
|
190
200
|
@Input() initialRouteName?: string;
|
|
191
201
|
@Input() screenOptions?: IAngularScreenOptions;
|
|
@@ -215,7 +225,8 @@ export class Stack implements AfterContentInit, OnDestroy, INavigatorHandle {
|
|
|
215
225
|
|
|
216
226
|
readonly push = (name: string, params?: unknown): void =>
|
|
217
227
|
this.dispatch({ type: 'push', route: this.createRoute(name, params) });
|
|
218
|
-
readonly pop = (count?: number): void =>
|
|
228
|
+
readonly pop = (count?: number): void =>
|
|
229
|
+
this.dispatch({ type: 'pop', count });
|
|
219
230
|
readonly popToTop = (): void => this.dispatch({ type: 'popToTop' });
|
|
220
231
|
readonly popTo = (key: string): void => this.dispatch({ type: 'popTo', key });
|
|
221
232
|
readonly replace = (name: string, params?: unknown): void =>
|
|
@@ -224,14 +235,17 @@ export class Stack implements AfterContentInit, OnDestroy, INavigatorHandle {
|
|
|
224
235
|
this.dispatch({ type: 'setParams', key, params });
|
|
225
236
|
readonly reset = (nextState: INavigatorState): void =>
|
|
226
237
|
this.dispatch({ type: 'reset', state: nextState });
|
|
227
|
-
readonly canGoBack = (): boolean =>
|
|
238
|
+
readonly canGoBack = (): boolean =>
|
|
239
|
+
(this.stateSignal()?.routes.length ?? 0) > 1;
|
|
228
240
|
|
|
229
241
|
ngAfterContentInit(): void {
|
|
230
242
|
this.rebuildRegistry();
|
|
231
243
|
this.initializeState();
|
|
232
|
-
this.screenChildrenSubscription = this.screenChildren.changes.subscribe(
|
|
233
|
-
|
|
234
|
-
|
|
244
|
+
this.screenChildrenSubscription = this.screenChildren.changes.subscribe(
|
|
245
|
+
() => {
|
|
246
|
+
this.rebuildRegistry();
|
|
247
|
+
},
|
|
248
|
+
);
|
|
235
249
|
}
|
|
236
250
|
|
|
237
251
|
ngOnDestroy(): void {
|
|
@@ -261,24 +275,36 @@ export class Stack implements AfterContentInit, OnDestroy, INavigatorHandle {
|
|
|
261
275
|
|
|
262
276
|
private initializeState(): void {
|
|
263
277
|
if (this.stateSignal() !== undefined) return;
|
|
264
|
-
const initialRouteName =
|
|
278
|
+
const initialRouteName =
|
|
279
|
+
this.initialRouteName ?? this.registry.keys().next().value;
|
|
265
280
|
if (initialRouteName === undefined) {
|
|
266
281
|
dlog('Stack: no <ng-template symbioteScreen> children registered');
|
|
267
282
|
this.stateSignal.set(
|
|
268
|
-
createInitialNavigatorState({
|
|
283
|
+
createInitialNavigatorState({
|
|
284
|
+
key: this.routeIdPrefix,
|
|
285
|
+
name: '',
|
|
286
|
+
params: undefined,
|
|
287
|
+
}),
|
|
269
288
|
);
|
|
270
289
|
return;
|
|
271
290
|
}
|
|
272
291
|
this.stateSignal.set(
|
|
273
292
|
createInitialNavigatorState(
|
|
274
|
-
this.createRoute(
|
|
293
|
+
this.createRoute(
|
|
294
|
+
initialRouteName,
|
|
295
|
+
this.registry.get(initialRouteName)?.initialParams,
|
|
296
|
+
),
|
|
275
297
|
),
|
|
276
298
|
);
|
|
277
299
|
}
|
|
278
300
|
|
|
279
301
|
private createRoute(name: string, params: unknown): IRoute<unknown> {
|
|
280
302
|
this.routeSequence += 1;
|
|
281
|
-
return {
|
|
303
|
+
return {
|
|
304
|
+
key: `${this.routeIdPrefix}-${name}-${this.routeSequence}`,
|
|
305
|
+
name,
|
|
306
|
+
params,
|
|
307
|
+
};
|
|
282
308
|
}
|
|
283
309
|
|
|
284
310
|
private dispatch(action: INavigatorAction): void {
|
|
@@ -361,7 +387,8 @@ export class Stack implements AfterContentInit, OnDestroy, INavigatorHandle {
|
|
|
361
387
|
isAndroid: Platform.OS === 'android',
|
|
362
388
|
screenPassthrough: {
|
|
363
389
|
[SCREEN_ON_DISMISSED]: () => this.dispatch({ type: 'pop', count: 1 }),
|
|
364
|
-
[SCREEN_ON_HEADER_BACK_BUTTON_CLICKED]: () =>
|
|
390
|
+
[SCREEN_ON_HEADER_BACK_BUTTON_CLICKED]: () =>
|
|
391
|
+
this.dispatch({ type: 'pop', count: 1 }),
|
|
365
392
|
[SCREEN_ON_APPEAR]: () => {
|
|
366
393
|
dlog(`Stack: route "${route.name}" appeared (focus)`);
|
|
367
394
|
this.emitterFor(route.key).emit(NAVIGATION_EVENT_FOCUS);
|
|
@@ -380,14 +407,19 @@ export class Stack implements AfterContentInit, OnDestroy, INavigatorHandle {
|
|
|
380
407
|
}
|
|
381
408
|
|
|
382
409
|
outerScreenIsModal(route: IRoute<unknown>, index: number): boolean {
|
|
383
|
-
return
|
|
410
|
+
return (
|
|
411
|
+
this.planFor(route, index).screenViewName === RNS_MODAL_SCREEN_VIEW_NAME
|
|
412
|
+
);
|
|
384
413
|
}
|
|
385
414
|
|
|
386
415
|
isInModal(route: IRoute<unknown>, index: number): boolean {
|
|
387
416
|
return this.planFor(route, index).inModal;
|
|
388
417
|
}
|
|
389
418
|
|
|
390
|
-
screenHostProps(
|
|
419
|
+
screenHostProps(
|
|
420
|
+
route: IRoute<unknown>,
|
|
421
|
+
index: number,
|
|
422
|
+
): Record<string, unknown> {
|
|
391
423
|
return this.planFor(route, index).screenProps;
|
|
392
424
|
}
|
|
393
425
|
|
|
@@ -395,16 +427,25 @@ export class Stack implements AfterContentInit, OnDestroy, INavigatorHandle {
|
|
|
395
427
|
return { style: resolveHeaderInModalStackStyle() };
|
|
396
428
|
}
|
|
397
429
|
|
|
398
|
-
innerScreenProps(
|
|
430
|
+
innerScreenProps(
|
|
431
|
+
route: IRoute<unknown>,
|
|
432
|
+
index: number,
|
|
433
|
+
): Record<string, unknown> {
|
|
399
434
|
const plan = this.planFor(route, index);
|
|
400
435
|
return { style: plan.innerScreenStyle, activityState: plan.activityState };
|
|
401
436
|
}
|
|
402
437
|
|
|
403
|
-
contentWrapperProps(
|
|
438
|
+
contentWrapperProps(
|
|
439
|
+
route: IRoute<unknown>,
|
|
440
|
+
index: number,
|
|
441
|
+
): Record<string, unknown> {
|
|
404
442
|
return this.planFor(route, index).contentWrapperProps;
|
|
405
443
|
}
|
|
406
444
|
|
|
407
|
-
headerConfigProps(
|
|
445
|
+
headerConfigProps(
|
|
446
|
+
route: IRoute<unknown>,
|
|
447
|
+
index: number,
|
|
448
|
+
): Record<string, unknown> {
|
|
408
449
|
return this.planFor(route, index).headerConfig.props;
|
|
409
450
|
}
|
|
410
451
|
|
|
@@ -412,11 +453,16 @@ export class Stack implements AfterContentInit, OnDestroy, INavigatorHandle {
|
|
|
412
453
|
return this.mergedOptionsFor(route).headerSearchBarOptions !== undefined;
|
|
413
454
|
}
|
|
414
455
|
|
|
415
|
-
searchBarRef(
|
|
456
|
+
searchBarRef(
|
|
457
|
+
route: IRoute<unknown>,
|
|
458
|
+
): { current: ISearchBarCommands | null } | undefined {
|
|
416
459
|
return this.mergedOptionsFor(route).headerSearchBarOptions?.ref;
|
|
417
460
|
}
|
|
418
461
|
|
|
419
|
-
searchBarProps(
|
|
462
|
+
searchBarProps(
|
|
463
|
+
route: IRoute<unknown>,
|
|
464
|
+
index: number,
|
|
465
|
+
): Record<string, unknown> {
|
|
420
466
|
return this.planFor(route, index).searchBarProps ?? {};
|
|
421
467
|
}
|
|
422
468
|
}
|
|
@@ -12,7 +12,9 @@ export type ITabScreenOptionsArgs = {
|
|
|
12
12
|
navigation: ITabNavigatorHandle;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
export type ITabScreenOptionsResolver = (
|
|
15
|
+
export type ITabScreenOptionsResolver = (
|
|
16
|
+
props: ITabScreenOptionsArgs,
|
|
17
|
+
) => ITabOptions;
|
|
16
18
|
|
|
17
19
|
@Directive({
|
|
18
20
|
selector: 'ng-template[symbioteTabScreen]',
|
|
@@ -66,7 +66,12 @@ let tabInstanceCounter = 0;
|
|
|
66
66
|
@Component({
|
|
67
67
|
selector: 'Tab',
|
|
68
68
|
standalone: true,
|
|
69
|
-
imports: [
|
|
69
|
+
imports: [
|
|
70
|
+
NgComponentOutlet,
|
|
71
|
+
NavigationScopeDirective,
|
|
72
|
+
DescriptorOutlet,
|
|
73
|
+
View,
|
|
74
|
+
],
|
|
70
75
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
71
76
|
template: `
|
|
72
77
|
<View [style]="rootStyle">
|
|
@@ -101,7 +106,8 @@ export class Tab implements AfterContentInit, OnDestroy, ITabNavigatorHandle {
|
|
|
101
106
|
// Keyed by name -> the LIVE TabScreenDirective instance - see stack.ts's matching comment for
|
|
102
107
|
// why a snapshot copy would go stale on an in-place `[options]`/`[component]` change.
|
|
103
108
|
private readonly registry = new Map<string, TabScreenDirective>();
|
|
104
|
-
private tabScreenChildrenSubscription:
|
|
109
|
+
private tabScreenChildrenSubscription:
|
|
110
|
+
{ unsubscribe: () => void } | undefined;
|
|
105
111
|
|
|
106
112
|
private readonly stateSignal = signal<ITabRouterState | undefined>(undefined);
|
|
107
113
|
readonly state = this.stateSignal.asReadonly();
|
|
@@ -116,9 +122,10 @@ export class Tab implements AfterContentInit, OnDestroy, ITabNavigatorHandle {
|
|
|
116
122
|
|
|
117
123
|
ngAfterContentInit(): void {
|
|
118
124
|
this.syncRegistry();
|
|
119
|
-
this.tabScreenChildrenSubscription =
|
|
120
|
-
this.
|
|
121
|
-
|
|
125
|
+
this.tabScreenChildrenSubscription =
|
|
126
|
+
this.tabScreenChildren.changes.subscribe(() => {
|
|
127
|
+
this.syncRegistry();
|
|
128
|
+
});
|
|
122
129
|
}
|
|
123
130
|
|
|
124
131
|
ngOnDestroy(): void {
|
|
@@ -150,7 +157,8 @@ export class Tab implements AfterContentInit, OnDestroy, ITabNavigatorHandle {
|
|
|
150
157
|
private syncRegistry(): void {
|
|
151
158
|
this.rebuildRegistry();
|
|
152
159
|
const routes = this.routesFromRegistry();
|
|
153
|
-
if (routes.length === 0)
|
|
160
|
+
if (routes.length === 0)
|
|
161
|
+
dlog('Tab: no <ng-template symbioteTabScreen> children registered');
|
|
154
162
|
const current = this.stateSignal();
|
|
155
163
|
if (current !== undefined) {
|
|
156
164
|
this.stateSignal.set(reconcileTabRoutes(current, routes));
|
|
@@ -168,9 +176,15 @@ export class Tab implements AfterContentInit, OnDestroy, ITabNavigatorHandle {
|
|
|
168
176
|
this.stateSignal.set(tabRouterReducer(current, action));
|
|
169
177
|
}
|
|
170
178
|
|
|
171
|
-
private resolveTabOptions(
|
|
179
|
+
private resolveTabOptions(
|
|
180
|
+
entry: TabScreenDirective,
|
|
181
|
+
route: IRoute<unknown>,
|
|
182
|
+
): ITabOptions {
|
|
172
183
|
const props: ITabScreenOptionsArgs = { route, navigation: this };
|
|
173
|
-
const own =
|
|
184
|
+
const own =
|
|
185
|
+
typeof entry.options === 'function'
|
|
186
|
+
? entry.options(props)
|
|
187
|
+
: entry.options;
|
|
174
188
|
return { ...this.screenOptions, ...own };
|
|
175
189
|
}
|
|
176
190
|
|
|
@@ -207,8 +221,12 @@ export class Tab implements AfterContentInit, OnDestroy, ITabNavigatorHandle {
|
|
|
207
221
|
focusedRouteEmitter(): INavigationEmitter {
|
|
208
222
|
const key = this.focusedRoute()?.key;
|
|
209
223
|
return untracked(() => {
|
|
210
|
-
if (key === this.currentEmitterKey && this.currentEmitter)
|
|
211
|
-
|
|
224
|
+
if (key === this.currentEmitterKey && this.currentEmitter)
|
|
225
|
+
return this.currentEmitter;
|
|
226
|
+
const { blurKey, focusKey } = diffFocusedRoute(
|
|
227
|
+
this.currentEmitterKey,
|
|
228
|
+
key,
|
|
229
|
+
);
|
|
212
230
|
if (blurKey !== undefined && this.currentEmitter) {
|
|
213
231
|
dlog('Tab: previous route blurred');
|
|
214
232
|
this.currentEmitter.emit(NAVIGATION_EVENT_BLUR);
|
|
@@ -257,12 +275,18 @@ export class Tab implements AfterContentInit, OnDestroy, ITabNavigatorHandle {
|
|
|
257
275
|
});
|
|
258
276
|
|
|
259
277
|
const focusedRoute = this.focusedRoute();
|
|
260
|
-
const focusedEntry = focusedRoute
|
|
278
|
+
const focusedEntry = focusedRoute
|
|
279
|
+
? this.registry.get(focusedRoute.name)
|
|
280
|
+
: undefined;
|
|
261
281
|
const focusedOptions =
|
|
262
282
|
focusedEntry && focusedRoute
|
|
263
283
|
? this.resolveTabOptions(focusedEntry, focusedRoute)
|
|
264
284
|
: this.screenOptions;
|
|
265
285
|
|
|
266
|
-
return renderTabBar({
|
|
286
|
+
return renderTabBar({
|
|
287
|
+
items,
|
|
288
|
+
style: focusedOptions?.tabBarStyle,
|
|
289
|
+
passthrough: {},
|
|
290
|
+
});
|
|
267
291
|
}
|
|
268
292
|
}
|
package/src/core/constants.ts
CHANGED
|
@@ -11,12 +11,14 @@ export const RNS_SCREEN_VIEW_NAME = 'RNSScreen';
|
|
|
11
11
|
// render-stack.ts for why a formSheet/modal screen MUST use this different Fabric name.
|
|
12
12
|
export const RNS_MODAL_SCREEN_VIEW_NAME = 'RNSModalScreen';
|
|
13
13
|
export const RNS_SCREEN_STACK_VIEW_NAME = 'RNSScreenStack';
|
|
14
|
-
export const RNS_SCREEN_STACK_HEADER_CONFIG_VIEW_NAME =
|
|
14
|
+
export const RNS_SCREEN_STACK_HEADER_CONFIG_VIEW_NAME =
|
|
15
|
+
'RNSScreenStackHeaderConfig';
|
|
15
16
|
// The header-subview leaf react-native-screens itself uses to host arbitrary framework children
|
|
16
17
|
// (left/right/back/title/center/searchBar) inside the header. Registered here for ground-truth
|
|
17
18
|
// parity, but unconsumed by any fold in v1 scope: renderHeaderConfig's Descriptor never takes
|
|
18
19
|
// framework children (see render-stack.ts), so mounting a subview child is left to the adapter.
|
|
19
|
-
export const RNS_SCREEN_STACK_HEADER_SUBVIEW_VIEW_NAME =
|
|
20
|
+
export const RNS_SCREEN_STACK_HEADER_SUBVIEW_VIEW_NAME =
|
|
21
|
+
'RNSScreenStackHeaderSubview';
|
|
20
22
|
export const RNS_SEARCH_BAR_VIEW_NAME = 'RNSSearchBar';
|
|
21
23
|
// react-native-screens' own JS layer (ScreenStackItem -> DebugContainer -> ScreenContentWrapper)
|
|
22
24
|
// ALWAYS wraps a screen's content in this native view, `collapsable: false` so Fabric can never
|
|
@@ -43,7 +45,8 @@ export const STACK_ON_FINISH_TRANSITIONING = 'onFinishTransitioning';
|
|
|
43
45
|
// plain bar-button-item press, and a press on a (possibly nested) menu action of a 'menu'-type
|
|
44
46
|
// bar-button item.
|
|
45
47
|
export const HEADER_ON_PRESS_BAR_BUTTON_ITEM = 'onPressHeaderBarButtonItem';
|
|
46
|
-
export const HEADER_ON_PRESS_BAR_BUTTON_MENU_ITEM =
|
|
48
|
+
export const HEADER_ON_PRESS_BAR_BUTTON_MENU_ITEM =
|
|
49
|
+
'onPressHeaderBarButtonMenuItem';
|
|
47
50
|
|
|
48
51
|
// RNSSearchBar event prop keys (all DirectEventHandler in the codegen spec). onClose/onOpen are
|
|
49
52
|
// Android-only per the codegen spec's own comment.
|