@symbiote-native/navigation 2.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/package.json +11 -10
- 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
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
// Per-tab agnostic options. Mirrors @react-navigation/bottom-tabs' `options` surface (title,
|
|
2
|
-
// tabBarLabel, tabBarIcon, tabBarBadge, tint colors, tabBarStyle) minus anything that requires a
|
|
3
|
-
// framework element: `tabBarIcon` there is a render prop returning a platform icon element, but
|
|
4
|
-
// per <prop_types_split_agnostic_vs_per_adapter> a render-callback-returning-a-framework-element
|
|
5
|
-
// field can't live in the shared agnostic type, so here it is EITHER a pre-built `IDescriptor`
|
|
6
|
-
// (an adapter can bridge any host node it already resolved, image or vector icon) or a bare icon
|
|
7
|
-
// NAME string the renderer paints as a label-style glyph - both framework-agnostic. An adapter
|
|
8
|
-
// wanting a `(props) => ReactNode`-style callback resolves it to one of these two forms itself,
|
|
9
|
-
// same split as IPressableProps's per-adapter children.
|
|
10
|
-
export {};
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { IRoute } from '../navigator-state';
|
|
2
|
-
export type { IRoute } from '../navigator-state';
|
|
3
|
-
type IRouteEntry = IRoute<unknown>;
|
|
4
|
-
export type ITabRouterState = Readonly<{
|
|
5
|
-
routes: readonly IRouteEntry[];
|
|
6
|
-
index: number;
|
|
7
|
-
}>;
|
|
8
|
-
export type ITabRouterAction = {
|
|
9
|
-
type: 'jumpTo';
|
|
10
|
-
name: string;
|
|
11
|
-
params?: unknown;
|
|
12
|
-
} | {
|
|
13
|
-
type: 'setParams';
|
|
14
|
-
key: string;
|
|
15
|
-
params: unknown;
|
|
16
|
-
};
|
|
17
|
-
export declare function createInitialTabState(routes: readonly IRouteEntry[], initialRouteName?: string): ITabRouterState;
|
|
18
|
-
export declare function tabRouterReducer(state: ITabRouterState, action: ITabRouterAction): ITabRouterState;
|
|
19
|
-
export declare function isFocusedRoute(index: number, focusedIndex: number): boolean;
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
// TabRouter: the logic half (framework-agnostic, zero render). A pure reducer over a FOCUSED
|
|
2
|
-
// INDEX into a fixed route list - jumpTo/setParams - mirroring what @react-navigation/routers'
|
|
3
|
-
// TabRouter keeps, minus the React-specific action-creator/linking machinery. The key structural
|
|
4
|
-
// difference from navigator-state's Stack: tabs never push/pop a route, they move a focused
|
|
5
|
-
// index between a fixed set (mirrors TabRouter.getInitialState / getStateForAction for
|
|
6
|
-
// JUMP_TO - see the tab-actions docs: jumpTo takes a route NAME, not a stack push). Route
|
|
7
|
-
// identity (`key`) is generated by the CALLER, same convention as navigator-state.ts - reused
|
|
8
|
-
// verbatim from there so Stack and Tabs share one route shape.
|
|
9
|
-
import { mergeParams } from '../guards.js';
|
|
10
|
-
const INITIAL_FOCUSED_INDEX = 0;
|
|
11
|
-
export function createInitialTabState(routes, initialRouteName) {
|
|
12
|
-
if (initialRouteName === undefined)
|
|
13
|
-
return { routes, index: INITIAL_FOCUSED_INDEX };
|
|
14
|
-
const index = routes.findIndex(route => route.name === initialRouteName);
|
|
15
|
-
return { routes, index: index === -1 ? INITIAL_FOCUSED_INDEX : index };
|
|
16
|
-
}
|
|
17
|
-
// jumpTo moves the focused index to an existing route by name and optionally merges params
|
|
18
|
-
// (navigation.jumpTo(name, params) - the tab-actions doc's own signature); it never adds,
|
|
19
|
-
// removes, or reorders routes (mirrors TabRouter: JUMP_TO is a no-op when the target name isn't
|
|
20
|
-
// registered, same as navigatorReducer's popTo bailing on an unknown key). setParams merges into
|
|
21
|
-
// the target route's params by key (CommonActions.setParams semantics - a merge, not a replace).
|
|
22
|
-
export function tabRouterReducer(state, action) {
|
|
23
|
-
switch (action.type) {
|
|
24
|
-
case 'jumpTo': {
|
|
25
|
-
const index = state.routes.findIndex(route => route.name === action.name);
|
|
26
|
-
if (index === -1)
|
|
27
|
-
return state;
|
|
28
|
-
if (action.params === undefined) {
|
|
29
|
-
return index === state.index ? state : { ...state, index };
|
|
30
|
-
}
|
|
31
|
-
const target = state.routes[index];
|
|
32
|
-
const routes = state.routes.slice();
|
|
33
|
-
routes[index] = { ...target, params: mergeParams(target.params, action.params) };
|
|
34
|
-
return { ...state, index, routes };
|
|
35
|
-
}
|
|
36
|
-
case 'setParams': {
|
|
37
|
-
const index = state.routes.findIndex(route => route.key === action.key);
|
|
38
|
-
if (index === -1)
|
|
39
|
-
return state;
|
|
40
|
-
const target = state.routes[index];
|
|
41
|
-
const routes = state.routes.slice();
|
|
42
|
-
routes[index] = { ...target, params: mergeParams(target.params, action.params) };
|
|
43
|
-
return { ...state, routes };
|
|
44
|
-
}
|
|
45
|
-
default:
|
|
46
|
-
return state;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
export function isFocusedRoute(index, focusedIndex) {
|
|
50
|
-
return index === focusedIndex;
|
|
51
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
// Shared by stack.ts/tabs.ts/drawer.ts: each navigator registers its own Screen marker
|
|
2
|
-
// component's props into a name -> entry map via React.Children, differing only in the marker
|
|
3
|
-
// component (isMarkerElement) and the props shape (TProps). Stays in react/ rather than core/:
|
|
4
|
-
// Children/isValidElement are React-only APIs (see CLAUDE.md <adapter_src_follows_framework_idioms>).
|
|
5
|
-
import { Children } from 'react';
|
|
6
|
-
export function collectRegistry(children, isMarkerElement) {
|
|
7
|
-
const registry = new Map();
|
|
8
|
-
Children.forEach(children, child => {
|
|
9
|
-
if (!isMarkerElement(child))
|
|
10
|
-
return;
|
|
11
|
-
const { name, ...entry } = child.props;
|
|
12
|
-
registry.set(name, entry);
|
|
13
|
-
});
|
|
14
|
-
return registry;
|
|
15
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
2
|
-
import { type IStyleProp, type IViewStyle } from '@symbiote-native/engine';
|
|
3
|
-
import type { IDrawerDescriptorMap, IDrawerNavigatorHandle, IDrawerOptions, IDrawerRouterState, IDrawerScreenOptions } from '../../core';
|
|
4
|
-
import { DrawerScreen } from '../drawer-screen';
|
|
5
|
-
export type { IDrawerNavigatorHandle, IDrawerDescriptorMap } from '../../core';
|
|
6
|
-
export type IDrawerProps = IDrawerOptions & {
|
|
7
|
-
initialRouteName?: string;
|
|
8
|
-
screenOptions?: IDrawerScreenOptions;
|
|
9
|
-
drawerStyle?: IStyleProp<IViewStyle>;
|
|
10
|
-
renderDrawerContent?: (props: {
|
|
11
|
-
state: IDrawerRouterState;
|
|
12
|
-
descriptors: IDrawerDescriptorMap;
|
|
13
|
-
navigation: IDrawerNavigatorHandle;
|
|
14
|
-
}) => ReactNode;
|
|
15
|
-
children?: ReactNode;
|
|
16
|
-
};
|
|
17
|
-
export declare const Drawer: import("react").ForwardRefExoticComponent<IDrawerOptions & {
|
|
18
|
-
initialRouteName?: string;
|
|
19
|
-
screenOptions?: IDrawerScreenOptions;
|
|
20
|
-
drawerStyle?: IStyleProp<IViewStyle>;
|
|
21
|
-
renderDrawerContent?: (props: {
|
|
22
|
-
state: IDrawerRouterState;
|
|
23
|
-
descriptors: IDrawerDescriptorMap;
|
|
24
|
-
navigation: IDrawerNavigatorHandle;
|
|
25
|
-
}) => ReactNode;
|
|
26
|
-
children?: ReactNode;
|
|
27
|
-
} & import("react").RefAttributes<IDrawerNavigatorHandle>> & {
|
|
28
|
-
Screen: typeof DrawerScreen;
|
|
29
|
-
};
|
|
@@ -1,277 +0,0 @@
|
|
|
1
|
-
// Drawer, the React lifecycle half. The open/closed + focused-route router (drawer-router-state)
|
|
2
|
-
// and the pure swipe/geometry math (drawer-options) live in @symbiote-native/navigation core,
|
|
3
|
-
// shared verbatim with the Vue/Angular adapters; here React supplies the lifecycle - useReducer
|
|
4
|
-
// for the router, a PanResponder for the swipe gesture (RN's own idiom: build it ONCE via
|
|
5
|
-
// useRef, let its callbacks read current values off refs rather than rebuilding it every render),
|
|
6
|
-
// an Animated.Value driving the slide/opacity transforms, and useImperativeHandle for the
|
|
7
|
-
// open/close/toggle/jumpTo handle - mirroring react/tabs.ts's shape (Tab is the closer sibling:
|
|
8
|
-
// both are fixed-route-list, no-react-native-screens navigators; Stack's push/pop + native-screen
|
|
9
|
-
// bridging don't apply here).
|
|
10
|
-
//
|
|
11
|
-
// FEASIBILITY NOTE (see the drawer skill / PR description for the full writeup): the REAL
|
|
12
|
-
// @react-navigation/drawer is built on react-native-gesture-handler + react-native-reanimated,
|
|
13
|
-
// neither of which this codebase depends on. What's built here reaches the same swipe-to-open/
|
|
14
|
-
// close + front/back/slide/permanent behavior using only PanResponder + Animated (both already in
|
|
15
|
-
// @symbiote-native/engine), which is sufficient for a solid drawer but NOT byte-for-byte parity -
|
|
16
|
-
// see the explicit gap list at the bottom of this file.
|
|
17
|
-
import { createElement, forwardRef, isValidElement, useCallback, useContext, useEffect, useId, useImperativeHandle, useMemo, useReducer, useRef, } from 'react';
|
|
18
|
-
import { Animated, PanResponder, useWindowDimensions } from '@symbiote-native/react';
|
|
19
|
-
import { dlog } from '@symbiote-native/engine';
|
|
20
|
-
import { DRAWER_DEFAULT_OVERLAY_COLOR, NAVIGATION_EVENT_BLUR, NAVIGATION_EVENT_FOCUS, createInitialDrawerRouterState, createNavigationEmitter, drawerChildOrder, drawerRouterReducer, isDrawerAnimated, renderDrawer, resolveDragProgress, resolveDrawerGeometry, resolveDrawerSlotInterpolation, resolveSwipeIntent, shouldClaimDrawerSwipe, } from '../../core/index.js';
|
|
21
|
-
import { collectRegistry } from '../collect-registry.js';
|
|
22
|
-
import { NavigationContext } from '../navigation-context.js';
|
|
23
|
-
import { DrawerScreen } from '../drawer-screen.js';
|
|
24
|
-
function isDrawerScreenElement(child) {
|
|
25
|
-
return isValidElement(child) && child.type === DrawerScreen;
|
|
26
|
-
}
|
|
27
|
-
function resolveDrawerScreenOptions(entry, optionsArgs, screenOptions) {
|
|
28
|
-
const own = typeof entry.options === 'function' ? entry.options(optionsArgs) : entry.options;
|
|
29
|
-
return { ...screenOptions, ...own };
|
|
30
|
-
}
|
|
31
|
-
const DRAWER_SNAP_DURATION = 250;
|
|
32
|
-
const DrawerImpl = forwardRef((props, forwardedRef) => {
|
|
33
|
-
// Read BEFORE establishing this Drawer's own Context value below - becomes the `parent` link a
|
|
34
|
-
// nested screen's useNavigation().getParent() walks (e.g. this Drawer rendered as a Stack
|
|
35
|
-
// screen's content reaches that Stack via this value). undefined when this Drawer is the
|
|
36
|
-
// nesting root.
|
|
37
|
-
const ambientContext = useContext(NavigationContext);
|
|
38
|
-
const { initialRouteName, screenOptions, renderDrawerContent, children, drawerStyle, drawerType, drawerPosition, drawerWidth, overlayColor, swipeEnabled, swipeEdgeWidth, swipeMinDistance, swipeMinVelocity, } = props;
|
|
39
|
-
const options = {
|
|
40
|
-
drawerType,
|
|
41
|
-
drawerPosition,
|
|
42
|
-
drawerWidth,
|
|
43
|
-
overlayColor,
|
|
44
|
-
swipeEnabled,
|
|
45
|
-
swipeEdgeWidth,
|
|
46
|
-
swipeMinDistance,
|
|
47
|
-
swipeMinVelocity,
|
|
48
|
-
};
|
|
49
|
-
const registry = useMemo(() => collectRegistry(children, isDrawerScreenElement), [children]);
|
|
50
|
-
const routeIdPrefix = useId();
|
|
51
|
-
const routes = useMemo(() => Array.from(registry.entries()).map(([name, entry]) => ({
|
|
52
|
-
key: `${routeIdPrefix}-${name}`,
|
|
53
|
-
name,
|
|
54
|
-
params: entry.initialParams,
|
|
55
|
-
})), [registry, routeIdPrefix]);
|
|
56
|
-
const [state, dispatch] = useReducer(drawerRouterReducer, undefined, () => createInitialDrawerRouterState(routes, initialRouteName));
|
|
57
|
-
if (routes.length === 0)
|
|
58
|
-
dlog('Drawer: no <Drawer.Screen> children registered');
|
|
59
|
-
const { width: screenWidth } = useWindowDimensions();
|
|
60
|
-
// progress: 0 closed -> 1 open, the single Animated.Value every slide/opacity transform below
|
|
61
|
-
// interpolates from.
|
|
62
|
-
const progress = useRef(new Animated.Value(state.isOpen ? 1 : 0)).current;
|
|
63
|
-
// Where a drag STARTS from, in progress units. Always exactly 0 or 1: a gesture only ever
|
|
64
|
-
// begins at rest, since terminate/release always snap the value back to a resting state before
|
|
65
|
-
// another grant can fire.
|
|
66
|
-
const dragStartProgress = useRef(0);
|
|
67
|
-
// Refs so the PanResponder's callbacks - built ONCE below, RN's own idiom - always read the
|
|
68
|
-
// CURRENT render's values without forcing a new PanResponder identity on every state/prop
|
|
69
|
-
// change (recreating panHandlers mid-gesture would drop the in-flight touch).
|
|
70
|
-
const optionsRef = useRef(options);
|
|
71
|
-
optionsRef.current = options;
|
|
72
|
-
const screenWidthRef = useRef(screenWidth);
|
|
73
|
-
screenWidthRef.current = screenWidth;
|
|
74
|
-
const isOpenRef = useRef(state.isOpen);
|
|
75
|
-
isOpenRef.current = state.isOpen;
|
|
76
|
-
const animateProgressTo = useCallback((open) => {
|
|
77
|
-
// Investigation instrumentation (Drawer openDrawer-no-op / toggleDrawer-no-animation bug):
|
|
78
|
-
// every imperative caller (openDrawer/closeDrawer/toggleDrawer/jumpTo) funnels through here,
|
|
79
|
-
// so this single seam proves whether Animated.timing is actually being started at all, and
|
|
80
|
-
// with what toValue - a live-only failure (e.g. a stale progress ref, or Animated.timing
|
|
81
|
-
// silently short-circuiting) would show up as this log firing with no visible motion. Kept
|
|
82
|
-
// behind DEBUG per <keep_logs_gate_behind_DEBUG>, never removed.
|
|
83
|
-
dlog(`Drawer: animateProgressTo(open=${open}) starting at t=${Date.now()}`);
|
|
84
|
-
Animated.timing(progress, {
|
|
85
|
-
toValue: open ? 1 : 0,
|
|
86
|
-
duration: DRAWER_SNAP_DURATION,
|
|
87
|
-
// Native-driver wiring (the AnimatedComponent passthrough opt-in ADR 0017 defines) is
|
|
88
|
-
// deferred for v1 - see this file's header feasibility note. The JS timing loop still
|
|
89
|
-
// drives every frame, same as any other non-native-driven Animated.timing in this codebase.
|
|
90
|
-
useNativeDriver: false,
|
|
91
|
-
}).start();
|
|
92
|
-
}, [progress]);
|
|
93
|
-
const handle = useMemo(() => ({
|
|
94
|
-
openDrawer: () => {
|
|
95
|
-
dlog(`Drawer: openDrawer() called, isOpen=${isOpenRef.current} at t=${Date.now()}`);
|
|
96
|
-
animateProgressTo(true);
|
|
97
|
-
dispatch({ type: 'openDrawer' });
|
|
98
|
-
},
|
|
99
|
-
closeDrawer: () => {
|
|
100
|
-
dlog(`Drawer: closeDrawer() called, isOpen=${isOpenRef.current} at t=${Date.now()}`);
|
|
101
|
-
animateProgressTo(false);
|
|
102
|
-
dispatch({ type: 'closeDrawer' });
|
|
103
|
-
},
|
|
104
|
-
toggleDrawer: () => {
|
|
105
|
-
dlog(`Drawer: toggleDrawer() called, isOpen=${isOpenRef.current} at t=${Date.now()}`);
|
|
106
|
-
animateProgressTo(!isOpenRef.current);
|
|
107
|
-
dispatch({ type: 'toggleDrawer' });
|
|
108
|
-
},
|
|
109
|
-
jumpTo: (name) => {
|
|
110
|
-
dispatch({ type: 'jumpTo', name });
|
|
111
|
-
if (isOpenRef.current)
|
|
112
|
-
animateProgressTo(false);
|
|
113
|
-
},
|
|
114
|
-
}), [animateProgressTo]);
|
|
115
|
-
useImperativeHandle(forwardedRef, () => handle, [handle]);
|
|
116
|
-
const panResponder = useRef(PanResponder.create({
|
|
117
|
-
onStartShouldSetPanResponder: (event, gestureState) => shouldClaimDrawerSwipe(event, gestureState, screenWidthRef.current, isOpenRef.current, optionsRef.current, 'start'),
|
|
118
|
-
onMoveShouldSetPanResponder: (event, gestureState) => shouldClaimDrawerSwipe(event, gestureState, screenWidthRef.current, isOpenRef.current, optionsRef.current, 'move'),
|
|
119
|
-
onPanResponderGrant: () => {
|
|
120
|
-
dlog('Drawer: gesture grant');
|
|
121
|
-
dragStartProgress.current = isOpenRef.current ? 1 : 0;
|
|
122
|
-
},
|
|
123
|
-
onPanResponderMove: (_event, gestureState) => {
|
|
124
|
-
const nextProgress = resolveDragProgress(gestureState, dragStartProgress.current, optionsRef.current);
|
|
125
|
-
progress.setValue(nextProgress);
|
|
126
|
-
},
|
|
127
|
-
onPanResponderRelease: (_event, gestureState) => {
|
|
128
|
-
const intent = resolveSwipeIntent(gestureState, isOpenRef.current, optionsRef.current);
|
|
129
|
-
const open = intent === 'open';
|
|
130
|
-
dlog(`Drawer: gesture release -> ${open ? 'open' : 'close'}`);
|
|
131
|
-
animateProgressTo(open);
|
|
132
|
-
dispatch(open ? { type: 'openDrawer' } : { type: 'closeDrawer' });
|
|
133
|
-
},
|
|
134
|
-
onPanResponderTerminate: () => {
|
|
135
|
-
dlog('Drawer: gesture terminated, snapping back');
|
|
136
|
-
animateProgressTo(isOpenRef.current);
|
|
137
|
-
},
|
|
138
|
-
})).current;
|
|
139
|
-
const animated = isDrawerAnimated(options);
|
|
140
|
-
const geometry = useMemo(() => resolveDrawerGeometry(options), [options.drawerType, options.drawerPosition, options.drawerWidth]);
|
|
141
|
-
const panelSlot = animated ? resolveDrawerSlotInterpolation(geometry, 'panel') : undefined;
|
|
142
|
-
const contentSlot = animated ? resolveDrawerSlotInterpolation(geometry, 'content') : undefined;
|
|
143
|
-
const overlaySlot = animated ? resolveDrawerSlotInterpolation(geometry, 'overlay') : undefined;
|
|
144
|
-
const panelTranslateX = panelSlot ? progress.interpolate(panelSlot.translateX) : undefined;
|
|
145
|
-
const contentTranslateX = contentSlot ? progress.interpolate(contentSlot.translateX) : undefined;
|
|
146
|
-
const overlayOpacity = overlaySlot ? progress.interpolate(overlaySlot.opacity) : undefined;
|
|
147
|
-
// The overlay is a full-screen absolutely-positioned sibling BELOW content in paint order
|
|
148
|
-
// (see render-drawer.ts's drawerChildOrder) - for 'front' that's fine since content never moves,
|
|
149
|
-
// but for 'slide' content itself translates away by contentTranslateX, and without following it
|
|
150
|
-
// the overlay stays pinned full-screen, dimming (and touch-capturing) the now-revealed panel
|
|
151
|
-
// underneath instead of just the content sliver it's meant to dim. Tying overlay to the SAME
|
|
152
|
-
// translateX as content keeps it registered exactly under content, wherever content actually is
|
|
153
|
-
// (resolveDrawerSlotInterpolation's 'overlay' overload returns that same content delta).
|
|
154
|
-
const overlayTranslateX = overlaySlot ? progress.interpolate(overlaySlot.translateX) : undefined;
|
|
155
|
-
const focusedRoute = state.routes[state.index];
|
|
156
|
-
const focusedEntry = focusedRoute ? registry.get(focusedRoute.name) : undefined;
|
|
157
|
-
if (focusedRoute && !focusedEntry) {
|
|
158
|
-
dlog(`Drawer: no screen registered for route name "${focusedRoute.name}"`);
|
|
159
|
-
}
|
|
160
|
-
// Only the focused route's screen is ever mounted (like Tab, unlike Stack which keeps every
|
|
161
|
-
// pushed route alive), so a fresh emitter per focus change is sufficient - see tabs.ts's
|
|
162
|
-
// matching comment for why no per-route emitter map is needed. Keyed on the route KEY, not the
|
|
163
|
-
// route object, so a jumpTo-with-no-params re-focus of the ALREADY-focused route (a no-op in
|
|
164
|
-
// drawerRouterReducer) and any future params merge don't spuriously re-fire focus/blur.
|
|
165
|
-
const focusedRouteKey = focusedRoute?.key;
|
|
166
|
-
const routeEmitter = useMemo(() => createNavigationEmitter(), [focusedRouteKey]);
|
|
167
|
-
// Drawer paints its own panel in pure JS - there is no native onAppear/onDisappear to hook
|
|
168
|
-
// (unlike Stack's RNSScreen), so focus/blur is synthesized here: mount = focus, cleanup = blur,
|
|
169
|
-
// exactly what an effect keyed on focusedRouteKey already encodes - no diffFocusedRoute
|
|
170
|
-
// indirection needed (unlike Vue/Angular, which diff real prev/next keys inside an imperative
|
|
171
|
-
// watch/CD callback that has no mount/cleanup pairing of its own).
|
|
172
|
-
useEffect(() => {
|
|
173
|
-
if (focusedRouteKey === undefined)
|
|
174
|
-
return undefined;
|
|
175
|
-
dlog(`Drawer: route "${focusedRoute?.name}" focused`);
|
|
176
|
-
routeEmitter.emit(NAVIGATION_EVENT_FOCUS);
|
|
177
|
-
return () => {
|
|
178
|
-
dlog(`Drawer: route "${focusedRoute?.name}" blurred`);
|
|
179
|
-
routeEmitter.emit(NAVIGATION_EVENT_BLUR);
|
|
180
|
-
};
|
|
181
|
-
// focusedRoute omitted deliberately: only its .key (tracked via focusedRouteKey) should
|
|
182
|
-
// re-run this effect - see the comment above on focusedRouteKey.
|
|
183
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
184
|
-
}, [routeEmitter, focusedRouteKey]);
|
|
185
|
-
const content = focusedEntry && focusedRoute
|
|
186
|
-
? createElement(NavigationContext.Provider, {
|
|
187
|
-
value: {
|
|
188
|
-
route: focusedRoute,
|
|
189
|
-
navigation: handle,
|
|
190
|
-
emitter: routeEmitter,
|
|
191
|
-
parent: ambientContext,
|
|
192
|
-
},
|
|
193
|
-
}, createElement(focusedEntry.component))
|
|
194
|
-
: null;
|
|
195
|
-
const descriptors = {};
|
|
196
|
-
for (const route of state.routes) {
|
|
197
|
-
const entry = registry.get(route.name);
|
|
198
|
-
if (entry === undefined)
|
|
199
|
-
continue;
|
|
200
|
-
descriptors[route.key] = {
|
|
201
|
-
options: resolveDrawerScreenOptions(entry, { route, navigation: handle }, screenOptions),
|
|
202
|
-
navigation: handle,
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
const drawerContent = renderDrawerContent?.({ state, descriptors, navigation: handle }) ?? null;
|
|
206
|
-
const root = renderDrawer({
|
|
207
|
-
overlayColor: overlayColor ?? DRAWER_DEFAULT_OVERLAY_COLOR,
|
|
208
|
-
drawerStyle,
|
|
209
|
-
contentPassthrough: {},
|
|
210
|
-
overlayPassthrough: animated
|
|
211
|
-
? {
|
|
212
|
-
pointerEvents: state.isOpen ? 'auto' : 'none',
|
|
213
|
-
onStartShouldSetResponder: () => true,
|
|
214
|
-
onResponderRelease: () => {
|
|
215
|
-
animateProgressTo(false);
|
|
216
|
-
dispatch({ type: 'closeDrawer' });
|
|
217
|
-
},
|
|
218
|
-
}
|
|
219
|
-
: {},
|
|
220
|
-
panelPassthrough: {},
|
|
221
|
-
}, options);
|
|
222
|
-
const order = drawerChildOrder(options);
|
|
223
|
-
const slots = new Map();
|
|
224
|
-
order.forEach((slot, index) => {
|
|
225
|
-
const descriptor = root.children[index];
|
|
226
|
-
if (typeof descriptor !== 'string')
|
|
227
|
-
slots.set(slot, descriptor);
|
|
228
|
-
});
|
|
229
|
-
const slotChildren = {
|
|
230
|
-
content,
|
|
231
|
-
overlay: null,
|
|
232
|
-
panel: drawerContent,
|
|
233
|
-
};
|
|
234
|
-
// Each animated style holds an AnimatedInterpolation node, not a plain number/color - it feeds
|
|
235
|
-
// only Animated.View's deliberately permissive `style?: unknown` (see create-animated-component.tsx),
|
|
236
|
-
// never the plain-IViewStyle branch below, so this stays untyped rather than widening IViewStyle.
|
|
237
|
-
const slotAnimatedStyle = {
|
|
238
|
-
content: contentTranslateX === undefined
|
|
239
|
-
? undefined
|
|
240
|
-
: { transform: [{ translateX: contentTranslateX }] },
|
|
241
|
-
overlay: overlayOpacity === undefined
|
|
242
|
-
? undefined
|
|
243
|
-
: { opacity: overlayOpacity, transform: [{ translateX: overlayTranslateX }] },
|
|
244
|
-
panel: panelTranslateX === undefined ? undefined : { transform: [{ translateX: panelTranslateX }] },
|
|
245
|
-
};
|
|
246
|
-
function renderSlot(slot) {
|
|
247
|
-
const descriptor = slots.get(slot);
|
|
248
|
-
if (descriptor === undefined)
|
|
249
|
-
return null;
|
|
250
|
-
const animatedStyle = slotAnimatedStyle[slot];
|
|
251
|
-
if (animatedStyle === undefined) {
|
|
252
|
-
return createElement(descriptor.type, { key: descriptor.key, ...descriptor.props }, slotChildren[slot]);
|
|
253
|
-
}
|
|
254
|
-
const style = [descriptor.props.style, animatedStyle];
|
|
255
|
-
return createElement(Animated.View, { key: descriptor.key, ...descriptor.props, style }, slotChildren[slot]);
|
|
256
|
-
}
|
|
257
|
-
const drawerChildren = order
|
|
258
|
-
.map(renderSlot)
|
|
259
|
-
.filter((element) => element !== null);
|
|
260
|
-
return createElement('symbiote-view', { style: root.props.style, ...panResponder.panHandlers }, ...drawerChildren);
|
|
261
|
-
});
|
|
262
|
-
export const Drawer = Object.assign(DrawerImpl, { Screen: DrawerScreen });
|
|
263
|
-
// --- Explicit gap list vs the real react-native-gesture-handler + react-native-reanimated
|
|
264
|
-
// @react-navigation/drawer (confirmed against its current docs) ---
|
|
265
|
-
// 1. `configureGestureHandler` - a raw react-native-gesture-handler `Gesture` object escape
|
|
266
|
-
// hatch. No PanResponder equivalent exists; not ported.
|
|
267
|
-
// 2. Simultaneous/failure gesture RELATIONSHIPS (gesture-handler's declarative composition vs a
|
|
268
|
-
// nested ScrollView, another PanResponder, etc.) - PanResponder only offers negotiation via the
|
|
269
|
-
// should-set boolean gates used here (edge-start + dominant-axis), which is more prone to an
|
|
270
|
-
// accidental hijack of a nested horizontal ScrollView/Swiper than gesture-handler's system.
|
|
271
|
-
// 3. `useDrawerProgress` - a Reanimated SharedValue read on the UI thread. `progress` here is a
|
|
272
|
-
// JS-thread AnimatedValue; interpolating it for consumer-facing content animation works, but
|
|
273
|
-
// without native-driver wiring (gap noted above) it does not carry the same synchronous
|
|
274
|
-
// UI-thread guarantee under JS-thread load.
|
|
275
|
-
// 4. `hideStatusBarOnOpen` / `keyboardDismissMode` / `statusBarAnimation` / `overlayStyle` - not
|
|
276
|
-
// wired in this pass; straightforward additions once StatusBar/Keyboard module wiring is
|
|
277
|
-
// needed here (not a PanResponder/Animated limitation, just unscoped for v1).
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { FC, ReactElement } from 'react';
|
|
2
|
-
import type { IDrawerNavigatorHandle, IDrawerScreenOptions, IRoute } from '../core';
|
|
3
|
-
export type IDrawerScreenOptionsArgs = {
|
|
4
|
-
route: IRoute<unknown>;
|
|
5
|
-
navigation: IDrawerNavigatorHandle;
|
|
6
|
-
};
|
|
7
|
-
export type IDrawerScreenOptionsResolver = (args: IDrawerScreenOptionsArgs) => IDrawerScreenOptions;
|
|
8
|
-
export type IDrawerScreenProps = {
|
|
9
|
-
name: string;
|
|
10
|
-
component: FC;
|
|
11
|
-
options?: IDrawerScreenOptions | IDrawerScreenOptionsResolver;
|
|
12
|
-
initialParams?: unknown;
|
|
13
|
-
};
|
|
14
|
-
export declare function DrawerScreen(_props: IDrawerScreenProps): ReactElement | null;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
// Drawer.Screen: a declarative marker, never rendered on its own - Drawer reads its props via
|
|
2
|
-
// React.Children to build the static name -> {component, options} registry, then mounts the
|
|
3
|
-
// FOCUSED route's component. Mirrors react/tab-screen.ts (Tab's twin - the closer sibling, both
|
|
4
|
-
// are fixed-route-list/no-push navigators) and react/screen.ts (Stack's twin); see CLAUDE.md
|
|
5
|
-
// <third_party_rn_packages_are_react_only> - this and Drawer import nothing from react-native.
|
|
6
|
-
export function DrawerScreen(_props) {
|
|
7
|
-
return null;
|
|
8
|
-
}
|
|
@@ -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/index.js';
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { IDrawerNavigatorHandle } from '../../core';
|
|
2
|
-
import type { INavigationHandle } from './use-navigation';
|
|
3
|
-
export type IDrawerNavigationHandle = IDrawerNavigatorHandle & Pick<INavigationHandle, 'addListener' | 'getParent'>;
|
|
4
|
-
export declare function useDrawerNavigation(): IDrawerNavigationHandle;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
// Twin of use-stack-navigation.ts, narrowing to a Drawer handle instead. See that file's header
|
|
2
|
-
// for why this exists: it hides the union-narrowing guard inside the library instead of every call site.
|
|
3
|
-
import { isDrawerNavigatorHandle } from '../../core/index.js';
|
|
4
|
-
import { useNavigation } from './use-navigation.js';
|
|
5
|
-
export function useDrawerNavigation() {
|
|
6
|
-
const navigation = useNavigation();
|
|
7
|
-
if (!isDrawerNavigatorHandle(navigation)) {
|
|
8
|
-
throw new Error('useDrawerNavigation() was called from a component whose nearest navigator is not a Drawer. ' +
|
|
9
|
-
'Use useNavigation() instead if this component can render under more than one navigator kind.');
|
|
10
|
-
}
|
|
11
|
-
return navigation;
|
|
12
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// Thin useEffect wrapper: runs `effect` while the route is focused and runs its own returned
|
|
2
|
-
// cleanup on blur - exactly React.useEffect's contract, just re-armed on every focus/blur pair
|
|
3
|
-
// instead of once on mount - mirrors @react-navigation's useFocusEffect. Callers should memoize
|
|
4
|
-
// `effect` (React.useCallback), same requirement the upstream hook documents, since a new
|
|
5
|
-
// `effect` identity re-subscribes here like any other useEffect dependency change.
|
|
6
|
-
import { useEffect } from 'react';
|
|
7
|
-
import { NAVIGATION_EVENT_BLUR, NAVIGATION_EVENT_FOCUS } from '../../core/index.js';
|
|
8
|
-
import { useRequiredNavigationContext } from '../navigation-context.js';
|
|
9
|
-
export function useFocusEffect(effect) {
|
|
10
|
-
const context = useRequiredNavigationContext('useFocusEffect');
|
|
11
|
-
const { emitter } = context;
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
let cleanup;
|
|
14
|
-
const runEffect = () => {
|
|
15
|
-
cleanup = effect();
|
|
16
|
-
};
|
|
17
|
-
const runCleanup = () => {
|
|
18
|
-
cleanup?.();
|
|
19
|
-
cleanup = undefined;
|
|
20
|
-
};
|
|
21
|
-
const unsubscribeFocus = emitter.addListener(NAVIGATION_EVENT_FOCUS, runEffect);
|
|
22
|
-
const unsubscribeBlur = emitter.addListener(NAVIGATION_EVENT_BLUR, runCleanup);
|
|
23
|
-
return () => {
|
|
24
|
-
unsubscribeFocus();
|
|
25
|
-
unsubscribeBlur();
|
|
26
|
-
runCleanup();
|
|
27
|
-
};
|
|
28
|
-
}, [emitter, effect]);
|
|
29
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function useIsFocused(): boolean;
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
// Thin useEffect wrapper: subscribes to the route's focus/blur pair and returns whether it's
|
|
2
|
-
// currently focused, re-rendering on change - mirrors @react-navigation's useIsFocused. Starts
|
|
3
|
-
// `false` rather than guessing from stack position: the route's emitter only fires 'focus' once
|
|
4
|
-
// RNSScreen's native onAppear actually lands (stack.ts), so a screen genuinely isn't focused yet
|
|
5
|
-
// at the instant it mounts - same async gap real native transitions have.
|
|
6
|
-
import { useEffect, useState } from 'react';
|
|
7
|
-
import { NAVIGATION_EVENT_BLUR, NAVIGATION_EVENT_FOCUS } from '../../core/index.js';
|
|
8
|
-
import { useRequiredNavigationContext } from '../navigation-context.js';
|
|
9
|
-
export function useIsFocused() {
|
|
10
|
-
const context = useRequiredNavigationContext('useIsFocused');
|
|
11
|
-
const { emitter } = context;
|
|
12
|
-
const [isFocused, setIsFocused] = useState(false);
|
|
13
|
-
useEffect(() => {
|
|
14
|
-
const unsubscribeFocus = emitter.addListener(NAVIGATION_EVENT_FOCUS, () => setIsFocused(true));
|
|
15
|
-
const unsubscribeBlur = emitter.addListener(NAVIGATION_EVENT_BLUR, () => setIsFocused(false));
|
|
16
|
-
return () => {
|
|
17
|
-
unsubscribeFocus();
|
|
18
|
-
unsubscribeBlur();
|
|
19
|
-
};
|
|
20
|
-
}, [emitter]);
|
|
21
|
-
return isFocused;
|
|
22
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
// Thin selector wrapper: subscribes to the router's `state` broadcast (stack.ts's render loop
|
|
2
|
-
// re-emits the full INavigatorState to every route's emitter after each commit) and returns
|
|
3
|
-
// `selector(state)`, re-rendering only when the emitted state changes - mirrors
|
|
4
|
-
// @react-navigation's useNavigationState. The reducer/dispatch machinery it selects over lives in
|
|
5
|
-
// core/navigator-state.ts; this hook only wires the subscription + local re-render.
|
|
6
|
-
//
|
|
7
|
-
// Seeded from a single-route snapshot ({ routes: [route] }) rather than left undefined: the real
|
|
8
|
-
// broadcast lands a tick later (post-commit useEffect), so a selector reading e.g.
|
|
9
|
-
// `state.routes.at(-1)?.name` still resolves correctly on first paint for the common single-route
|
|
10
|
-
// case, closing the same async gap useIsFocused documents.
|
|
11
|
-
//
|
|
12
|
-
// Live updates currently only arrive under <Stack> - it's the only navigator that emits
|
|
13
|
-
// NAVIGATION_EVENT_STATE (its post-commit useEffect in stack.ts); <Tab>/<Drawer> don't broadcast
|
|
14
|
-
// it, so under those this hook silently stays on its initial snapshot.
|
|
15
|
-
import { useEffect, useRef, useState } from 'react';
|
|
16
|
-
import { NAVIGATION_EVENT_STATE, isRecord } from '../../../core/index.js';
|
|
17
|
-
import { useRequiredNavigationContext } from '../../navigation-context.js';
|
|
18
|
-
export function useNavigationState(selector) {
|
|
19
|
-
const context = useRequiredNavigationContext('useNavigationState');
|
|
20
|
-
const { route, emitter } = context;
|
|
21
|
-
const [result, setResult] = useState(() => selector({ routes: [route] }));
|
|
22
|
-
// selector is typically an inline arrow at the call site (`useNavigationState(s => ...)`), a
|
|
23
|
-
// fresh identity every render - read through a ref so the effect below doesn't need `selector`
|
|
24
|
-
// in its deps, and doesn't unsubscribe/resubscribe on every render because of it (the same trap
|
|
25
|
-
// useFocusEffect's own comment documents for its `effect` argument).
|
|
26
|
-
const selectorRef = useRef(selector);
|
|
27
|
-
selectorRef.current = selector;
|
|
28
|
-
useEffect(() => {
|
|
29
|
-
return emitter.addListener(NAVIGATION_EVENT_STATE, (state) => {
|
|
30
|
-
if (!isNavigatorState(state))
|
|
31
|
-
return;
|
|
32
|
-
setResult(selectorRef.current(state));
|
|
33
|
-
});
|
|
34
|
-
}, [emitter]);
|
|
35
|
-
return result;
|
|
36
|
-
}
|
|
37
|
-
function isNavigatorState(value) {
|
|
38
|
-
return isRecord(value) && Array.isArray(value.routes);
|
|
39
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { INavigationEventListener, INavigationEventName } from '../../core';
|
|
2
|
-
import type { IAnyNavigatorHandle } from '../navigation-context';
|
|
3
|
-
export type INavigationHandle = IAnyNavigatorHandle & {
|
|
4
|
-
addListener: (event: INavigationEventName, listener: INavigationEventListener) => () => void;
|
|
5
|
-
getParent: () => IAnyNavigatorHandle | undefined;
|
|
6
|
-
};
|
|
7
|
-
export declare function useNavigation(): INavigationHandle;
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
// Thin lifecycle wrapper: returns the current screen's navigator handle (push/pop/replace/... for a
|
|
2
|
-
// Stack screen, jumpTo/setParams for a Tab screen, openDrawer/... for a Drawer screen) plus
|
|
3
|
-
// addListener bound to that route's own emitter - mirrors @react-navigation's
|
|
4
|
-
// `navigation.addListener('focus', cb)` surface. All pub/sub logic lives in
|
|
5
|
-
// ../../core/navigation-events; this hook only reads NavigationContext and binds identity.
|
|
6
|
-
import { useMemo } from 'react';
|
|
7
|
-
import { useRequiredNavigationContext } from '../navigation-context.js';
|
|
8
|
-
export function useNavigation() {
|
|
9
|
-
const context = useRequiredNavigationContext('useNavigation');
|
|
10
|
-
const { navigation, emitter, parent } = context;
|
|
11
|
-
return useMemo(() => ({
|
|
12
|
-
...navigation,
|
|
13
|
-
addListener: emitter.addListener,
|
|
14
|
-
getParent: () => parent?.navigation,
|
|
15
|
-
}), [navigation, emitter, parent]);
|
|
16
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
// Thin lifecycle wrapper: returns the current screen's IRoute from NavigationContext - mirrors
|
|
2
|
-
// @react-navigation's useRoute. Zero logic of its own; the route object itself is built by
|
|
3
|
-
// stack.ts's render loop from the core reducer's state.
|
|
4
|
-
import { useRequiredNavigationContext } from '../navigation-context.js';
|
|
5
|
-
export function useRoute() {
|
|
6
|
-
const context = useRequiredNavigationContext('useRoute');
|
|
7
|
-
return context.route;
|
|
8
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { INavigatorHandle } from '../../core';
|
|
2
|
-
import type { INavigationHandle } from './use-navigation';
|
|
3
|
-
export type IStackNavigationHandle = INavigatorHandle & Pick<INavigationHandle, 'addListener' | 'getParent'>;
|
|
4
|
-
export declare function useStackNavigation(): IStackNavigationHandle;
|