@symbiote-native/navigation 1.0.0 → 2.0.1
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 +159 -0
- package/package.json +14 -12
- package/build/angular/drawer/index.d.ts +0 -76
- package/build/angular/drawer/index.js +0 -508
- package/build/angular/drawer-screen.directive.d.ts +0 -14
- package/build/angular/drawer-screen.directive.js +0 -87
- package/build/angular/index.d.ts +0 -23
- package/build/angular/index.js +0 -22
- package/build/angular/injectors/index.d.ts +0 -13
- package/build/angular/injectors/index.js +0 -8
- package/build/angular/injectors/inject-drawer-navigation.d.ts +0 -4
- package/build/angular/injectors/inject-drawer-navigation.js +0 -12
- package/build/angular/injectors/inject-focus-effect.d.ts +0 -2
- package/build/angular/injectors/inject-focus-effect.js +0 -29
- package/build/angular/injectors/inject-is-focused.d.ts +0 -2
- package/build/angular/injectors/inject-is-focused.js +0 -20
- package/build/angular/injectors/inject-navigation-state.d.ts +0 -3
- package/build/angular/injectors/inject-navigation-state.js +0 -35
- package/build/angular/injectors/inject-navigation.d.ts +0 -7
- package/build/angular/injectors/inject-navigation.js +0 -16
- package/build/angular/injectors/inject-route.d.ts +0 -3
- package/build/angular/injectors/inject-route.js +0 -20
- package/build/angular/injectors/inject-stack-navigation.d.ts +0 -4
- package/build/angular/injectors/inject-stack-navigation.js +0 -15
- package/build/angular/injectors/inject-tab-navigation.d.ts +0 -4
- package/build/angular/injectors/inject-tab-navigation.js +0 -11
- package/build/angular/linking.d.ts +0 -3
- package/build/angular/linking.js +0 -43
- package/build/angular/navigation-context.service.d.ts +0 -13
- package/build/angular/navigation-context.service.js +0 -105
- package/build/angular/navigation-scope.directive.d.ts +0 -10
- package/build/angular/navigation-scope.directive.js +0 -90
- package/build/angular/screen.directive.d.ts +0 -22
- package/build/angular/screen.directive.js +0 -90
- package/build/angular/search-bar-ref.directive.d.ts +0 -10
- package/build/angular/search-bar-ref.directive.js +0 -88
- package/build/angular/stack/index.d.ts +0 -55
- package/build/angular/stack/index.js +0 -386
- package/build/angular/tab-screen.directive.d.ts +0 -14
- package/build/angular/tab-screen.directive.js +0 -87
- package/build/angular/tabs/index.d.ts +0 -42
- package/build/angular/tabs/index.js +0 -263
- package/build/core/constants.d.ts +0 -27
- package/build/core/constants.js +0 -61
- package/build/core/drawer-options/index.d.ts +0 -64
- package/build/core/drawer-options/index.js +0 -200
- package/build/core/drawer-router-state/index.d.ts +0 -21
- package/build/core/drawer-router-state/index.js +0 -40
- package/build/core/guards.d.ts +0 -2
- package/build/core/guards.js +0 -22
- package/build/core/header-bar-buttons.d.ts +0 -9
- package/build/core/header-bar-buttons.js +0 -163
- package/build/core/index.d.ts +0 -29
- package/build/core/index.js +0 -32
- package/build/core/linking-config/index.d.ts +0 -13
- package/build/core/linking-config/index.js +0 -171
- package/build/core/navigation-events/index.d.ts +0 -16
- package/build/core/navigation-events/index.js +0 -57
- package/build/core/navigator-handles/index.d.ts +0 -30
- package/build/core/navigator-handles/index.js +0 -15
- package/build/core/navigator-props.d.ts +0 -216
- package/build/core/navigator-props.js +0 -7
- package/build/core/navigator-state/index.d.ts +0 -36
- package/build/core/navigator-state/index.js +0 -81
- package/build/core/render-drawer.d.ts +0 -13
- package/build/core/render-drawer.js +0 -88
- package/build/core/render-stack.d.ts +0 -35
- package/build/core/render-stack.js +0 -294
- package/build/core/render-tabs.d.ts +0 -20
- package/build/core/render-tabs.js +0 -97
- package/build/core/screen-options.d.ts +0 -4
- package/build/core/screen-options.js +0 -133
- package/build/core/search-bar-commands.d.ts +0 -10
- package/build/core/search-bar-commands.js +0 -42
- package/build/core/state-persistence/index.d.ts +0 -3
- package/build/core/state-persistence/index.js +0 -23
- package/build/core/tab-options.d.ts +0 -13
- package/build/core/tab-options.js +0 -10
- package/build/core/tab-router-state/index.d.ts +0 -19
- package/build/core/tab-router-state/index.js +0 -51
- package/build/react/collect-registry.d.ts +0 -4
- package/build/react/collect-registry.js +0 -15
- package/build/react/drawer/index.d.ts +0 -29
- package/build/react/drawer/index.js +0 -277
- package/build/react/drawer-screen.d.ts +0 -14
- package/build/react/drawer-screen.js +0 -8
- package/build/react/hooks/index.d.ts +0 -12
- package/build/react/hooks/index.js +0 -8
- package/build/react/hooks/use-drawer-navigation.d.ts +0 -4
- package/build/react/hooks/use-drawer-navigation.js +0 -12
- package/build/react/hooks/use-focus-effect.d.ts +0 -2
- package/build/react/hooks/use-focus-effect.js +0 -29
- package/build/react/hooks/use-is-focused.d.ts +0 -1
- package/build/react/hooks/use-is-focused.js +0 -22
- package/build/react/hooks/use-navigation-state/index.d.ts +0 -2
- package/build/react/hooks/use-navigation-state/index.js +0 -39
- package/build/react/hooks/use-navigation.d.ts +0 -7
- package/build/react/hooks/use-navigation.js +0 -16
- package/build/react/hooks/use-route.d.ts +0 -2
- package/build/react/hooks/use-route.js +0 -8
- package/build/react/hooks/use-stack-navigation.d.ts +0 -4
- package/build/react/hooks/use-stack-navigation.js +0 -14
- package/build/react/hooks/use-tab-navigation.d.ts +0 -4
- package/build/react/hooks/use-tab-navigation.js +0 -12
- package/build/react/index.d.ts +0 -24
- package/build/react/index.js +0 -21
- package/build/react/linking.d.ts +0 -3
- package/build/react/linking.js +0 -47
- package/build/react/navigation-context.d.ts +0 -10
- package/build/react/navigation-context.js +0 -22
- package/build/react/screen.d.ts +0 -22
- package/build/react/screen.js +0 -7
- package/build/react/stack/index.d.ts +0 -13
- package/build/react/stack/index.js +0 -202
- package/build/react/tab-screen.d.ts +0 -15
- package/build/react/tab-screen.js +0 -8
- package/build/react/tabs/index.d.ts +0 -12
- package/build/react/tabs/index.js +0 -116
- package/build/register.d.ts +0 -7
- package/build/register.js +0 -58
- package/build/vue/composables/index.d.ts +0 -12
- package/build/vue/composables/index.js +0 -8
- package/build/vue/composables/use-drawer-navigation.d.ts +0 -5
- package/build/vue/composables/use-drawer-navigation.js +0 -15
- package/build/vue/composables/use-focus-effect.d.ts +0 -1
- package/build/vue/composables/use-focus-effect.js +0 -31
- package/build/vue/composables/use-is-focused.d.ts +0 -2
- package/build/vue/composables/use-is-focused.js +0 -28
- package/build/vue/composables/use-navigation-state.d.ts +0 -3
- package/build/vue/composables/use-navigation-state.js +0 -29
- package/build/vue/composables/use-navigation.d.ts +0 -8
- package/build/vue/composables/use-navigation.js +0 -13
- package/build/vue/composables/use-route.d.ts +0 -3
- package/build/vue/composables/use-route.js +0 -9
- package/build/vue/composables/use-stack-navigation.d.ts +0 -5
- package/build/vue/composables/use-stack-navigation.js +0 -18
- package/build/vue/composables/use-tab-navigation.d.ts +0 -5
- package/build/vue/composables/use-tab-navigation.js +0 -15
- package/build/vue/drawer/index.d.ts +0 -19
- package/build/vue/drawer/index.js +0 -390
- package/build/vue/drawer-screen.d.ts +0 -14
- package/build/vue/drawer-screen.js +0 -12
- package/build/vue/index.d.ts +0 -21
- package/build/vue/index.js +0 -19
- package/build/vue/linking.d.ts +0 -3
- package/build/vue/linking.js +0 -57
- package/build/vue/navigation-context.d.ts +0 -16
- package/build/vue/navigation-context.js +0 -65
- package/build/vue/screen.d.ts +0 -21
- package/build/vue/screen.js +0 -12
- package/build/vue/stack/index.d.ts +0 -9
- package/build/vue/stack/index.js +0 -279
- package/build/vue/tab-screen.d.ts +0 -14
- package/build/vue/tab-screen.js +0 -11
- package/build/vue/tabs/index.d.ts +0 -10
- package/build/vue/tabs/index.js +0 -209
- package/build-ngc/angular/drawer.d.ts +0 -79
- package/build-ngc/angular/drawer.js +0 -475
- package/build-ngc/angular/drawer.js.map +0 -1
- package/build-ngc/angular/stack.d.ts +0 -58
- package/build-ngc/angular/stack.js +0 -402
- package/build-ngc/angular/stack.js.map +0 -1
- package/build-ngc/angular/tabs.d.ts +0 -45
- package/build-ngc/angular/tabs.js +0 -231
- package/build-ngc/angular/tabs.js.map +0 -1
- package/build-ngc/core/drawer-options.d.ts +0 -64
- package/build-ngc/core/drawer-options.js +0 -201
- package/build-ngc/core/drawer-options.js.map +0 -1
- package/build-ngc/core/drawer-router-state.d.ts +0 -21
- package/build-ngc/core/drawer-router-state.js +0 -41
- package/build-ngc/core/drawer-router-state.js.map +0 -1
- package/build-ngc/core/href.d.ts +0 -16
- package/build-ngc/core/href.js +0 -55
- package/build-ngc/core/href.js.map +0 -1
- package/build-ngc/core/linking-config.d.ts +0 -13
- package/build-ngc/core/linking-config.js +0 -172
- package/build-ngc/core/linking-config.js.map +0 -1
- package/build-ngc/core/navigation-events.d.ts +0 -16
- package/build-ngc/core/navigation-events.js +0 -58
- package/build-ngc/core/navigation-events.js.map +0 -1
- package/build-ngc/core/navigator-handles.d.ts +0 -30
- package/build-ngc/core/navigator-handles.js +0 -16
- package/build-ngc/core/navigator-handles.js.map +0 -1
- package/build-ngc/core/navigator-state.d.ts +0 -36
- package/build-ngc/core/navigator-state.js +0 -82
- package/build-ngc/core/navigator-state.js.map +0 -1
- package/build-ngc/core/search-params.d.ts +0 -9
- package/build-ngc/core/search-params.js +0 -60
- package/build-ngc/core/search-params.js.map +0 -1
- package/build-ngc/core/state-persistence.d.ts +0 -3
- package/build-ngc/core/state-persistence.js +0 -24
- package/build-ngc/core/state-persistence.js.map +0 -1
- package/build-ngc/core/tab-router-state.d.ts +0 -19
- package/build-ngc/core/tab-router-state.js +0 -52
- package/build-ngc/core/tab-router-state.js.map +0 -1
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
// Stack, the React lifecycle half. The route-stack transitions (navigator-state) and the
|
|
2
|
-
// options/props folds (screen-options, render-stack) live in @symbiote-native/navigation core,
|
|
3
|
-
// shared verbatim with the Vue/Angular adapters; here React supplies the lifecycle - useReducer
|
|
4
|
-
// for the pushed-route stack, useId + a ref counter for route-key generation, useImperativeHandle
|
|
5
|
-
// for the push/pop/replace navigator handle - plus the descriptor bridge for the header config
|
|
6
|
-
// leaf. Pushing/popping a route is an ordinary child mount/unmount: RNSScreenStack diffs its
|
|
7
|
-
// RNSScreen children natively, so no imperative native command is needed here at all. Neither
|
|
8
|
-
// this nor the Screen marker imports react-native-screens' own React components (ScreenStack.tsx
|
|
9
|
-
// et al - hooks, crashes a non-React adapter); the native views are driven directly through the
|
|
10
|
-
// ViewConfig ../register registers. See CLAUDE.md <third_party_rn_packages_are_react_only>.
|
|
11
|
-
import { createElement, forwardRef, isValidElement, useCallback, useContext, useEffect, useId, useImperativeHandle, useMemo, useReducer, useRef, } from 'react';
|
|
12
|
-
import { descriptorToReact } from '@symbiote-native/react';
|
|
13
|
-
import { Platform, debugNodeId, dlog } from '@symbiote-native/engine';
|
|
14
|
-
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, navigatorReducer, resolveScreenRenderPlan, resolveStackProps, } from '../../core/index.js';
|
|
15
|
-
import { collectRegistry } from '../collect-registry.js';
|
|
16
|
-
import { NavigationContext } from '../navigation-context.js';
|
|
17
|
-
import { Screen } from '../screen.js';
|
|
18
|
-
// backTitleVisible defaults to `true` on both platforms per the codegen spec's own default
|
|
19
|
-
// (CT.WithDefault<boolean, 'true'>) - no ios/android divergence in v1 scope, so a single constant
|
|
20
|
-
// stands in for the per-platform injection point ISliderPlatform-style adapters use elsewhere.
|
|
21
|
-
const NAVIGATOR_PLATFORM = { defaultHeaderBackTitleVisible: true };
|
|
22
|
-
function isScreenElement(child) {
|
|
23
|
-
return isValidElement(child) && child.type === Screen;
|
|
24
|
-
}
|
|
25
|
-
function resolveScreenOptions(entry, optionsArgs, screenOptions) {
|
|
26
|
-
const own = typeof entry.options === 'function' ? entry.options(optionsArgs) : entry.options;
|
|
27
|
-
return { ...screenOptions, ...own };
|
|
28
|
-
}
|
|
29
|
-
const StackImpl = forwardRef((props, forwardedRef) => {
|
|
30
|
-
// Read BEFORE establishing this Stack's own Context value below - becomes the `parent` link a
|
|
31
|
-
// nested screen's useNavigation().getParent() walks (e.g. this Stack rendered as a Tab screen's
|
|
32
|
-
// content reaches that Tab via this value). undefined when this Stack is the nesting root.
|
|
33
|
-
const ambientContext = useContext(NavigationContext);
|
|
34
|
-
const registry = useMemo(() => collectRegistry(props.children, isScreenElement), [props.children]);
|
|
35
|
-
const routeIdPrefix = useId();
|
|
36
|
-
const routeSequence = useRef(0);
|
|
37
|
-
// One emitter per route.key, keyed exactly like routeSequence's counter is scoped - created
|
|
38
|
-
// lazily the first time a route is rendered, pruned once it's popped off the stack (below).
|
|
39
|
-
const emitters = useRef(new Map()).current;
|
|
40
|
-
// Investigation instrumentation (flicker-on-focus bug): route keys whose resolved
|
|
41
|
-
// transition/animation-timing props have already been logged, so the once-per-mount dump below
|
|
42
|
-
// doesn't spam on every re-render. Kept behind DEBUG, never removed.
|
|
43
|
-
const loggedScreenPropKeys = useRef(new Set()).current;
|
|
44
|
-
const emitterFor = useCallback((routeKey) => {
|
|
45
|
-
let emitter = emitters.get(routeKey);
|
|
46
|
-
if (!emitter) {
|
|
47
|
-
emitter = createNavigationEmitter();
|
|
48
|
-
emitters.set(routeKey, emitter);
|
|
49
|
-
}
|
|
50
|
-
return emitter;
|
|
51
|
-
}, [emitters]);
|
|
52
|
-
const createRoute = useCallback((name, params) => {
|
|
53
|
-
routeSequence.current += 1;
|
|
54
|
-
return { key: `${routeIdPrefix}-${name}-${routeSequence.current}`, name, params };
|
|
55
|
-
}, [routeIdPrefix]);
|
|
56
|
-
const [state, dispatch] = useReducer(navigatorReducer, undefined, () => {
|
|
57
|
-
const initialRouteName = props.initialRouteName ?? registry.keys().next().value;
|
|
58
|
-
if (initialRouteName === undefined) {
|
|
59
|
-
dlog('Stack: no <Stack.Screen> children registered');
|
|
60
|
-
return createInitialNavigatorState({ key: routeIdPrefix, name: '', params: undefined });
|
|
61
|
-
}
|
|
62
|
-
return createInitialNavigatorState(createRoute(initialRouteName, registry.get(initialRouteName)?.initialParams));
|
|
63
|
-
});
|
|
64
|
-
const handle = useMemo(() => ({
|
|
65
|
-
push: (name, params) => dispatch({ type: 'push', route: createRoute(name, params) }),
|
|
66
|
-
pop: count => dispatch({ type: 'pop', count }),
|
|
67
|
-
popToTop: () => dispatch({ type: 'popToTop' }),
|
|
68
|
-
popTo: key => dispatch({ type: 'popTo', key }),
|
|
69
|
-
replace: (name, params) => dispatch({ type: 'replace', route: createRoute(name, params) }),
|
|
70
|
-
setParams: (params, key) => dispatch({ type: 'setParams', key, params }),
|
|
71
|
-
reset: nextState => dispatch({ type: 'reset', state: nextState }),
|
|
72
|
-
canGoBack: () => state.routes.length > 1,
|
|
73
|
-
}), [createRoute, state.routes.length]);
|
|
74
|
-
useImperativeHandle(forwardedRef, () => handle, [handle]);
|
|
75
|
-
// Broadcasts the router state to every still-live route's emitter (useNavigationState's
|
|
76
|
-
// source) after each commit, and prunes emitters for routes popped off the stack - mirroring
|
|
77
|
-
// React's own subscribe-in-effect pattern rather than emitting mid-render, which would call a
|
|
78
|
-
// descendant's setState while this component is still rendering.
|
|
79
|
-
useEffect(() => {
|
|
80
|
-
for (const route of state.routes) {
|
|
81
|
-
emitterFor(route.key).emit(NAVIGATION_EVENT_STATE, state);
|
|
82
|
-
}
|
|
83
|
-
for (const routeKey of emitters.keys()) {
|
|
84
|
-
if (!state.routes.some(route => route.key === routeKey))
|
|
85
|
-
emitters.delete(routeKey);
|
|
86
|
-
}
|
|
87
|
-
}, [state, emitterFor, emitters]);
|
|
88
|
-
// Investigation instrumentation (flicker-on-focus bug): STACK_ON_FINISH_TRANSITIONING is the
|
|
89
|
-
// native signal that the WHOLE push/pop animation has finished (as opposed to onAppear/
|
|
90
|
-
// onDisappear, which are per-screen) - logging it lets the per-screen appear/disappear
|
|
91
|
-
// timestamps above be checked against the actual transition-complete moment. Kept behind
|
|
92
|
-
// DEBUG, never removed.
|
|
93
|
-
const stackProps = resolveStackProps({
|
|
94
|
-
passthrough: {
|
|
95
|
-
[STACK_ON_FINISH_TRANSITIONING]: () => dlog(`Stack: onFinishTransitioning at t=${Date.now()}`),
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
const children = state.routes.map((route, index) => {
|
|
99
|
-
const entry = registry.get(route.name);
|
|
100
|
-
if (!entry) {
|
|
101
|
-
dlog(`Stack: no screen registered for route name "${route.name}"`);
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
const screenOptionsArgs = { route, navigation: handle };
|
|
105
|
-
const mergedOptions = resolveScreenOptions(entry, screenOptionsArgs, props.screenOptions);
|
|
106
|
-
const activityState = computeActivityState(index, state.routes.length);
|
|
107
|
-
// Investigation instrumentation (flicker-on-focus bug): fires on EVERY Stack render, not just
|
|
108
|
-
// on transitions, so the log stream shows whether a route's activityState/index ever changes
|
|
109
|
-
// outside of a push/pop dispatch (e.g. from an unrelated re-render racing the native
|
|
110
|
-
// transition). Kept behind DEBUG per <keep_logs_gate_behind_DEBUG> - never remove.
|
|
111
|
-
dlog(`Stack: render route "${route.name}" index=${index}/${state.routes.length - 1} ` +
|
|
112
|
-
`activityState=${activityState} at t=${Date.now()}`);
|
|
113
|
-
const routeEmitter = emitterFor(route.key);
|
|
114
|
-
const searchBarOptions = mergedOptions.headerSearchBarOptions;
|
|
115
|
-
const plan = resolveScreenRenderPlan({
|
|
116
|
-
screenId: route.key,
|
|
117
|
-
index,
|
|
118
|
-
routeCount: state.routes.length,
|
|
119
|
-
options: mergedOptions,
|
|
120
|
-
platform: NAVIGATOR_PLATFORM,
|
|
121
|
-
isAndroid: Platform.OS === 'android',
|
|
122
|
-
screenPassthrough: {
|
|
123
|
-
[SCREEN_ON_DISMISSED]: () => dispatch({ type: 'pop', count: 1 }),
|
|
124
|
-
[SCREEN_ON_HEADER_BACK_BUTTON_CLICKED]: () => dispatch({ type: 'pop', count: 1 }),
|
|
125
|
-
// onAppear/onDisappear are the definitive visibility boundary (post-transition-animation),
|
|
126
|
-
// so 'focus'/'blur' fire exactly once per transition; onWillAppear/onWillDisappear fire
|
|
127
|
-
// BEFORE the animation runs, so wiring them to emit() too would double-invoke
|
|
128
|
-
// useFocusEffect per transition - they only get a debug log here.
|
|
129
|
-
[SCREEN_ON_WILL_APPEAR]: () => dlog(`Stack: route "${route.name}" will appear at t=${Date.now()}`),
|
|
130
|
-
[SCREEN_ON_APPEAR]: () => {
|
|
131
|
-
dlog(`Stack: route "${route.name}" appeared (focus) at t=${Date.now()}`);
|
|
132
|
-
routeEmitter.emit(NAVIGATION_EVENT_FOCUS);
|
|
133
|
-
},
|
|
134
|
-
[SCREEN_ON_WILL_DISAPPEAR]: () => dlog(`Stack: route "${route.name}" will disappear at t=${Date.now()}`),
|
|
135
|
-
[SCREEN_ON_DISAPPEAR]: () => {
|
|
136
|
-
dlog(`Stack: route "${route.name}" disappeared (blur) at t=${Date.now()}`);
|
|
137
|
-
routeEmitter.emit(NAVIGATION_EVENT_BLUR);
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
searchBarPassthrough: searchBarOptions
|
|
141
|
-
? {
|
|
142
|
-
...buildSearchBarPassthrough(searchBarOptions, message => dlog(`Stack: route "${route.name}" ${message}`)),
|
|
143
|
-
// The imperative ref (SearchBarCommands): a callback ref attached straight to the
|
|
144
|
-
// RNSSearchBar host element via `passthrough.ref` - createElement extracts `ref` from
|
|
145
|
-
// a props object same as ScrollView/TextInput's own ref binding (see
|
|
146
|
-
// descriptor-to-react's comment), so the host instance IS the ISymbioteNode. Built
|
|
147
|
-
// fresh per mount/unmount, never captured eagerly (buildSearchBarHandle's own
|
|
148
|
-
// lazy-getter contract is satisfied trivially here since `node` is already resolved).
|
|
149
|
-
ref: (node) => {
|
|
150
|
-
// Investigation instrumentation (search-bar-ref "node not committed" bug): compare
|
|
151
|
-
// this debugNodeId against the mirror.set/dispatchViewCommand logs in commit.ts -
|
|
152
|
-
// same id on both sides proves the ref really does hold the committed node; a
|
|
153
|
-
// mismatch proves a stale closure instead. Kept behind DEBUG, never removed.
|
|
154
|
-
dlog(`Stack: search bar ref callback, node=${node === null ? 'null' : debugNodeId(node)} at t=${Date.now()}`);
|
|
155
|
-
const appRef = searchBarOptions.ref;
|
|
156
|
-
if (!appRef)
|
|
157
|
-
return;
|
|
158
|
-
appRef.current = node === null ? null : buildSearchBarHandle(() => node);
|
|
159
|
-
},
|
|
160
|
-
}
|
|
161
|
-
: undefined,
|
|
162
|
-
});
|
|
163
|
-
// Investigation instrumentation (flicker-on-focus bug): the actual timing/z-order-relevant
|
|
164
|
-
// values resolved onto the native RNSScreen, once per route.key (not every render) - rules a
|
|
165
|
-
// stackAnimation/transitionDuration mismatch against react-native-screens' own native default
|
|
166
|
-
// in or out. Kept behind DEBUG, never removed.
|
|
167
|
-
if (!loggedScreenPropKeys.has(route.key)) {
|
|
168
|
-
loggedScreenPropKeys.add(route.key);
|
|
169
|
-
dlog(`Stack: route "${route.name}" resolved screen props ` +
|
|
170
|
-
`stackAnimation=${String(plan.screenProps.stackAnimation)} ` +
|
|
171
|
-
`stackPresentation=${String(plan.screenProps.stackPresentation)} ` +
|
|
172
|
-
`transitionDuration=${String(plan.screenProps.transitionDuration)} ` +
|
|
173
|
-
`gestureEnabled=${String(plan.screenProps.gestureEnabled)} at t=${Date.now()}`);
|
|
174
|
-
}
|
|
175
|
-
// Must not be flattened away (collapsable: false) - react-native-screens' native side
|
|
176
|
-
// finds THIS specific view type by class check to register a formSheet's content for
|
|
177
|
-
// sizing (see RNS_SCREEN_CONTENT_WRAPPER_VIEW_NAME's comment in core/constants.ts). A
|
|
178
|
-
// `push` screen doesn't need it, but a `formSheet` one is otherwise left with no content
|
|
179
|
-
// ever attached natively.
|
|
180
|
-
const content = createElement(RNS_SCREEN_CONTENT_WRAPPER_VIEW_NAME, plan.contentWrapperProps, createElement(NavigationContext.Provider, { value: { route, navigation: handle, emitter: routeEmitter, parent: ambientContext } }, createElement(entry.component)));
|
|
181
|
-
// react-native-screens' own Screen.tsx swaps in a DIFFERENT Fabric component
|
|
182
|
-
// ('RNSModalScreen') for a modally-presented screen - its native updateLayoutMetrics: relies on
|
|
183
|
-
// this exact class to know it must NOT apply Yoga's computed frame (see resolveScreenViewName's
|
|
184
|
-
// comment in core/render-stack.ts). The nested Screen isHeaderInModal adds below is always
|
|
185
|
-
// plain 'RNSScreen': react-native-screens' own inner Screen never carries a presentation prop
|
|
186
|
-
// either, since it exists purely to host the header, not to be modally presented itself.
|
|
187
|
-
// A modal/formSheet screen has no UINavigationController of its own on iOS - nest an inner
|
|
188
|
-
// RNSScreenStack/RNSScreen purely to host the native header bar (see isHeaderInModal's
|
|
189
|
-
// comment in core/render-stack.ts). Skipping this leaves RNSScreenStackHeaderConfig with no
|
|
190
|
-
// navigation controller to attach to, so the header silently never renders.
|
|
191
|
-
return plan.inModal
|
|
192
|
-
? createElement(plan.screenViewName, { key: route.key, ...plan.screenProps }, createElement(RNS_SCREEN_STACK_VIEW_NAME, { style: plan.innerStackStyle }, createElement(RNS_SCREEN_VIEW_NAME,
|
|
193
|
-
// activityState mirrors the outer Screen's own value - react-native-screens'
|
|
194
|
-
// RNSScreen.mm treats an unset/inactive nested screen as not yet pushed, leaving it
|
|
195
|
-
// parked at its pre-push transition position (off past the bottom edge) instead of
|
|
196
|
-
// its real, presented frame.
|
|
197
|
-
{ style: plan.innerScreenStyle, activityState: plan.activityState }, descriptorToReact(plan.headerConfig), content)))
|
|
198
|
-
: createElement(plan.screenViewName, { key: route.key, ...plan.screenProps }, descriptorToReact(plan.headerConfig), content);
|
|
199
|
-
});
|
|
200
|
-
return createElement(RNS_SCREEN_STACK_VIEW_NAME, stackProps, ...children);
|
|
201
|
-
});
|
|
202
|
-
export const Stack = Object.assign(StackImpl, { Screen });
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { FC, ReactElement } from 'react';
|
|
2
|
-
import type { IRoute, ITabOptions } from '../core';
|
|
3
|
-
import type { ITabNavigatorHandle } from '../core';
|
|
4
|
-
export type ITabScreenOptionsArgs = {
|
|
5
|
-
route: IRoute<unknown>;
|
|
6
|
-
navigation: ITabNavigatorHandle;
|
|
7
|
-
};
|
|
8
|
-
export type ITabScreenOptionsResolver = (args: ITabScreenOptionsArgs) => ITabOptions;
|
|
9
|
-
export type ITabScreenProps = {
|
|
10
|
-
name: string;
|
|
11
|
-
component: FC;
|
|
12
|
-
options?: ITabOptions | ITabScreenOptionsResolver;
|
|
13
|
-
initialParams?: unknown;
|
|
14
|
-
};
|
|
15
|
-
export declare function TabScreen(_props: ITabScreenProps): ReactElement | null;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
// Tab.Screen: a declarative marker, never rendered on its own - Tab reads its props via
|
|
2
|
-
// React.Children to build the static name -> {component, options} registry, then mounts the
|
|
3
|
-
// FOCUSED route's component itself. Mirrors react/screen.ts's Screen (Stack's twin) minus the
|
|
4
|
-
// stack-only concepts (no push/pop lifecycle events to wire); see CLAUDE.md
|
|
5
|
-
// <third_party_rn_packages_are_react_only> - this and Tab import nothing from react-native.
|
|
6
|
-
export function TabScreen(_props) {
|
|
7
|
-
return null;
|
|
8
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
2
|
-
import type { ITabNavigatorHandle, ITabOptions } from '../../core';
|
|
3
|
-
import { TabScreen } from '../tab-screen';
|
|
4
|
-
export type { ITabNavigatorHandle } from '../../core';
|
|
5
|
-
export type ITabProps = {
|
|
6
|
-
initialRouteName?: string;
|
|
7
|
-
screenOptions?: ITabOptions;
|
|
8
|
-
children?: ReactNode;
|
|
9
|
-
};
|
|
10
|
-
export declare const Tab: import("react").ForwardRefExoticComponent<ITabProps & import("react").RefAttributes<ITabNavigatorHandle>> & {
|
|
11
|
-
Screen: typeof TabScreen;
|
|
12
|
-
};
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
// Tab, the React lifecycle half. The focused-index router (tab-router-state) and the tab-bar
|
|
2
|
-
// Descriptor builder (render-tabs) live in @symbiote-native/navigation core, shared verbatim
|
|
3
|
-
// with the Vue/Angular adapters; here React supplies the lifecycle - useReducer for the focused
|
|
4
|
-
// index, useId for route-key generation, useImperativeHandle for the jumpTo/setParams handle -
|
|
5
|
-
// plus the descriptor bridge for the tab-bar leaf, exactly like Stack bridges its header config
|
|
6
|
-
// (react/stack.ts). Unlike Stack, a bottom-tabs bar is a PURE-JS UI: it paints ordinary
|
|
7
|
-
// `symbiote-view`/`symbiote-text` primitives via the shared render fn, so there is no
|
|
8
|
-
// react-native-screens ViewConfig to register here - Tab needs no `../register` import.
|
|
9
|
-
import { createElement, forwardRef, isValidElement, useCallback, useContext, useEffect, useId, useImperativeHandle, useMemo, useReducer, } from 'react';
|
|
10
|
-
import { descriptorToReact } from '@symbiote-native/react';
|
|
11
|
-
import { dlog } from '@symbiote-native/engine';
|
|
12
|
-
import { NAVIGATION_EVENT_BLUR, NAVIGATION_EVENT_FOCUS, createInitialTabState, createNavigationEmitter, isFocusedRoute, renderTabBar, tabRouterReducer, } from '../../core/index.js';
|
|
13
|
-
import { collectRegistry } from '../collect-registry.js';
|
|
14
|
-
import { NavigationContext } from '../navigation-context.js';
|
|
15
|
-
import { TabScreen } from '../tab-screen.js';
|
|
16
|
-
function isTabScreenElement(child) {
|
|
17
|
-
return isValidElement(child) && child.type === TabScreen;
|
|
18
|
-
}
|
|
19
|
-
function resolveTabOptions(entry, optionsArgs, screenOptions) {
|
|
20
|
-
const own = typeof entry.options === 'function' ? entry.options(optionsArgs) : entry.options;
|
|
21
|
-
return { ...screenOptions, ...own };
|
|
22
|
-
}
|
|
23
|
-
const TAB_CONTENT_STYLE = { flex: 1 };
|
|
24
|
-
const TAB_ROOT_STYLE = { flex: 1 };
|
|
25
|
-
const TabImpl = forwardRef((props, forwardedRef) => {
|
|
26
|
-
// Read BEFORE establishing this Tab's own Context value below - becomes the `parent` link a
|
|
27
|
-
// nested screen's useNavigation().getParent() walks (e.g. this Tab rendered as a Stack screen's
|
|
28
|
-
// content reaches that Stack via this value). undefined when this Tab is the nesting root.
|
|
29
|
-
const ambientContext = useContext(NavigationContext);
|
|
30
|
-
const registry = useMemo(() => collectRegistry(props.children, isTabScreenElement), [props.children]);
|
|
31
|
-
const routeIdPrefix = useId();
|
|
32
|
-
const routes = useMemo(() => Array.from(registry.entries()).map(([name, entry]) => ({
|
|
33
|
-
key: `${routeIdPrefix}-${name}`,
|
|
34
|
-
name,
|
|
35
|
-
params: entry.initialParams,
|
|
36
|
-
})), [registry, routeIdPrefix]);
|
|
37
|
-
const [state, dispatch] = useReducer(tabRouterReducer, undefined, () => createInitialTabState(routes, props.initialRouteName));
|
|
38
|
-
if (routes.length === 0)
|
|
39
|
-
dlog('Tab: no <Tab.Screen> children registered');
|
|
40
|
-
const jumpTo = useCallback((name, params) => dispatch({ type: 'jumpTo', name, params }), []);
|
|
41
|
-
const setParams = useCallback((params, key) => dispatch({ type: 'setParams', key, params }), []);
|
|
42
|
-
const handle = useMemo(() => ({ jumpTo, setParams }), [jumpTo, setParams]);
|
|
43
|
-
useImperativeHandle(forwardedRef, () => handle, [handle]);
|
|
44
|
-
const focusedRoute = state.routes[state.index];
|
|
45
|
-
const items = state.routes.map((route, index) => {
|
|
46
|
-
const entry = registry.get(route.name);
|
|
47
|
-
const focused = isFocusedRoute(index, state.index);
|
|
48
|
-
if (!entry) {
|
|
49
|
-
dlog(`Tab: no screen registered for route name "${route.name}"`);
|
|
50
|
-
return {
|
|
51
|
-
key: route.key,
|
|
52
|
-
focused,
|
|
53
|
-
label: route.name,
|
|
54
|
-
passthrough: {},
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
const options = resolveTabOptions(entry, { route, navigation: handle }, props.screenOptions);
|
|
58
|
-
return {
|
|
59
|
-
key: route.key,
|
|
60
|
-
focused,
|
|
61
|
-
label: options.tabBarLabel ?? options.title ?? route.name,
|
|
62
|
-
icon: options.tabBarIcon,
|
|
63
|
-
badge: options.tabBarBadge,
|
|
64
|
-
activeTintColor: options.tabBarActiveTintColor,
|
|
65
|
-
inactiveTintColor: options.tabBarInactiveTintColor,
|
|
66
|
-
passthrough: {
|
|
67
|
-
onPress: () => jumpTo(route.name),
|
|
68
|
-
accessibilityRole: 'tab',
|
|
69
|
-
accessibilityState: { selected: focused },
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
});
|
|
73
|
-
const focusedEntry = focusedRoute ? registry.get(focusedRoute.name) : undefined;
|
|
74
|
-
const focusedOptions = focusedEntry && focusedRoute
|
|
75
|
-
? resolveTabOptions(focusedEntry, { route: focusedRoute, navigation: handle }, props.screenOptions)
|
|
76
|
-
: props.screenOptions;
|
|
77
|
-
const tabBar = descriptorToReact(renderTabBar({ items, style: focusedOptions?.tabBarStyle, passthrough: {} }));
|
|
78
|
-
// Only the focused route's screen is ever mounted (unlike Stack, which keeps every pushed route
|
|
79
|
-
// alive), so a fresh emitter per focus change is sufficient - no per-route emitter map to prune,
|
|
80
|
-
// because the previous screen's whole subtree (and any listeners it registered) is torn down by
|
|
81
|
-
// an ordinary React unmount when focus moves on. Keyed on the route KEY rather than the route
|
|
82
|
-
// object so a setParams-only change (new route object, same key) doesn't spuriously re-fire
|
|
83
|
-
// focus/blur.
|
|
84
|
-
const focusedRouteKey = focusedRoute?.key;
|
|
85
|
-
const routeEmitter = useMemo(() => createNavigationEmitter(), [focusedRouteKey]);
|
|
86
|
-
// Tab paints its own bar in pure JS - there is no native onAppear/onDisappear to hook (unlike
|
|
87
|
-
// Stack's RNSScreen), so focus/blur is synthesized here: mount = focus, cleanup = blur, exactly
|
|
88
|
-
// what an effect keyed on focusedRouteKey already encodes - no diffFocusedRoute indirection
|
|
89
|
-
// needed (unlike Vue/Angular, which diff real prev/next keys inside an imperative watch/CD
|
|
90
|
-
// callback that has no mount/cleanup pairing of its own).
|
|
91
|
-
useEffect(() => {
|
|
92
|
-
if (focusedRouteKey === undefined)
|
|
93
|
-
return undefined;
|
|
94
|
-
dlog(`Tab: route "${focusedRoute?.name}" focused`);
|
|
95
|
-
routeEmitter.emit(NAVIGATION_EVENT_FOCUS);
|
|
96
|
-
return () => {
|
|
97
|
-
dlog(`Tab: route "${focusedRoute?.name}" blurred`);
|
|
98
|
-
routeEmitter.emit(NAVIGATION_EVENT_BLUR);
|
|
99
|
-
};
|
|
100
|
-
// focusedRoute omitted deliberately: only its .key (tracked via focusedRouteKey) should
|
|
101
|
-
// re-run this effect - see the comment above on focusedRouteKey.
|
|
102
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
103
|
-
}, [routeEmitter, focusedRouteKey]);
|
|
104
|
-
const content = focusedEntry && focusedRoute
|
|
105
|
-
? createElement(NavigationContext.Provider, {
|
|
106
|
-
value: {
|
|
107
|
-
route: focusedRoute,
|
|
108
|
-
navigation: handle,
|
|
109
|
-
emitter: routeEmitter,
|
|
110
|
-
parent: ambientContext,
|
|
111
|
-
},
|
|
112
|
-
}, createElement(focusedEntry.component))
|
|
113
|
-
: null;
|
|
114
|
-
return createElement('symbiote-view', { style: TAB_ROOT_STYLE }, createElement('symbiote-view', { style: TAB_CONTENT_STYLE }, content), tabBar);
|
|
115
|
-
});
|
|
116
|
-
export const Tab = Object.assign(TabImpl, { Screen: TabScreen });
|
package/build/register.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import 'react-native-screens/src/fabric/ScreenNativeComponent';
|
|
2
|
-
import 'react-native-screens/src/fabric/ModalScreenNativeComponent';
|
|
3
|
-
import 'react-native-screens/src/fabric/ScreenStackNativeComponent';
|
|
4
|
-
import 'react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent';
|
|
5
|
-
import 'react-native-screens/src/fabric/ScreenStackHeaderSubviewNativeComponent';
|
|
6
|
-
import 'react-native-screens/src/fabric/SearchBarNativeComponent';
|
|
7
|
-
import 'react-native-screens/src/fabric/ScreenContentWrapperNativeComponent';
|
package/build/register.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
// Side-effect: register RNSScreen/RNSScreenStack/RNSScreenStackHeaderConfig/
|
|
2
|
-
// RNSScreenStackHeaderSubview/RNSSearchBar/RNSScreenContentWrapper's ViewConfigs in RN's
|
|
3
|
-
// ReactNativeViewConfigRegistry. Evaluating each codegen native-component spec
|
|
4
|
-
// (codegenNativeComponent('RNSScreen', ...) etc.) registers the getter the engine reads via
|
|
5
|
-
// setNativeViewConfigSource, so events + color processors derive on first commit. We import the
|
|
6
|
-
// codegen SPECS, never react-native-screens' React components (ScreenStack.tsx et al - hooks,
|
|
7
|
-
// crashes a non-React adapter) - these modules have no hooks, so they are framework-agnostic and
|
|
8
|
-
// shared by every adapter entry. Each adapter barrel imports this; the component modules and
|
|
9
|
-
// their headless tests stay free of it. See CLAUDE.md <third_party_rn_packages_are_react_only>.
|
|
10
|
-
//
|
|
11
|
-
// RNSSearchBar (headerSearchBarOptions' native leaf, nested by render-stack.ts's
|
|
12
|
-
// renderHeaderConfig) was missing from this list - its ViewConfig was never registered, so Fabric
|
|
13
|
-
// had no component descriptor for 'RNSSearchBar' and crashed on mount the moment a screen set
|
|
14
|
-
// headerSearchBarOptions. That is the root cause of the search-bar crash, not a deeper
|
|
15
|
-
// Fabric-interop gap.
|
|
16
|
-
//
|
|
17
|
-
// RNSScreenStackHeaderSubview (the wrapper renderHeaderConfig nests the search bar leaf inside,
|
|
18
|
-
// per RNSScreenStackHeaderConfig.mm's mountChildComponentView requiring every header child to be
|
|
19
|
-
// one) needs the same registration, or the same class of mount crash follows.
|
|
20
|
-
//
|
|
21
|
-
// RNSScreenContentWrapper (the RNS_SCREEN_CONTENT_WRAPPER_VIEW_NAME wrapper stack.ts wraps every
|
|
22
|
-
// screen's content in - see its comment in core/constants.ts) additionally needs a NATIVE-side
|
|
23
|
-
// registration: this file alone is not enough. The native iOS registry
|
|
24
|
-
// (RCTThirdPartyComponentsProvider.mm, generated by codegen from THIS package's own
|
|
25
|
-
// package.json#codegenConfig.ios.componentProvider map, NOT from react-native-screens') has no
|
|
26
|
-
// entry for 'RNSScreenContentWrapper' by default, so without it Fabric falls back to its
|
|
27
|
-
// "Unimplemented component" placeholder view regardless of this file's import. Both registries
|
|
28
|
-
// are independently required for a never-before-used react-native-screens view: this file (JS
|
|
29
|
-
// ViewConfig, read by @symbiote-native/engine's registry.ts) and package.json's componentProvider
|
|
30
|
-
// map (native ComponentDescriptor, read by codegen at `pod install` time).
|
|
31
|
-
//
|
|
32
|
-
// RNSModalScreen's gap on both registries isn't a mount crash but a silent mispositioning bug.
|
|
33
|
-
// react-native-screens' own Screen.tsx picks
|
|
34
|
-
// ModalScreenNativeComponent ('RNSModalScreen') instead of ScreenNativeComponent ('RNSScreen')
|
|
35
|
-
// whenever stackPresentation isn't push/containedModal/containedTransparentModal, because
|
|
36
|
-
// RNSScreen.mm's updateLayoutMetrics: checks `[self isKindOfClass:RNSModalScreen.class]` and, for
|
|
37
|
-
// that class, DELIBERATELY skips applying Yoga's computed frame (its own comment: "the available
|
|
38
|
-
// space is most likely restricted & differs from what Yoga resolves during first layout - we want
|
|
39
|
-
// to rely on native layout here"). Emitting a plain 'RNSScreen' for a formSheet/modal presentation
|
|
40
|
-
// (as resolveScreenViewName below used to do unconditionally) means that check never matches, so
|
|
41
|
-
// Yoga's frame - computed as if the screen were still a normal flexed sibling in its outer
|
|
42
|
-
// RNSScreenStack's push-oriented column layout - gets applied verbatim and never corrected,
|
|
43
|
-
// visually shifting the whole modal down by one full screen's height. Fixed in
|
|
44
|
-
// core/render-stack.ts's resolveScreenViewName, which now mirrors Screen.tsx's own
|
|
45
|
-
// shouldUseModalScreenComponent condition exactly.
|
|
46
|
-
//
|
|
47
|
-
// Unlike @symbiote-native/slider (which deep-imports @react-native-community/slider's compiled
|
|
48
|
-
// `dist/`), react-native-screens declares `"react-native": "src/index"` in its package.json -
|
|
49
|
-
// Metro's resolverMainFields prioritizes that field, so it resolves the library straight to raw
|
|
50
|
-
// TypeScript source and transforms it like first-party code. The deep import below targets that
|
|
51
|
-
// same `src/fabric/` tree rather than a compiled output.
|
|
52
|
-
import 'react-native-screens/src/fabric/ScreenNativeComponent';
|
|
53
|
-
import 'react-native-screens/src/fabric/ModalScreenNativeComponent';
|
|
54
|
-
import 'react-native-screens/src/fabric/ScreenStackNativeComponent';
|
|
55
|
-
import 'react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent';
|
|
56
|
-
import 'react-native-screens/src/fabric/ScreenStackHeaderSubviewNativeComponent';
|
|
57
|
-
import 'react-native-screens/src/fabric/SearchBarNativeComponent';
|
|
58
|
-
import 'react-native-screens/src/fabric/ScreenContentWrapperNativeComponent';
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export { useNavigation } from './use-navigation';
|
|
2
|
-
export type { INavigationHandle } from './use-navigation';
|
|
3
|
-
export { useStackNavigation } from './use-stack-navigation';
|
|
4
|
-
export type { IStackNavigationHandle } from './use-stack-navigation';
|
|
5
|
-
export { useTabNavigation } from './use-tab-navigation';
|
|
6
|
-
export type { ITabNavigationHandle } from './use-tab-navigation';
|
|
7
|
-
export { useDrawerNavigation } from './use-drawer-navigation';
|
|
8
|
-
export type { IDrawerNavigationHandle } from './use-drawer-navigation';
|
|
9
|
-
export { useRoute } from './use-route';
|
|
10
|
-
export { useIsFocused } from './use-is-focused';
|
|
11
|
-
export { useFocusEffect } from './use-focus-effect';
|
|
12
|
-
export { useNavigationState } from './use-navigation-state';
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export { useNavigation } from './use-navigation.js';
|
|
2
|
-
export { useStackNavigation } from './use-stack-navigation.js';
|
|
3
|
-
export { useTabNavigation } from './use-tab-navigation.js';
|
|
4
|
-
export { useDrawerNavigation } from './use-drawer-navigation.js';
|
|
5
|
-
export { useRoute } from './use-route.js';
|
|
6
|
-
export { useIsFocused } from './use-is-focused.js';
|
|
7
|
-
export { useFocusEffect } from './use-focus-effect.js';
|
|
8
|
-
export { useNavigationState } from './use-navigation-state.js';
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import type { ComputedRef } from '@vue/runtime-core';
|
|
2
|
-
import type { IDrawerNavigatorHandle } from '../../core';
|
|
3
|
-
import type { INavigationHandle } from './use-navigation';
|
|
4
|
-
export type IDrawerNavigationHandle = IDrawerNavigatorHandle & Pick<INavigationHandle, 'addListener' | 'getParent'>;
|
|
5
|
-
export declare function useDrawerNavigation(): ComputedRef<IDrawerNavigationHandle>;
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
// Twin of use-stack-navigation.ts, narrowing to a Drawer handle instead.
|
|
2
|
-
import { computed } from '@vue/runtime-core';
|
|
3
|
-
import { isDrawerNavigatorHandle } from '../../core/index.js';
|
|
4
|
-
import { useNavigation } from './use-navigation.js';
|
|
5
|
-
export function useDrawerNavigation() {
|
|
6
|
-
const navigation = useNavigation();
|
|
7
|
-
return computed(() => {
|
|
8
|
-
const value = navigation.value;
|
|
9
|
-
if (!isDrawerNavigatorHandle(value)) {
|
|
10
|
-
throw new Error('useDrawerNavigation() was called from a component whose nearest navigator is not a Drawer. ' +
|
|
11
|
-
'Use useNavigation() instead if this component can render under more than one navigator kind.');
|
|
12
|
-
}
|
|
13
|
-
return value;
|
|
14
|
-
});
|
|
15
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function useFocusEffect(effect: () => (() => void) | void): void;
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
// Mirrors @react-navigation's useFocusEffect; Vue twin of react/hooks/use-focus-effect.ts.
|
|
2
|
-
// React's version requires memoizing `effect` (useCallback) since a new identity re-subscribes
|
|
3
|
-
// it like any other useEffect dependency. That doesn't apply here: Vue's setup() runs once, so
|
|
4
|
-
// `effect` is read once at composable-call time and closed over directly by onMounted/onUnmounted -
|
|
5
|
-
// there's no dependency array to go stale.
|
|
6
|
-
import { onMounted, onUnmounted } from '@vue/runtime-core';
|
|
7
|
-
import { NAVIGATION_EVENT_BLUR, NAVIGATION_EVENT_FOCUS } from '../../core/index.js';
|
|
8
|
-
import { requireNavigationScope } from '../navigation-context.js';
|
|
9
|
-
export function useFocusEffect(effect) {
|
|
10
|
-
const scope = requireNavigationScope('useFocusEffect');
|
|
11
|
-
let cleanup;
|
|
12
|
-
let unsubscribeFocus;
|
|
13
|
-
let unsubscribeBlur;
|
|
14
|
-
const runEffect = () => {
|
|
15
|
-
cleanup = effect();
|
|
16
|
-
};
|
|
17
|
-
const runCleanup = () => {
|
|
18
|
-
cleanup?.();
|
|
19
|
-
cleanup = undefined;
|
|
20
|
-
};
|
|
21
|
-
onMounted(() => {
|
|
22
|
-
const { emitter } = scope.value;
|
|
23
|
-
unsubscribeFocus = emitter.addListener(NAVIGATION_EVENT_FOCUS, runEffect);
|
|
24
|
-
unsubscribeBlur = emitter.addListener(NAVIGATION_EVENT_BLUR, runCleanup);
|
|
25
|
-
});
|
|
26
|
-
onUnmounted(() => {
|
|
27
|
-
unsubscribeFocus?.();
|
|
28
|
-
unsubscribeBlur?.();
|
|
29
|
-
runCleanup();
|
|
30
|
-
});
|
|
31
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
// Mirrors @react-navigation's useIsFocused; Vue twin of react/hooks/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.ts) or the screen mounts focused (tabs.ts/drawer.ts) -
|
|
4
|
-
// a screen genuinely isn't focused at the instant it mounts, the same async gap real native
|
|
5
|
-
// transitions have.
|
|
6
|
-
import { onMounted, onUnmounted, ref } from '@vue/runtime-core';
|
|
7
|
-
import { NAVIGATION_EVENT_BLUR, NAVIGATION_EVENT_FOCUS } from '../../core/index.js';
|
|
8
|
-
import { requireNavigationScope } from '../navigation-context.js';
|
|
9
|
-
export function useIsFocused() {
|
|
10
|
-
const scope = requireNavigationScope('useIsFocused');
|
|
11
|
-
const isFocused = ref(false);
|
|
12
|
-
let unsubscribeFocus;
|
|
13
|
-
let unsubscribeBlur;
|
|
14
|
-
onMounted(() => {
|
|
15
|
-
const { emitter } = scope.value;
|
|
16
|
-
unsubscribeFocus = emitter.addListener(NAVIGATION_EVENT_FOCUS, () => {
|
|
17
|
-
isFocused.value = true;
|
|
18
|
-
});
|
|
19
|
-
unsubscribeBlur = emitter.addListener(NAVIGATION_EVENT_BLUR, () => {
|
|
20
|
-
isFocused.value = false;
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
onUnmounted(() => {
|
|
24
|
-
unsubscribeFocus?.();
|
|
25
|
-
unsubscribeBlur?.();
|
|
26
|
-
});
|
|
27
|
-
return isFocused;
|
|
28
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// Mirrors @react-navigation's useNavigationState; Vue twin of react/hooks/use-navigation-state.ts.
|
|
2
|
-
// The reducer/dispatch machinery lives in core/navigator-state.ts (tab-router-state.ts /
|
|
3
|
-
// drawer-router-state.ts for the other two navigators) - this composable only wires the
|
|
4
|
-
// subscription to the `state` broadcast each render loop re-emits after commit.
|
|
5
|
-
//
|
|
6
|
-
// Seeded from a single-route snapshot ({ routes: [route] }) rather than left undefined: the real
|
|
7
|
-
// broadcast lands after mount (Stack's render loop; tabs.ts/drawer.ts emit on focus), so a
|
|
8
|
-
// selector reading e.g. `state.routes.at(-1)?.name` still resolves correctly on first paint for
|
|
9
|
-
// the common single-route case - the same async gap useIsFocused documents.
|
|
10
|
-
import { onMounted, onUnmounted, shallowRef } from '@vue/runtime-core';
|
|
11
|
-
import { NAVIGATION_EVENT_STATE, isRecord } from '../../core/index.js';
|
|
12
|
-
import { requireNavigationScope } from '../navigation-context.js';
|
|
13
|
-
function isNavigatorState(value) {
|
|
14
|
-
return isRecord(value) && Array.isArray(value.routes);
|
|
15
|
-
}
|
|
16
|
-
export function useNavigationState(selector) {
|
|
17
|
-
const scope = requireNavigationScope('useNavigationState');
|
|
18
|
-
const result = shallowRef(selector({ routes: [scope.value.route] }));
|
|
19
|
-
let unsubscribe;
|
|
20
|
-
onMounted(() => {
|
|
21
|
-
unsubscribe = scope.value.emitter.addListener(NAVIGATION_EVENT_STATE, (state) => {
|
|
22
|
-
if (!isNavigatorState(state))
|
|
23
|
-
return;
|
|
24
|
-
result.value = selector(state);
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
onUnmounted(() => unsubscribe?.());
|
|
28
|
-
return result;
|
|
29
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { ComputedRef } from '@vue/runtime-core';
|
|
2
|
-
import type { INavigationEventListener, INavigationEventName } from '../../core';
|
|
3
|
-
import type { IAnyNavigatorHandle } from '../navigation-context';
|
|
4
|
-
export type INavigationHandle = IAnyNavigatorHandle & {
|
|
5
|
-
addListener: (event: INavigationEventName, listener: INavigationEventListener) => () => void;
|
|
6
|
-
getParent: () => IAnyNavigatorHandle | undefined;
|
|
7
|
-
};
|
|
8
|
-
export declare function useNavigation(): ComputedRef<INavigationHandle>;
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// Mirrors @react-navigation's `navigation.addListener('focus', cb)` surface; Vue twin of
|
|
2
|
-
// react/hooks/use-navigation.ts (returns a ComputedRef, Vue's reactive-primitive convention,
|
|
3
|
-
// instead of a plain value). All pub/sub logic lives in ../../core/navigation-events - this
|
|
4
|
-
// composable only reads the injected navigation scope and binds identity.
|
|
5
|
-
import { computed } from '@vue/runtime-core';
|
|
6
|
-
import { requireNavigationScope } from '../navigation-context.js';
|
|
7
|
-
export function useNavigation() {
|
|
8
|
-
const scope = requireNavigationScope('useNavigation');
|
|
9
|
-
return computed(() => {
|
|
10
|
-
const { navigation, emitter, parent } = scope.value;
|
|
11
|
-
return { ...navigation, addListener: emitter.addListener, getParent: () => parent?.navigation };
|
|
12
|
-
});
|
|
13
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
// Mirrors @react-navigation's useRoute; Vue twin of react/hooks/use-route.ts (returns a
|
|
2
|
-
// ComputedRef instead of a plain value). Zero logic of its own - the route object itself is built
|
|
3
|
-
// by stack.ts's/tabs.ts's/drawer.ts's render loop from the shared core reducer's state.
|
|
4
|
-
import { computed } from '@vue/runtime-core';
|
|
5
|
-
import { requireNavigationScope } from '../navigation-context.js';
|
|
6
|
-
export function useRoute() {
|
|
7
|
-
const scope = requireNavigationScope('useRoute');
|
|
8
|
-
return computed(() => scope.value.route);
|
|
9
|
-
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import type { ComputedRef } from '@vue/runtime-core';
|
|
2
|
-
import type { INavigatorHandle } from '../../core';
|
|
3
|
-
import type { INavigationHandle } from './use-navigation';
|
|
4
|
-
export type IStackNavigationHandle = INavigatorHandle & Pick<INavigationHandle, 'addListener' | 'getParent'>;
|
|
5
|
-
export declare function useStackNavigation(): ComputedRef<IStackNavigationHandle>;
|