@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,390 +0,0 @@
|
|
|
1
|
-
// Drawer, the Vue 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 React/Angular adapters; here Vue supplies the lifecycle - a plain ref
|
|
4
|
-
// for the router (Vue's twin of useReducer), a PanResponder for the swipe gesture (built ONCE in
|
|
5
|
-
// setup, its callbacks reading options/window-width/isOpen LIVE off Vue's own reactive `attrs` /
|
|
6
|
-
// composable refs at gesture time - Vue's reactivity already gives every gesture callback the
|
|
7
|
-
// CURRENT value with no ref-mirroring dance, unlike React's useRef(value); value = X every
|
|
8
|
-
// render), an Animated.Value driving the slide/opacity transforms, and expose() for the
|
|
9
|
-
// open/close/toggle/jumpTo handle - mirroring tabs.ts's shape (Tab is the closer sibling: both are
|
|
10
|
-
// fixed-route-list, no-react-native-screens navigators; Stack's push/pop + native-screen bridging
|
|
11
|
-
// don't apply here).
|
|
12
|
-
//
|
|
13
|
-
// FEASIBILITY NOTE (see the drawer skill / PR description for the full writeup, mirrored from
|
|
14
|
-
// react/drawer.ts): the REAL @react-navigation/drawer is built on react-native-gesture-handler +
|
|
15
|
-
// react-native-reanimated, neither of which this codebase depends on. What's built here reaches
|
|
16
|
-
// the same swipe-to-open/close + front/back/slide/permanent behavior using only PanResponder +
|
|
17
|
-
// Animated (both already in @symbiote-native/engine), which is sufficient for a solid drawer but
|
|
18
|
-
// NOT byte-for-byte parity - see the explicit gap list at the bottom of this file (verbatim from
|
|
19
|
-
// the React twin - the gaps are architectural, not adapter-specific).
|
|
20
|
-
import { defineComponent, h, nextTick, onMounted, onUnmounted, shallowRef, useId, watch, } from '@vue/runtime-core';
|
|
21
|
-
import { Animated, PanResponder, normalizeVueAttrs, useWindowDimensions, } from '@symbiote-native/vue';
|
|
22
|
-
import { dlog } from '@symbiote-native/engine';
|
|
23
|
-
import { DRAWER_DEFAULT_OVERLAY_COLOR, NAVIGATION_EVENT_BLUR, NAVIGATION_EVENT_FOCUS, createInitialDrawerRouterState, createNavigationEmitter, diffFocusedRoute, drawerChildOrder, drawerRouterReducer, isDrawerAnimated, isRecord, renderDrawer, resolveDragProgress, resolveDrawerGeometry, resolveDrawerSlotInterpolation, resolveSwipeIntent, shouldClaimDrawerSwipe, } from '../../core/index.js';
|
|
24
|
-
import { NavigationScope, injectNavigationScope } from '../navigation-context.js';
|
|
25
|
-
import { DrawerScreen } from '../drawer-screen.js';
|
|
26
|
-
function asString(value) {
|
|
27
|
-
return typeof value === 'string' ? value : undefined;
|
|
28
|
-
}
|
|
29
|
-
function asNumber(value) {
|
|
30
|
-
return typeof value === 'number' ? value : undefined;
|
|
31
|
-
}
|
|
32
|
-
function asBoolean(value) {
|
|
33
|
-
return typeof value === 'boolean' ? value : undefined;
|
|
34
|
-
}
|
|
35
|
-
function isStyleProp(value) {
|
|
36
|
-
return isRecord(value);
|
|
37
|
-
}
|
|
38
|
-
const DRAWER_TYPES = ['front', 'back', 'slide', 'permanent'];
|
|
39
|
-
// .some() itself doesn't narrow `value`'s type back at the call site - the `value is IDrawerType`
|
|
40
|
-
// predicate on THIS function is what lets asDrawerType's ternary return `value` typed, mirroring
|
|
41
|
-
// Modal's isOrientation guard (adapters/vue/src/components/modal.ts).
|
|
42
|
-
function isDrawerType(value) {
|
|
43
|
-
return typeof value === 'string' && DRAWER_TYPES.some(type => type === value);
|
|
44
|
-
}
|
|
45
|
-
function asDrawerType(value) {
|
|
46
|
-
return isDrawerType(value) ? value : undefined;
|
|
47
|
-
}
|
|
48
|
-
const DRAWER_POSITIONS = ['left', 'right'];
|
|
49
|
-
function isDrawerPosition(value) {
|
|
50
|
-
return typeof value === 'string' && DRAWER_POSITIONS.some(position => position === value);
|
|
51
|
-
}
|
|
52
|
-
function asDrawerPosition(value) {
|
|
53
|
-
return isDrawerPosition(value) ? value : undefined;
|
|
54
|
-
}
|
|
55
|
-
function asComponent(value) {
|
|
56
|
-
if (typeof value === 'function')
|
|
57
|
-
return value;
|
|
58
|
-
return isRecord(value) ? value : undefined;
|
|
59
|
-
}
|
|
60
|
-
// IDrawerScreenOptions carries only `title`/`drawerLabel`, small enough for direct field guards -
|
|
61
|
-
// kept as a single cast-at-the-edge helper anyway for symmetry with stack.ts/tabs.ts's
|
|
62
|
-
// asScreenOptions/asTabOptions (same CLAUDE.md-sanctioned I/O-edge narrowing).
|
|
63
|
-
function asDrawerScreenOptions(value) {
|
|
64
|
-
return isRecord(value) ? value : undefined;
|
|
65
|
-
}
|
|
66
|
-
function asDrawerScreenOptionsOrResolver(value) {
|
|
67
|
-
if (typeof value === 'function')
|
|
68
|
-
return value;
|
|
69
|
-
return asDrawerScreenOptions(value);
|
|
70
|
-
}
|
|
71
|
-
function collectRegistry(vnodes) {
|
|
72
|
-
const registry = new Map();
|
|
73
|
-
for (const vnode of vnodes) {
|
|
74
|
-
if (vnode.type !== DrawerScreen || !isRecord(vnode.props))
|
|
75
|
-
continue;
|
|
76
|
-
const name = asString(vnode.props.name);
|
|
77
|
-
const component = asComponent(vnode.props.component);
|
|
78
|
-
if (name === undefined || component === undefined)
|
|
79
|
-
continue;
|
|
80
|
-
registry.set(name, {
|
|
81
|
-
component,
|
|
82
|
-
options: asDrawerScreenOptionsOrResolver(vnode.props.options),
|
|
83
|
-
initialParams: vnode.props.initialParams,
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
return registry;
|
|
87
|
-
}
|
|
88
|
-
function resolveDrawerScreenOptions(entry, screenComponentProps) {
|
|
89
|
-
if (typeof entry.options === 'function')
|
|
90
|
-
return entry.options(screenComponentProps);
|
|
91
|
-
return entry.options ?? {};
|
|
92
|
-
}
|
|
93
|
-
const DRAWER_SNAP_DURATION = 250;
|
|
94
|
-
const DrawerImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose }) => {
|
|
95
|
-
const attrs = normalizeVueAttrs(rawAttrs);
|
|
96
|
-
// Read BEFORE this Drawer establishes its own per-screen NavigationScope below - becomes the
|
|
97
|
-
// `parent` link a nested screen's useNavigation().getParent() walks. undefined when this
|
|
98
|
-
// Drawer is the nesting root.
|
|
99
|
-
const ambientScopeRef = injectNavigationScope();
|
|
100
|
-
function currentOptions() {
|
|
101
|
-
return {
|
|
102
|
-
drawerType: asDrawerType(attrs.drawerType),
|
|
103
|
-
drawerPosition: asDrawerPosition(attrs.drawerPosition),
|
|
104
|
-
drawerWidth: asNumber(attrs.drawerWidth),
|
|
105
|
-
overlayColor: asString(attrs.overlayColor),
|
|
106
|
-
swipeEnabled: asBoolean(attrs.swipeEnabled),
|
|
107
|
-
swipeEdgeWidth: asNumber(attrs.swipeEdgeWidth),
|
|
108
|
-
swipeMinDistance: asNumber(attrs.swipeMinDistance),
|
|
109
|
-
swipeMinVelocity: asNumber(attrs.swipeMinVelocity),
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
let routeSequence = 0;
|
|
113
|
-
const routeIdPrefix = useId();
|
|
114
|
-
function buildRoutes(registry) {
|
|
115
|
-
return Array.from(registry.entries()).map(([name, entry]) => {
|
|
116
|
-
routeSequence += 1;
|
|
117
|
-
return {
|
|
118
|
-
key: `${routeIdPrefix}-${name}-${routeSequence}`,
|
|
119
|
-
name,
|
|
120
|
-
params: entry.initialParams,
|
|
121
|
-
};
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
const initialRegistry = collectRegistry(slots.default?.() ?? []);
|
|
125
|
-
const initialRoutes = buildRoutes(initialRegistry);
|
|
126
|
-
if (initialRoutes.length === 0)
|
|
127
|
-
dlog('Drawer: no <Drawer.Screen> children registered');
|
|
128
|
-
const state = shallowRef(createInitialDrawerRouterState(initialRoutes, asString(attrs.initialRouteName)));
|
|
129
|
-
function dispatch(action) {
|
|
130
|
-
state.value = drawerRouterReducer(state.value, action);
|
|
131
|
-
}
|
|
132
|
-
const windowDimensions = useWindowDimensions();
|
|
133
|
-
// progress: 0 closed -> 1 open, the single Animated.Value every slide/opacity transform below
|
|
134
|
-
// interpolates from. A plain `const` (not a ref): setup() runs once, so this needs no
|
|
135
|
-
// re-creation guard the way React's useRef(new Animated.Value(...)).current does.
|
|
136
|
-
const progress = new Animated.Value(state.value.isOpen ? 1 : 0);
|
|
137
|
-
// Where a drag STARTS from, in progress units. Always exactly 0 or 1: a gesture only ever
|
|
138
|
-
// begins at rest, since terminate/release always snap the value back to a resting state
|
|
139
|
-
// before another grant can fire.
|
|
140
|
-
let dragStartProgress = 0;
|
|
141
|
-
function animateProgressTo(open) {
|
|
142
|
-
dlog(`Drawer: animateProgressTo(open=${open}) starting at t=${Date.now()}`);
|
|
143
|
-
Animated.timing(progress, {
|
|
144
|
-
toValue: open ? 1 : 0,
|
|
145
|
-
duration: DRAWER_SNAP_DURATION,
|
|
146
|
-
// Native-driver wiring is deferred for v1 - see this file's header feasibility note. The
|
|
147
|
-
// JS timing loop still drives every frame, same as any other non-native-driven
|
|
148
|
-
// Animated.timing in this codebase.
|
|
149
|
-
useNativeDriver: false,
|
|
150
|
-
}).start();
|
|
151
|
-
}
|
|
152
|
-
const handle = {
|
|
153
|
-
openDrawer: () => {
|
|
154
|
-
dlog(`Drawer: openDrawer() called, isOpen=${state.value.isOpen} at t=${Date.now()}`);
|
|
155
|
-
animateProgressTo(true);
|
|
156
|
-
dispatch({ type: 'openDrawer' });
|
|
157
|
-
},
|
|
158
|
-
closeDrawer: () => {
|
|
159
|
-
dlog(`Drawer: closeDrawer() called, isOpen=${state.value.isOpen} at t=${Date.now()}`);
|
|
160
|
-
animateProgressTo(false);
|
|
161
|
-
dispatch({ type: 'closeDrawer' });
|
|
162
|
-
},
|
|
163
|
-
toggleDrawer: () => {
|
|
164
|
-
dlog(`Drawer: toggleDrawer() called, isOpen=${state.value.isOpen} at t=${Date.now()}`);
|
|
165
|
-
animateProgressTo(!state.value.isOpen);
|
|
166
|
-
dispatch({ type: 'toggleDrawer' });
|
|
167
|
-
},
|
|
168
|
-
jumpTo: (name) => {
|
|
169
|
-
// Captured BEFORE dispatch: unlike React's isOpenRef (only refreshed at the TOP of the
|
|
170
|
-
// next render, so it still holds the pre-dispatch value here since React batches the
|
|
171
|
-
// re-render asynchronously), this ref's `.value` mutates SYNCHRONOUSLY inside dispatch -
|
|
172
|
-
// reading it after dispatch would already see the reducer's own isOpen: false.
|
|
173
|
-
const wasOpen = state.value.isOpen;
|
|
174
|
-
dispatch({ type: 'jumpTo', name });
|
|
175
|
-
if (wasOpen)
|
|
176
|
-
animateProgressTo(false);
|
|
177
|
-
},
|
|
178
|
-
};
|
|
179
|
-
expose(handle);
|
|
180
|
-
const panResponder = PanResponder.create({
|
|
181
|
-
onStartShouldSetPanResponder: (event, gestureState) => shouldClaimDrawerSwipe(event, gestureState, windowDimensions.value.width, state.value.isOpen, currentOptions(), 'start'),
|
|
182
|
-
onMoveShouldSetPanResponder: (event, gestureState) => shouldClaimDrawerSwipe(event, gestureState, windowDimensions.value.width, state.value.isOpen, currentOptions(), 'move'),
|
|
183
|
-
onPanResponderGrant: () => {
|
|
184
|
-
dlog('Drawer: gesture grant');
|
|
185
|
-
dragStartProgress = state.value.isOpen ? 1 : 0;
|
|
186
|
-
},
|
|
187
|
-
onPanResponderMove: (_event, gestureState) => {
|
|
188
|
-
progress.setValue(resolveDragProgress(gestureState, dragStartProgress, currentOptions()));
|
|
189
|
-
},
|
|
190
|
-
onPanResponderRelease: (_event, gestureState) => {
|
|
191
|
-
const intent = resolveSwipeIntent(gestureState, state.value.isOpen, currentOptions());
|
|
192
|
-
const open = intent === 'open';
|
|
193
|
-
dlog(`Drawer: gesture release -> ${open ? 'open' : 'close'}`);
|
|
194
|
-
animateProgressTo(open);
|
|
195
|
-
dispatch(open ? { type: 'openDrawer' } : { type: 'closeDrawer' });
|
|
196
|
-
},
|
|
197
|
-
onPanResponderTerminate: () => {
|
|
198
|
-
dlog('Drawer: gesture terminated, snapping back');
|
|
199
|
-
animateProgressTo(state.value.isOpen);
|
|
200
|
-
},
|
|
201
|
-
});
|
|
202
|
-
// Only the focused route's screen is ever mounted (like Tab, unlike Stack which keeps every
|
|
203
|
-
// pushed route alive). One emitter per route.key, created lazily and cached for the
|
|
204
|
-
// navigator's whole lifetime (mirrors stack.ts's own `emitters` map) - see tabs.ts's matching
|
|
205
|
-
// comment for why a stable per-key lookup, not a recreate-on-change scheme, is what actually
|
|
206
|
-
// avoids racing Vue's own render/mount cycle regardless of which watch `flush` timing is used.
|
|
207
|
-
function focusedKeyOf(current) {
|
|
208
|
-
return current.routes[current.index]?.key;
|
|
209
|
-
}
|
|
210
|
-
const emitters = new Map();
|
|
211
|
-
function emitterFor(routeKey) {
|
|
212
|
-
let emitter = emitters.get(routeKey);
|
|
213
|
-
if (emitter === undefined) {
|
|
214
|
-
emitter = createNavigationEmitter();
|
|
215
|
-
emitters.set(routeKey, emitter);
|
|
216
|
-
}
|
|
217
|
-
return emitter;
|
|
218
|
-
}
|
|
219
|
-
// Emitted from onMounted (NOT from inside the render closure below, which runs before the
|
|
220
|
-
// focused screen's own component ever mounts - an emit there always has zero subscribers).
|
|
221
|
-
let focusedRouteKey = focusedKeyOf(state.value);
|
|
222
|
-
onMounted(() => {
|
|
223
|
-
if (focusedRouteKey !== undefined) {
|
|
224
|
-
dlog(`Drawer: route "${focusedRouteKey}" focused at t=${Date.now()}`);
|
|
225
|
-
emitterFor(focusedRouteKey).emit(NAVIGATION_EVENT_FOCUS);
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
// The bookkeeping (which key is focused) updates immediately so the render closure below
|
|
229
|
-
// always reads the right route; the actual emit is deferred to nextTick(), which resolves
|
|
230
|
-
// only after the CURRENT flush cycle fully drains - including the newly-focused screen's own
|
|
231
|
-
// onMounted (subscribing its useIsFocused/useFocusEffect listeners). flush:'post' alone is
|
|
232
|
-
// NOT enough here: it only guarantees this callback runs after THIS component's own render
|
|
233
|
-
// effect, not after an arbitrary sibling post-flush job (a just-mounted child's onMounted)
|
|
234
|
-
// that may be queued after it in the same batch - nextTick is the one API that waits for the
|
|
235
|
-
// WHOLE cycle, not just this watcher's own slot in it.
|
|
236
|
-
watch(() => focusedKeyOf(state.value), nextKey => {
|
|
237
|
-
const { blurKey, focusKey } = diffFocusedRoute(focusedRouteKey, nextKey);
|
|
238
|
-
if (blurKey === undefined && focusKey === undefined)
|
|
239
|
-
return;
|
|
240
|
-
focusedRouteKey = nextKey;
|
|
241
|
-
nextTick(() => {
|
|
242
|
-
if (blurKey !== undefined) {
|
|
243
|
-
dlog(`Drawer: route "${blurKey}" blurred at t=${Date.now()}`);
|
|
244
|
-
emitterFor(blurKey).emit(NAVIGATION_EVENT_BLUR);
|
|
245
|
-
}
|
|
246
|
-
if (focusKey !== undefined) {
|
|
247
|
-
dlog(`Drawer: route "${focusKey}" focused at t=${Date.now()}`);
|
|
248
|
-
emitterFor(focusKey).emit(NAVIGATION_EVENT_FOCUS);
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
});
|
|
252
|
-
onUnmounted(() => {
|
|
253
|
-
if (focusedRouteKey !== undefined)
|
|
254
|
-
emitterFor(focusedRouteKey).emit(NAVIGATION_EVENT_BLUR);
|
|
255
|
-
});
|
|
256
|
-
return () => {
|
|
257
|
-
const registry = collectRegistry(slots.default?.() ?? []);
|
|
258
|
-
const options = currentOptions();
|
|
259
|
-
const drawerStyle = isStyleProp(attrs.drawerStyle) ? attrs.drawerStyle : undefined;
|
|
260
|
-
if (registry.size === 0)
|
|
261
|
-
dlog('Drawer: no <Drawer.Screen> children registered');
|
|
262
|
-
const animated = isDrawerAnimated(options);
|
|
263
|
-
const geometry = resolveDrawerGeometry(options);
|
|
264
|
-
const panelTranslateX = animated
|
|
265
|
-
? progress.interpolate(resolveDrawerSlotInterpolation(geometry, 'panel').translateX)
|
|
266
|
-
: undefined;
|
|
267
|
-
const contentTranslateX = animated
|
|
268
|
-
? progress.interpolate(resolveDrawerSlotInterpolation(geometry, 'content').translateX)
|
|
269
|
-
: undefined;
|
|
270
|
-
// The overlay is a full-screen absolutely-positioned sibling BELOW content in paint order
|
|
271
|
-
// (see render-drawer.ts's drawerChildOrder) - for 'front' that's fine since content never
|
|
272
|
-
// moves, but for 'slide' content itself translates away by contentTranslateX, and without
|
|
273
|
-
// following it the overlay stays pinned full-screen. resolveDrawerSlotInterpolation's
|
|
274
|
-
// 'overlay' branch ties its translateX to the SAME range as content's for exactly this
|
|
275
|
-
// reason, so overlayOpacity and overlayTranslateX below share one resolved config.
|
|
276
|
-
const overlayInterpolation = animated
|
|
277
|
-
? resolveDrawerSlotInterpolation(geometry, 'overlay')
|
|
278
|
-
: undefined;
|
|
279
|
-
const overlayOpacity = overlayInterpolation
|
|
280
|
-
? progress.interpolate(overlayInterpolation.opacity)
|
|
281
|
-
: undefined;
|
|
282
|
-
const overlayTranslateX = overlayInterpolation
|
|
283
|
-
? progress.interpolate(overlayInterpolation.translateX)
|
|
284
|
-
: undefined;
|
|
285
|
-
const focusedRoute = state.value.routes[state.value.index];
|
|
286
|
-
const focusedEntry = focusedRoute ? registry.get(focusedRoute.name) : undefined;
|
|
287
|
-
if (focusedRoute && !focusedEntry) {
|
|
288
|
-
dlog(`Drawer: no screen registered for route name "${focusedRoute.name}"`);
|
|
289
|
-
}
|
|
290
|
-
const content = focusedEntry && focusedRoute
|
|
291
|
-
? h(NavigationScope, {
|
|
292
|
-
value: {
|
|
293
|
-
route: focusedRoute,
|
|
294
|
-
navigation: handle,
|
|
295
|
-
emitter: emitterFor(focusedRoute.key),
|
|
296
|
-
parent: ambientScopeRef?.value,
|
|
297
|
-
},
|
|
298
|
-
},
|
|
299
|
-
// No route/navigation props: the screen reads both through composables off the
|
|
300
|
-
// NavigationScope provided just above (useRoute / useDrawerNavigation).
|
|
301
|
-
() => h(focusedEntry.component))
|
|
302
|
-
: null;
|
|
303
|
-
const descriptors = {};
|
|
304
|
-
for (const route of state.value.routes) {
|
|
305
|
-
const entry = registry.get(route.name);
|
|
306
|
-
if (entry === undefined)
|
|
307
|
-
continue;
|
|
308
|
-
descriptors[route.key] = {
|
|
309
|
-
options: resolveDrawerScreenOptions(entry, { route, navigation: handle }),
|
|
310
|
-
navigation: handle,
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
const drawerContent = slots.drawerContent?.({ state: state.value, descriptors, navigation: handle }) ?? null;
|
|
314
|
-
const root = renderDrawer({
|
|
315
|
-
overlayColor: asString(attrs.overlayColor) ?? DRAWER_DEFAULT_OVERLAY_COLOR,
|
|
316
|
-
drawerStyle,
|
|
317
|
-
contentPassthrough: {},
|
|
318
|
-
overlayPassthrough: animated
|
|
319
|
-
? {
|
|
320
|
-
pointerEvents: state.value.isOpen ? 'auto' : 'none',
|
|
321
|
-
onStartShouldSetResponder: () => true,
|
|
322
|
-
onResponderRelease: () => {
|
|
323
|
-
animateProgressTo(false);
|
|
324
|
-
dispatch({ type: 'closeDrawer' });
|
|
325
|
-
},
|
|
326
|
-
}
|
|
327
|
-
: {},
|
|
328
|
-
panelPassthrough: {},
|
|
329
|
-
}, options);
|
|
330
|
-
const order = drawerChildOrder(options);
|
|
331
|
-
const slotDescriptors = new Map();
|
|
332
|
-
order.forEach((slot, index) => {
|
|
333
|
-
const descriptor = root.children[index];
|
|
334
|
-
if (typeof descriptor !== 'string')
|
|
335
|
-
slotDescriptors.set(slot, descriptor);
|
|
336
|
-
});
|
|
337
|
-
const slotChildren = {
|
|
338
|
-
content: content === null ? [] : [content],
|
|
339
|
-
overlay: null,
|
|
340
|
-
panel: drawerContent,
|
|
341
|
-
};
|
|
342
|
-
// Each animated style holds an AnimatedInterpolation node, not a plain number/color - it
|
|
343
|
-
// feeds only Animated.View's deliberately permissive `style?: unknown`, never the plain-
|
|
344
|
-
// IViewStyle branch below, so this stays untyped rather than widening IViewStyle.
|
|
345
|
-
const slotAnimatedStyle = {
|
|
346
|
-
content: contentTranslateX === undefined
|
|
347
|
-
? undefined
|
|
348
|
-
: { transform: [{ translateX: contentTranslateX }] },
|
|
349
|
-
overlay: overlayOpacity === undefined
|
|
350
|
-
? undefined
|
|
351
|
-
: { opacity: overlayOpacity, transform: [{ translateX: overlayTranslateX }] },
|
|
352
|
-
panel: panelTranslateX === undefined
|
|
353
|
-
? undefined
|
|
354
|
-
: { transform: [{ translateX: panelTranslateX }] },
|
|
355
|
-
};
|
|
356
|
-
function renderSlot(slot) {
|
|
357
|
-
const descriptor = slotDescriptors.get(slot);
|
|
358
|
-
if (descriptor === undefined)
|
|
359
|
-
return null;
|
|
360
|
-
const children = slotChildren[slot] ?? [];
|
|
361
|
-
const animatedStyle = slotAnimatedStyle[slot];
|
|
362
|
-
if (animatedStyle === undefined) {
|
|
363
|
-
return h(descriptor.type, { key: descriptor.key, ...descriptor.props }, children);
|
|
364
|
-
}
|
|
365
|
-
const style = [descriptor.props.style, animatedStyle];
|
|
366
|
-
return h(Animated.View, { key: descriptor.key, ...descriptor.props, style }, children);
|
|
367
|
-
}
|
|
368
|
-
const drawerChildren = order
|
|
369
|
-
.map(renderSlot)
|
|
370
|
-
.filter((element) => element !== null);
|
|
371
|
-
return h('symbiote-view', { style: root.props.style, ...panResponder.panHandlers }, drawerChildren);
|
|
372
|
-
};
|
|
373
|
-
}, { name: 'Drawer', inheritAttrs: false });
|
|
374
|
-
export const Drawer = Object.assign(DrawerImpl, { Screen: DrawerScreen });
|
|
375
|
-
// --- Explicit gap list vs the real react-native-gesture-handler + react-native-reanimated
|
|
376
|
-
// @react-navigation/drawer (confirmed against its current docs, mirrored verbatim from the React
|
|
377
|
-
// twin - the gaps are architectural, not adapter-specific) ---
|
|
378
|
-
// 1. `configureGestureHandler` - a raw react-native-gesture-handler `Gesture` object escape
|
|
379
|
-
// hatch. No PanResponder equivalent exists; not ported.
|
|
380
|
-
// 2. Simultaneous/failure gesture RELATIONSHIPS (gesture-handler's declarative composition vs a
|
|
381
|
-
// nested ScrollView, another PanResponder, etc.) - PanResponder only offers negotiation via the
|
|
382
|
-
// should-set boolean gates used here (edge-start + dominant-axis), which is more prone to an
|
|
383
|
-
// accidental hijack of a nested horizontal ScrollView/Swiper than gesture-handler's system.
|
|
384
|
-
// 3. `useDrawerProgress` - a Reanimated SharedValue read on the UI thread. `progress` here is a
|
|
385
|
-
// JS-thread AnimatedValue; interpolating it for consumer-facing content animation works, but
|
|
386
|
-
// without native-driver wiring (gap noted above) it does not carry the same synchronous
|
|
387
|
-
// UI-thread guarantee under JS-thread load.
|
|
388
|
-
// 4. `hideStatusBarOnOpen` / `keyboardDismissMode` / `statusBarAnimation` / `overlayStyle` - not
|
|
389
|
-
// wired in this pass; straightforward additions once StatusBar/Keyboard module wiring is
|
|
390
|
-
// needed here (not a PanResponder/Animated limitation, just unscoped for v1).
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { Component } from '@vue/runtime-core';
|
|
2
|
-
import type { IDrawerScreenOptions, IRoute, IDrawerNavigatorHandle } from '../core';
|
|
3
|
-
export type IDrawerScreenOptionsArgs = {
|
|
4
|
-
route: IRoute<unknown>;
|
|
5
|
-
navigation: IDrawerNavigatorHandle;
|
|
6
|
-
};
|
|
7
|
-
export type IDrawerScreenOptionsResolver = (props: IDrawerScreenOptionsArgs) => IDrawerScreenOptions;
|
|
8
|
-
export type IDrawerScreenProps = {
|
|
9
|
-
name: string;
|
|
10
|
-
component: Component;
|
|
11
|
-
options?: IDrawerScreenOptions | IDrawerScreenOptionsResolver;
|
|
12
|
-
initialParams?: unknown;
|
|
13
|
-
};
|
|
14
|
-
export declare const DrawerScreen: import("vue").DefineSetupFnComponent<IDrawerScreenProps, {}, {}, IDrawerScreenProps & {}, import("vue").PublicProps>;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
// Drawer.Screen: a declarative marker, never rendered on its own - Drawer reads its props via a
|
|
2
|
-
// default-slot scan (see drawer.ts's collectRegistry) to build the static name -> {component,
|
|
3
|
-
// options} registry, then mounts the FOCUSED route's component. The Vue twin of
|
|
4
|
-
// react/drawer-screen.ts's DrawerScreen, mirroring tab-screen.ts's slot-scanning marker (Tab is
|
|
5
|
-
// the closer sibling - both are fixed-route-list/no-push navigators) and screen.ts's; see
|
|
6
|
-
// CLAUDE.md <third_party_rn_packages_are_react_only> - this and Drawer import nothing from
|
|
7
|
-
// react-native.
|
|
8
|
-
import { defineComponent } from '@vue/runtime-core';
|
|
9
|
-
export const DrawerScreen = defineComponent(() => () => null, {
|
|
10
|
-
name: 'DrawerScreen',
|
|
11
|
-
inheritAttrs: false,
|
|
12
|
-
});
|
package/build/vue/index.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import '../register';
|
|
2
|
-
export { Stack } from './stack';
|
|
3
|
-
export type { INavigatorHandle, IStackProps } from './stack';
|
|
4
|
-
export { Screen } from './screen';
|
|
5
|
-
export type { IScreenOptionsArgs, IScreenOptionsResolver, IScreenProps, IVueScreenOptions, IVueSearchBarOptions, } from './screen';
|
|
6
|
-
export type { IScreenOptions, IStackAnimation, IStackPresentation } from '../core';
|
|
7
|
-
export { useNavigation, useStackNavigation, useTabNavigation, useDrawerNavigation, useRoute, useIsFocused, useFocusEffect, useNavigationState, } from './composables';
|
|
8
|
-
export type { INavigationHandle, IStackNavigationHandle, ITabNavigationHandle, IDrawerNavigationHandle, } from './composables';
|
|
9
|
-
export type { INavigationScopeValue, IAnyNavigatorHandle } from './navigation-context';
|
|
10
|
-
export { useLinkingIntegration } from './linking';
|
|
11
|
-
export type { ILinkingConfig, IScreenLinkingConfig } from '../core';
|
|
12
|
-
export { Tab } from './tabs';
|
|
13
|
-
export type { ITabNavigatorHandle, ITabProps } from './tabs';
|
|
14
|
-
export { TabScreen } from './tab-screen';
|
|
15
|
-
export type { ITabScreenOptionsArgs, ITabScreenOptionsResolver, ITabScreenProps, } from './tab-screen';
|
|
16
|
-
export type { ITabOptions, ITabBarIcon } from '../core';
|
|
17
|
-
export { Drawer } from './drawer';
|
|
18
|
-
export type { IDrawerContentSlotProps, IDrawerNavigatorHandle, IDrawerProps, IDrawerDescriptorMap, } from './drawer';
|
|
19
|
-
export { DrawerScreen } from './drawer-screen';
|
|
20
|
-
export type { IDrawerScreenOptionsArgs, IDrawerScreenOptionsResolver, IDrawerScreenProps, } from './drawer-screen';
|
|
21
|
-
export type { IDrawerOptions, IDrawerScreenOptions, IDrawerType, IDrawerPosition } from '../core';
|
package/build/vue/index.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
// @symbiote-native/navigation/vue: the Vue native-stack navigator over react-native-screens'
|
|
2
|
-
// native view primitives. Importing this barrel first registers the native views' ViewConfigs
|
|
3
|
-
// (../register, a side-effect import of the three codegen specs - never react-native-screens' own
|
|
4
|
-
// React components), then exposes Stack (with Stack.Screen attached) and the navigator handle.
|
|
5
|
-
import '../register';
|
|
6
|
-
export { Stack } from './stack/index.js';
|
|
7
|
-
export { Screen } from './screen.js';
|
|
8
|
-
export { useNavigation, useStackNavigation, useTabNavigation, useDrawerNavigation, useRoute, useIsFocused, useFocusEffect, useNavigationState, } from './composables/index.js';
|
|
9
|
-
export { useLinkingIntegration } from './linking.js';
|
|
10
|
-
// Tab: the bottom-tabs navigator, a PURE-JS UI (no react-native-screens views involved, so no
|
|
11
|
-
// extra ViewConfig registration is needed beyond the ../register import above).
|
|
12
|
-
export { Tab } from './tabs/index.js';
|
|
13
|
-
export { TabScreen } from './tab-screen.js';
|
|
14
|
-
// Drawer: the swipeable drawer navigator, a PURE-JS UI (PanResponder + Animated, no
|
|
15
|
-
// react-native-screens views involved, so no extra ViewConfig registration is needed beyond the
|
|
16
|
-
// ../register import above). See drawer.ts's header for the feasibility note re:
|
|
17
|
-
// react-native-gesture-handler / react-native-reanimated parity gaps.
|
|
18
|
-
export { Drawer } from './drawer/index.js';
|
|
19
|
-
export { DrawerScreen } from './drawer-screen.js';
|
package/build/vue/linking.d.ts
DELETED
package/build/vue/linking.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
// useLinkingIntegration: wires the framework-agnostic linking config (../core's
|
|
2
|
-
// resolveRouteFromUrl) onto an existing Stack's INavigatorHandle. Deliberately NOT called from
|
|
3
|
-
// inside stack.ts - app code owns the wiring, the same top-level-prop shape @react-navigation
|
|
4
|
-
// keeps `linking` in on NavigationContainer rather than baking it into the router itself.
|
|
5
|
-
//
|
|
6
|
-
// Vue-shape difference from React: React's twin takes the RESOLVED `navigatorHandle:
|
|
7
|
-
// INavigatorHandle` directly, because a React hook is called fresh on EVERY render with the
|
|
8
|
-
// caller's current value, and useImperativeHandle's ref assignment (a layout effect) is
|
|
9
|
-
// guaranteed to run before this hook's OWN mount effect, so `handleRef.current` is already
|
|
10
|
-
// populated by the time it's read. Vue's setup() runs exactly ONCE per component instance, so
|
|
11
|
-
// there is no "fresh call every render" to re-supply a resolved value, and a template/h() `ref`
|
|
12
|
-
// only resolves to Stack's expose()d handle AFTER mount - an eagerly-resolved argument evaluated
|
|
13
|
-
// at composable-call time (during setup, before mount) would be null. This composable therefore
|
|
14
|
-
// takes the Stack's `Ref<INavigatorHandle | null>` itself (the ref object, not its resolved
|
|
15
|
-
// value) and reads `.value` lazily inside onMounted - Vue's own onMounted hooks fire bottom-up
|
|
16
|
-
// (children before parents), giving the same "already populated" guarantee React's layout-effect
|
|
17
|
-
// ordering does.
|
|
18
|
-
import { onMounted, onUnmounted } from '@vue/runtime-core';
|
|
19
|
-
import { Linking, dlog } from '@symbiote-native/engine';
|
|
20
|
-
import { resolveRouteFromUrl } from '../core/index.js';
|
|
21
|
-
export function useLinkingIntegration(config, navigatorHandle) {
|
|
22
|
-
let cancelled = false;
|
|
23
|
-
let subscription;
|
|
24
|
-
function applyRoute(url, dispatch) {
|
|
25
|
-
const route = resolveRouteFromUrl(config, url);
|
|
26
|
-
if (route === null) {
|
|
27
|
-
dlog(`useLinkingIntegration: no route resolved for "${url}"`);
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
dispatch(route.name, route.params);
|
|
31
|
-
}
|
|
32
|
-
onMounted(() => {
|
|
33
|
-
const handle = navigatorHandle.value;
|
|
34
|
-
if (handle === null) {
|
|
35
|
-
dlog('useLinkingIntegration: navigatorHandle is not yet available at mount time');
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
Linking.getInitialURL()
|
|
39
|
-
.then(url => {
|
|
40
|
-
if (cancelled || url === null)
|
|
41
|
-
return;
|
|
42
|
-
dlog(`useLinkingIntegration: initial URL "${url}"`);
|
|
43
|
-
applyRoute(url, handle.replace);
|
|
44
|
-
})
|
|
45
|
-
.catch((error) => {
|
|
46
|
-
dlog(`useLinkingIntegration: getInitialURL failed: ${String(error)}`);
|
|
47
|
-
});
|
|
48
|
-
subscription = Linking.addEventListener('url', (event) => {
|
|
49
|
-
dlog(`useLinkingIntegration: url event "${event.url}"`);
|
|
50
|
-
applyRoute(event.url, handle.push);
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
onUnmounted(() => {
|
|
54
|
-
cancelled = true;
|
|
55
|
-
subscription?.remove();
|
|
56
|
-
});
|
|
57
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { ShallowRef } from '@vue/runtime-core';
|
|
2
|
-
import type { INavigationEmitter, IRoute, IAnyNavigatorHandle } from '../core';
|
|
3
|
-
export type { IAnyNavigatorHandle };
|
|
4
|
-
export type INavigationScopeValue = {
|
|
5
|
-
route: IRoute<unknown>;
|
|
6
|
-
navigation: IAnyNavigatorHandle;
|
|
7
|
-
emitter: INavigationEmitter;
|
|
8
|
-
parent?: INavigationScopeValue;
|
|
9
|
-
};
|
|
10
|
-
export declare function injectNavigationScope(): ShallowRef<INavigationScopeValue> | undefined;
|
|
11
|
-
export declare function requireNavigationScope(hookName: string): ShallowRef<INavigationScopeValue>;
|
|
12
|
-
export declare const NavigationScope: import("vue").DefineSetupFnComponent<{
|
|
13
|
-
value: INavigationScopeValue;
|
|
14
|
-
}, {}, {}, {
|
|
15
|
-
value: INavigationScopeValue;
|
|
16
|
-
} & {}, import("vue").PublicProps>;
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
// The lifecycle half of the framework-agnostic emitter (../core/navigation-events): Vue has no
|
|
2
|
-
// "Context" concept the way React does. The equivalent here is a plain provide/inject
|
|
3
|
-
// getter/setter pair over one shared InjectionKey (mirrors this codebase's own pd-widgets
|
|
4
|
-
// convention: `provideX(value)` writes, `injectX()` reads and throws if nothing was provided), so
|
|
5
|
-
// a screen's subtree can read its own route, navigator handle, and per-route emitter without
|
|
6
|
-
// prop-drilling - the Vue twin of react/navigation-context.ts's React Context, collapsed into one
|
|
7
|
-
// key the same way (a symbiote screen only ever needs its OWN route, no per-navigator
|
|
8
|
-
// param-list generics in v1 scope, see screen.ts).
|
|
9
|
-
//
|
|
10
|
-
// Vue's provide() must run once, synchronously, inside a component's OWN setup(). Unlike React,
|
|
11
|
-
// which re-creates a fresh `<Context.Provider value={...}>` element (and therefore a fresh value)
|
|
12
|
-
// on every render loop iteration, Vue's provide/inject is scoped to the component INSTANCE, not to
|
|
13
|
-
// a render-time position. Stack/Tab/Drawer keep every mounted route's screen alive as a SEPARATE
|
|
14
|
-
// sibling subtree (Stack keeps every pushed route mounted; see stack.ts), and each one needs its
|
|
15
|
-
// OWN distinct provided value (its own route/emitter) invisible to its siblings - that is an
|
|
16
|
-
// inherent property of any hierarchical dependency-injection scheme, provide/inject included, not
|
|
17
|
-
// a React artifact. NavigationScope is the small setup-boundary component that gives each route
|
|
18
|
-
// subtree its own instance to call provideNavigationScope from: stack.ts/tabs.ts/drawer.ts's
|
|
19
|
-
// render loop mounts ONE NavigationScope per route (keyed by route.key, exactly like React's
|
|
20
|
-
// per-route `<NavigationContext.Provider>`), and that instance's setup() calls the setter ONCE,
|
|
21
|
-
// refreshed from the latest `value` prop on every one of ITS OWN re-renders - consumers read
|
|
22
|
-
// `.value` off the injected ref, so a later change (e.g. setParams producing a new route object
|
|
23
|
-
// for the same key) still reaches them reactively.
|
|
24
|
-
//
|
|
25
|
-
// `parent` threads the ambient value a navigator read on ITS OWN mount (undefined at the root)
|
|
26
|
-
// into the value it provides to its own screens, forming a linked list - this is what lets a
|
|
27
|
-
// screen nested inside e.g. a Stack-screen-renders-a-Tab composition reach the enclosing Stack via
|
|
28
|
-
// useNavigation().getParent().
|
|
29
|
-
import { defineComponent, inject, provide, shallowRef } from '@vue/runtime-core';
|
|
30
|
-
const NAVIGATION_SCOPE_KEY = Symbol('navigation-scope');
|
|
31
|
-
// The setter half of the pair - writes the CURRENT route's scope onto the shared key. Called only
|
|
32
|
-
// from NavigationScope's own setup below (never directly by app code).
|
|
33
|
-
function provideNavigationScope(value) {
|
|
34
|
-
provide(NAVIGATION_SCOPE_KEY, value);
|
|
35
|
-
}
|
|
36
|
-
// The getter half of the pair - every composable in ./composables and every navigator
|
|
37
|
-
// (stack.ts/tabs.ts/drawer.ts, reading the AMBIENT/parent scope on their own mount) calls this
|
|
38
|
-
// directly; undefined simply means "no ancestor NavigationScope", which is a legitimate state (the
|
|
39
|
-
// nesting root) rather than an error - callers that require one throw their own message.
|
|
40
|
-
export function injectNavigationScope() {
|
|
41
|
-
return inject(NAVIGATION_SCOPE_KEY, undefined);
|
|
42
|
-
}
|
|
43
|
-
// The "throw if missing" half every composable in ./composables needs (useNavigation, useRoute,
|
|
44
|
-
// useIsFocused, useFocusEffect, useNavigationState) - they all called injectNavigationScope() and
|
|
45
|
-
// threw the same shaped error, differing only in which hook name the message names. Centralized
|
|
46
|
-
// here so a wording change lands once instead of five times.
|
|
47
|
-
export function requireNavigationScope(hookName) {
|
|
48
|
-
const scope = injectNavigationScope();
|
|
49
|
-
if (scope === undefined) {
|
|
50
|
-
throw new Error(`${hookName} must be used within a screen rendered by <Stack>, <Tab>, or <Drawer>`);
|
|
51
|
-
}
|
|
52
|
-
return scope;
|
|
53
|
-
}
|
|
54
|
-
// The setup-boundary component described above. `props: ['value']` is a REQUIRED runtime
|
|
55
|
-
// declaration here, not stylistic - without it Vue would route the incoming `value` into `attrs`
|
|
56
|
-
// instead of a tracked reactive `props` field, and the render closure's refresh below would
|
|
57
|
-
// silently keep re-reading a stale initial snapshot.
|
|
58
|
-
export const NavigationScope = defineComponent((props, { slots }) => {
|
|
59
|
-
const current = shallowRef(props.value);
|
|
60
|
-
provideNavigationScope(current);
|
|
61
|
-
return () => {
|
|
62
|
-
current.value = props.value;
|
|
63
|
-
return slots.default?.() ?? null;
|
|
64
|
-
};
|
|
65
|
-
}, { name: 'NavigationScope', props: ['value'] });
|
package/build/vue/screen.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { Component, Ref } from '@vue/runtime-core';
|
|
2
|
-
import type { IRoute } from '../core';
|
|
3
|
-
import type { ISearchBarCommands, ISearchBarOptions, IScreenOptions, INavigatorHandle } from '../core';
|
|
4
|
-
export type IVueSearchBarOptions = ISearchBarOptions & {
|
|
5
|
-
ref?: Ref<ISearchBarCommands | null>;
|
|
6
|
-
};
|
|
7
|
-
export type IVueScreenOptions = Omit<IScreenOptions, 'headerSearchBarOptions'> & {
|
|
8
|
-
headerSearchBarOptions?: IVueSearchBarOptions;
|
|
9
|
-
};
|
|
10
|
-
export type IScreenOptionsArgs = {
|
|
11
|
-
route: IRoute<unknown>;
|
|
12
|
-
navigation: INavigatorHandle;
|
|
13
|
-
};
|
|
14
|
-
export type IScreenOptionsResolver = (args: IScreenOptionsArgs) => IVueScreenOptions;
|
|
15
|
-
export type IScreenProps = {
|
|
16
|
-
name: string;
|
|
17
|
-
component: Component;
|
|
18
|
-
options?: IVueScreenOptions | IScreenOptionsResolver;
|
|
19
|
-
initialParams?: unknown;
|
|
20
|
-
};
|
|
21
|
-
export declare const Screen: import("vue").DefineSetupFnComponent<IScreenProps, {}, {}, IScreenProps & {}, import("vue").PublicProps>;
|
package/build/vue/screen.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
// Stack.Screen: a declarative marker, never rendered on its own - Stack reads its props via a
|
|
2
|
-
// default-slot scan (see stack.ts's collectRegistry) to build the static name -> {component,
|
|
3
|
-
// options} registry, then mounts the registered component itself for each pushed route. The Vue
|
|
4
|
-
// twin of react/screen.ts's Screen, using the slot-scanning pattern real Vue component libraries
|
|
5
|
-
// use for this exact declarative-marker-child shape (Element Plus's `<el-collapse-item>`, Ant
|
|
6
|
-
// Design Vue's `<a-menu-item>`): the parent inspects `vnode.type === Screen` and reads
|
|
7
|
-
// `vnode.props` directly, so Screen's own setup() never actually runs.
|
|
8
|
-
import { defineComponent } from '@vue/runtime-core';
|
|
9
|
-
export const Screen = defineComponent(() => () => null, {
|
|
10
|
-
name: 'Screen',
|
|
11
|
-
inheritAttrs: false,
|
|
12
|
-
});
|