@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,133 +0,0 @@
|
|
|
1
|
-
// Pure folds from a route's agnostic IScreenOptions into the native view-props shapes
|
|
2
|
-
// (RNSScreen + RNSScreenStackHeaderConfig + RNSSearchBar paint from). Framework-agnostic and
|
|
3
|
-
// shared verbatim by every adapter - the adapter's own job is only wiring identity (screenId),
|
|
4
|
-
// position (activityState via computeActivityState) and the lifecycle event handlers into
|
|
5
|
-
// `passthrough`.
|
|
6
|
-
//
|
|
7
|
-
// The header bar-button payload+dispatch cluster (id-tagging, onPress lookup) lives in
|
|
8
|
-
// header-bar-buttons.ts - self-contained, consumed here only as a black box by
|
|
9
|
-
// resolveHeaderConfigView.
|
|
10
|
-
import { buildHeaderBarButtonDispatch, prepareHeaderBarButtonItems } from './header-bar-buttons.js';
|
|
11
|
-
// These sentinels must be kept in sync with react-native-screens' native side - they are RNS's
|
|
12
|
-
// own compat values for the 'fitToContents'/'large'/'medium'/'all' legacy presets, replicated
|
|
13
|
-
// here because we drive RNSScreen directly (no react-native-screens JS component in the path).
|
|
14
|
-
const SHEET_FIT_TO_CONTENTS = [-1];
|
|
15
|
-
const SHEET_COMPAT_LARGE = [1.0];
|
|
16
|
-
const SHEET_COMPAT_MEDIUM = [0.5];
|
|
17
|
-
const SHEET_COMPAT_ALL = [0.5, 1.0];
|
|
18
|
-
const SHEET_DIMMED_ALWAYS = -1;
|
|
19
|
-
function resolveSheetAllowedDetents(compat) {
|
|
20
|
-
if (compat === undefined)
|
|
21
|
-
return SHEET_COMPAT_LARGE;
|
|
22
|
-
if (Array.isArray(compat))
|
|
23
|
-
return compat;
|
|
24
|
-
switch (compat) {
|
|
25
|
-
case 'fitToContents':
|
|
26
|
-
return SHEET_FIT_TO_CONTENTS;
|
|
27
|
-
case 'large':
|
|
28
|
-
return SHEET_COMPAT_LARGE;
|
|
29
|
-
case 'medium':
|
|
30
|
-
return SHEET_COMPAT_MEDIUM;
|
|
31
|
-
case 'all':
|
|
32
|
-
return SHEET_COMPAT_ALL;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
function resolveSheetLargestUndimmedDetent(compat, lastDetentIndex) {
|
|
36
|
-
if (typeof compat === 'number')
|
|
37
|
-
return compat;
|
|
38
|
-
switch (compat) {
|
|
39
|
-
case 'last':
|
|
40
|
-
return lastDetentIndex;
|
|
41
|
-
case 'large':
|
|
42
|
-
return 1;
|
|
43
|
-
case 'medium':
|
|
44
|
-
return 0;
|
|
45
|
-
case 'none':
|
|
46
|
-
case 'all':
|
|
47
|
-
case undefined:
|
|
48
|
-
return SHEET_DIMMED_ALWAYS;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
function resolveSheetInitialDetent(compat, lastDetentIndex) {
|
|
52
|
-
if (compat === 'last')
|
|
53
|
-
return lastDetentIndex;
|
|
54
|
-
if (compat === undefined)
|
|
55
|
-
return 0;
|
|
56
|
-
return compat;
|
|
57
|
-
}
|
|
58
|
-
function toOptionalBooleanNativeProp(value) {
|
|
59
|
-
if (value === undefined)
|
|
60
|
-
return 'undefined';
|
|
61
|
-
return value ? 'true' : 'false';
|
|
62
|
-
}
|
|
63
|
-
export function resolveHeaderConfigView(options, platform, passthrough = {}) {
|
|
64
|
-
return {
|
|
65
|
-
title: options?.title,
|
|
66
|
-
hidden: options?.headerShown === false,
|
|
67
|
-
backTitle: options?.headerBackTitle,
|
|
68
|
-
backTitleVisible: platform.defaultHeaderBackTitleVisible,
|
|
69
|
-
backButtonDisplayMode: options?.headerBackButtonDisplayMode,
|
|
70
|
-
largeTitle: options?.headerLargeTitle,
|
|
71
|
-
translucent: options?.headerTranslucent,
|
|
72
|
-
color: options?.headerTintColor,
|
|
73
|
-
titleColor: options?.headerTitleColor,
|
|
74
|
-
backgroundColor: options?.headerStyle?.backgroundColor,
|
|
75
|
-
largeTitleBackgroundColor: options?.headerLargeStyle?.backgroundColor,
|
|
76
|
-
userInterfaceStyle: options?.headerUserInterfaceStyle,
|
|
77
|
-
headerLeftBarButtonItems: prepareHeaderBarButtonItems(options?.headerLeftBarButtonItems, 'left'),
|
|
78
|
-
headerRightBarButtonItems: prepareHeaderBarButtonItems(options?.headerRightBarButtonItems, 'right'),
|
|
79
|
-
...buildHeaderBarButtonDispatch(options?.headerLeftBarButtonItems, options?.headerRightBarButtonItems),
|
|
80
|
-
passthrough,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
export function resolveScreenView(screenId, activityState, options, passthrough = {}) {
|
|
84
|
-
const sheetAllowedDetents = resolveSheetAllowedDetents(options?.sheetAllowedDetents);
|
|
85
|
-
const lastDetentIndex = sheetAllowedDetents.length - 1;
|
|
86
|
-
return {
|
|
87
|
-
screenId,
|
|
88
|
-
activityState,
|
|
89
|
-
gestureEnabled: options?.gestureEnabled,
|
|
90
|
-
stackAnimation: options?.stackAnimation,
|
|
91
|
-
stackPresentation: options?.stackPresentation,
|
|
92
|
-
transitionDuration: options?.transitionDuration,
|
|
93
|
-
sheetAllowedDetents,
|
|
94
|
-
sheetLargestUndimmedDetent: resolveSheetLargestUndimmedDetent(options?.sheetLargestUndimmedDetentIndex, lastDetentIndex),
|
|
95
|
-
sheetInitialDetent: resolveSheetInitialDetent(options?.sheetInitialDetentIndex, lastDetentIndex),
|
|
96
|
-
sheetGrabberVisible: options?.sheetGrabberVisible,
|
|
97
|
-
sheetCornerRadius: options?.sheetCornerRadius,
|
|
98
|
-
sheetExpandsWhenScrolledToEdge: options?.sheetExpandsWhenScrolledToEdge,
|
|
99
|
-
sheetElevation: options?.sheetElevation,
|
|
100
|
-
sheetShouldOverflowTopInset: options?.sheetShouldOverflowTopInset,
|
|
101
|
-
sheetDefaultResizeAnimationEnabled: options?.sheetDefaultResizeAnimationEnabled,
|
|
102
|
-
statusBarStyle: options?.statusBarStyle,
|
|
103
|
-
statusBarHidden: options?.statusBarHidden,
|
|
104
|
-
statusBarAnimation: options?.statusBarAnimation,
|
|
105
|
-
screenOrientation: options?.screenOrientation,
|
|
106
|
-
passthrough,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
// RNSSearchBar is a standalone Fabric leaf (react-native-screens mounts it as a header-subview
|
|
110
|
-
// child in its own JS component) - resolveSearchBarView only folds the static config surface;
|
|
111
|
-
// where/how the adapter mounts the resulting leaf in the header tree is an adapter concern.
|
|
112
|
-
export function resolveSearchBarView(options, passthrough = {}) {
|
|
113
|
-
return {
|
|
114
|
-
placeholder: options?.placeholder,
|
|
115
|
-
autoCapitalize: options?.autoCapitalize,
|
|
116
|
-
placement: options?.placement,
|
|
117
|
-
hideWhenScrolling: options?.hideWhenScrolling,
|
|
118
|
-
allowToolbarIntegration: options?.allowToolbarIntegration,
|
|
119
|
-
obscureBackground: toOptionalBooleanNativeProp(options?.obscureBackground),
|
|
120
|
-
hideNavigationBar: toOptionalBooleanNativeProp(options?.hideNavigationBar),
|
|
121
|
-
cancelButtonText: options?.cancelButtonText,
|
|
122
|
-
barTintColor: options?.barTintColor,
|
|
123
|
-
tintColor: options?.tintColor,
|
|
124
|
-
textColor: options?.textColor,
|
|
125
|
-
autoFocus: options?.autoFocus,
|
|
126
|
-
disableBackButtonOverride: options?.disableBackButtonOverride,
|
|
127
|
-
inputType: options?.inputType,
|
|
128
|
-
hintTextColor: options?.hintTextColor,
|
|
129
|
-
headerIconColor: options?.headerIconColor,
|
|
130
|
-
shouldShowHintSearchIcon: options?.shouldShowHintSearchIcon,
|
|
131
|
-
passthrough,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { type ISymbioteNode } from '@symbiote-native/engine';
|
|
2
|
-
export interface ISearchBarCommands {
|
|
3
|
-
focus(): void;
|
|
4
|
-
blur(): void;
|
|
5
|
-
clearText(): void;
|
|
6
|
-
setText(text: string): void;
|
|
7
|
-
cancelSearch(): void;
|
|
8
|
-
toggleCancelButton(show: boolean): void;
|
|
9
|
-
}
|
|
10
|
-
export declare function buildSearchBarHandle(getNode: () => ISymbioteNode | null): ISearchBarCommands;
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
// The imperative API react-native-screens' own SearchBarProps exposes via its `ref` field
|
|
2
|
-
// (SearchBarCommands): each method dispatches a native view command on the RNSSearchBar node
|
|
3
|
-
// (SearchBarNativeComponent's supportedCommands: blur/focus/clearText/toggleCancelButton/
|
|
4
|
-
// setText/cancelSearch). Framework-agnostic - mirrors @symbiote-native/components'
|
|
5
|
-
// buildScrollViewHandle exactly (same lazy-getter shape); the adapter supplies the node getter
|
|
6
|
-
// and its own framework-specific ref field (see react/screen.ts's IReactSearchBarOptions - the
|
|
7
|
-
// ref itself is per-adapter, same split as IPressableProps).
|
|
8
|
-
import { dispatchViewCommand, dlog } from '@symbiote-native/engine';
|
|
9
|
-
// dispatchViewCommand itself dlogs a "node not committed" skip; this null case (the ref never
|
|
10
|
-
// attached to a live RNSSearchBar at all - e.g. the header's search bar isn't mounted, or a
|
|
11
|
-
// stale handle outlived its screen) was the one unlogged gap, silently indistinguishable from
|
|
12
|
-
// "the command fired but did nothing". Investigation instrumentation (HeaderOptionsScreen
|
|
13
|
-
// unresponsive-buttons bug); kept behind DEBUG per <keep_logs_gate_behind_DEBUG>, never removed.
|
|
14
|
-
function warnIfDetached(command, node) {
|
|
15
|
-
if (node !== null)
|
|
16
|
-
return true;
|
|
17
|
-
dlog(`SearchBarCommands.${command}: skipped, node is null (ref never attached) at t=${Date.now()}`);
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
|
-
// Every command is the same 3-line dance: get the (lazily-read) node, bail with a dlog'd warning
|
|
21
|
-
// if it's detached, dispatch the native view command. Factored once so the six methods below
|
|
22
|
-
// differ only in which command name they dispatch.
|
|
23
|
-
function makeCommand(getNode, command) {
|
|
24
|
-
return (...args) => {
|
|
25
|
-
const node = getNode();
|
|
26
|
-
if (!warnIfDetached(command, node))
|
|
27
|
-
return;
|
|
28
|
-
dispatchViewCommand(node, command, args);
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
// `getNode` is a LAZY getter, read on every call - see buildScrollViewHandle's comment for why
|
|
32
|
-
// an eager capture would freeze `null`.
|
|
33
|
-
export function buildSearchBarHandle(getNode) {
|
|
34
|
-
return {
|
|
35
|
-
focus: makeCommand(getNode, 'focus'),
|
|
36
|
-
blur: makeCommand(getNode, 'blur'),
|
|
37
|
-
clearText: makeCommand(getNode, 'clearText'),
|
|
38
|
-
setText: makeCommand(getNode, 'setText'),
|
|
39
|
-
cancelSearch: makeCommand(getNode, 'cancelSearch'),
|
|
40
|
-
toggleCancelButton: makeCommand(getNode, 'toggleCancelButton'),
|
|
41
|
-
};
|
|
42
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
// Lightweight state persistence: pure serialize/deserialize for INavigatorState, the primitive
|
|
2
|
-
// @react-navigation's `initialState`/`onStateChange` persistence recipe is built on. Routes are
|
|
3
|
-
// already `{key, name, params}` - plain data, no functions/refs/class instances - so a JSON-safe
|
|
4
|
-
// passthrough is sufficient; no framework wiring lives here yet (that's an adapter-level task).
|
|
5
|
-
import { isRecord } from '../guards.js';
|
|
6
|
-
// No `'params' in value` check: a route pushed with no params serializes to `{key, name,
|
|
7
|
-
// params: undefined}`, and JSON.stringify drops undefined-valued keys - a JSON round trip of a
|
|
8
|
-
// perfectly valid route legitimately arrives here without a `params` key at all.
|
|
9
|
-
function isRoute(value) {
|
|
10
|
-
return isRecord(value) && typeof value.key === 'string' && typeof value.name === 'string';
|
|
11
|
-
}
|
|
12
|
-
function isNavigatorState(value) {
|
|
13
|
-
return isRecord(value) && Array.isArray(value.routes) && value.routes.every(isRoute);
|
|
14
|
-
}
|
|
15
|
-
export function serializeNavigatorState(state) {
|
|
16
|
-
return state;
|
|
17
|
-
}
|
|
18
|
-
export function deserializeNavigatorState(raw) {
|
|
19
|
-
if (!isNavigatorState(raw)) {
|
|
20
|
-
throw new Error('deserializeNavigatorState: persisted value is not a valid navigator state');
|
|
21
|
-
}
|
|
22
|
-
return raw;
|
|
23
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { IColorValue } from '@symbiote-native/engine';
|
|
2
|
-
import type { IViewStyle, IStyleProp } from '@symbiote-native/engine';
|
|
3
|
-
import type { IDescriptor } from '@symbiote-native/components';
|
|
4
|
-
export type ITabBarIcon = IDescriptor | string;
|
|
5
|
-
export type ITabOptions = {
|
|
6
|
-
title?: string;
|
|
7
|
-
tabBarLabel?: string;
|
|
8
|
-
tabBarIcon?: ITabBarIcon;
|
|
9
|
-
tabBarBadge?: string | number;
|
|
10
|
-
tabBarActiveTintColor?: IColorValue;
|
|
11
|
-
tabBarInactiveTintColor?: IColorValue;
|
|
12
|
-
tabBarStyle?: IStyleProp<IViewStyle>;
|
|
13
|
-
};
|
|
@@ -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).
|