@symbiote-native/navigation 2.0.1 → 2.0.2

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.
Files changed (155) hide show
  1. package/build/angular/drawer/index.d.ts +76 -0
  2. package/build/angular/drawer/index.js +508 -0
  3. package/build/angular/drawer-screen.directive.d.ts +14 -0
  4. package/build/angular/drawer-screen.directive.js +87 -0
  5. package/build/angular/index.d.ts +23 -0
  6. package/build/angular/index.js +22 -0
  7. package/build/angular/injectors/index.d.ts +13 -0
  8. package/build/angular/injectors/index.js +8 -0
  9. package/build/angular/injectors/inject-drawer-navigation.d.ts +4 -0
  10. package/build/angular/injectors/inject-drawer-navigation.js +12 -0
  11. package/build/angular/injectors/inject-focus-effect.d.ts +2 -0
  12. package/build/angular/injectors/inject-focus-effect.js +29 -0
  13. package/build/angular/injectors/inject-is-focused.d.ts +2 -0
  14. package/build/angular/injectors/inject-is-focused.js +20 -0
  15. package/build/angular/injectors/inject-navigation-state.d.ts +3 -0
  16. package/build/angular/injectors/inject-navigation-state.js +35 -0
  17. package/build/angular/injectors/inject-navigation.d.ts +7 -0
  18. package/build/angular/injectors/inject-navigation.js +16 -0
  19. package/build/angular/injectors/inject-route.d.ts +3 -0
  20. package/build/angular/injectors/inject-route.js +20 -0
  21. package/build/angular/injectors/inject-stack-navigation.d.ts +4 -0
  22. package/build/angular/injectors/inject-stack-navigation.js +15 -0
  23. package/build/angular/injectors/inject-tab-navigation.d.ts +4 -0
  24. package/build/angular/injectors/inject-tab-navigation.js +11 -0
  25. package/build/angular/linking.d.ts +3 -0
  26. package/build/angular/linking.js +43 -0
  27. package/build/angular/navigation-context.service.d.ts +13 -0
  28. package/build/angular/navigation-context.service.js +105 -0
  29. package/build/angular/navigation-scope.directive.d.ts +10 -0
  30. package/build/angular/navigation-scope.directive.js +90 -0
  31. package/build/angular/screen.directive.d.ts +22 -0
  32. package/build/angular/screen.directive.js +90 -0
  33. package/build/angular/search-bar-ref.directive.d.ts +10 -0
  34. package/build/angular/search-bar-ref.directive.js +88 -0
  35. package/build/angular/stack/index.d.ts +55 -0
  36. package/build/angular/stack/index.js +386 -0
  37. package/build/angular/tab-screen.directive.d.ts +14 -0
  38. package/build/angular/tab-screen.directive.js +87 -0
  39. package/build/angular/tabs/index.d.ts +42 -0
  40. package/build/angular/tabs/index.js +263 -0
  41. package/build/core/constants.d.ts +27 -0
  42. package/build/core/constants.js +61 -0
  43. package/build/core/drawer-options/index.d.ts +64 -0
  44. package/build/core/drawer-options/index.js +200 -0
  45. package/build/core/drawer-router-state/index.d.ts +21 -0
  46. package/build/core/drawer-router-state/index.js +40 -0
  47. package/build/core/guards.d.ts +2 -0
  48. package/build/core/guards.js +22 -0
  49. package/build/core/header-bar-buttons.d.ts +9 -0
  50. package/build/core/header-bar-buttons.js +163 -0
  51. package/build/core/index.d.ts +29 -0
  52. package/build/core/index.js +32 -0
  53. package/build/core/linking-config/index.d.ts +13 -0
  54. package/build/core/linking-config/index.js +171 -0
  55. package/build/core/navigation-events/index.d.ts +16 -0
  56. package/build/core/navigation-events/index.js +57 -0
  57. package/build/core/navigator-handles/index.d.ts +30 -0
  58. package/build/core/navigator-handles/index.js +15 -0
  59. package/build/core/navigator-props.d.ts +216 -0
  60. package/build/core/navigator-props.js +7 -0
  61. package/build/core/navigator-state/index.d.ts +36 -0
  62. package/build/core/navigator-state/index.js +81 -0
  63. package/build/core/render-drawer.d.ts +13 -0
  64. package/build/core/render-drawer.js +88 -0
  65. package/build/core/render-stack.d.ts +35 -0
  66. package/build/core/render-stack.js +294 -0
  67. package/build/core/render-tabs.d.ts +20 -0
  68. package/build/core/render-tabs.js +97 -0
  69. package/build/core/screen-options.d.ts +4 -0
  70. package/build/core/screen-options.js +133 -0
  71. package/build/core/search-bar-commands.d.ts +10 -0
  72. package/build/core/search-bar-commands.js +42 -0
  73. package/build/core/state-persistence/index.d.ts +3 -0
  74. package/build/core/state-persistence/index.js +23 -0
  75. package/build/core/tab-options.d.ts +13 -0
  76. package/build/core/tab-options.js +10 -0
  77. package/build/core/tab-router-state/index.d.ts +19 -0
  78. package/build/core/tab-router-state/index.js +51 -0
  79. package/build/react/collect-registry.d.ts +4 -0
  80. package/build/react/collect-registry.js +15 -0
  81. package/build/react/drawer/index.d.ts +29 -0
  82. package/build/react/drawer/index.js +277 -0
  83. package/build/react/drawer-screen.d.ts +14 -0
  84. package/build/react/drawer-screen.js +8 -0
  85. package/build/react/hooks/index.d.ts +12 -0
  86. package/build/react/hooks/index.js +8 -0
  87. package/build/react/hooks/use-drawer-navigation.d.ts +4 -0
  88. package/build/react/hooks/use-drawer-navigation.js +12 -0
  89. package/build/react/hooks/use-focus-effect.d.ts +2 -0
  90. package/build/react/hooks/use-focus-effect.js +29 -0
  91. package/build/react/hooks/use-is-focused.d.ts +1 -0
  92. package/build/react/hooks/use-is-focused.js +22 -0
  93. package/build/react/hooks/use-navigation-state/index.d.ts +2 -0
  94. package/build/react/hooks/use-navigation-state/index.js +39 -0
  95. package/build/react/hooks/use-navigation.d.ts +7 -0
  96. package/build/react/hooks/use-navigation.js +16 -0
  97. package/build/react/hooks/use-route.d.ts +2 -0
  98. package/build/react/hooks/use-route.js +8 -0
  99. package/build/react/hooks/use-stack-navigation.d.ts +4 -0
  100. package/build/react/hooks/use-stack-navigation.js +14 -0
  101. package/build/react/hooks/use-tab-navigation.d.ts +4 -0
  102. package/build/react/hooks/use-tab-navigation.js +12 -0
  103. package/build/react/index.d.ts +24 -0
  104. package/build/react/index.js +21 -0
  105. package/build/react/linking.d.ts +3 -0
  106. package/build/react/linking.js +47 -0
  107. package/build/react/navigation-context.d.ts +10 -0
  108. package/build/react/navigation-context.js +22 -0
  109. package/build/react/screen.d.ts +22 -0
  110. package/build/react/screen.js +7 -0
  111. package/build/react/stack/index.d.ts +13 -0
  112. package/build/react/stack/index.js +202 -0
  113. package/build/react/tab-screen.d.ts +15 -0
  114. package/build/react/tab-screen.js +8 -0
  115. package/build/react/tabs/index.d.ts +12 -0
  116. package/build/react/tabs/index.js +116 -0
  117. package/build/register.d.ts +7 -0
  118. package/build/register.js +58 -0
  119. package/build/vue/composables/index.d.ts +12 -0
  120. package/build/vue/composables/index.js +8 -0
  121. package/build/vue/composables/use-drawer-navigation.d.ts +5 -0
  122. package/build/vue/composables/use-drawer-navigation.js +15 -0
  123. package/build/vue/composables/use-focus-effect.d.ts +1 -0
  124. package/build/vue/composables/use-focus-effect.js +31 -0
  125. package/build/vue/composables/use-is-focused.d.ts +2 -0
  126. package/build/vue/composables/use-is-focused.js +28 -0
  127. package/build/vue/composables/use-navigation-state.d.ts +3 -0
  128. package/build/vue/composables/use-navigation-state.js +29 -0
  129. package/build/vue/composables/use-navigation.d.ts +8 -0
  130. package/build/vue/composables/use-navigation.js +13 -0
  131. package/build/vue/composables/use-route.d.ts +3 -0
  132. package/build/vue/composables/use-route.js +9 -0
  133. package/build/vue/composables/use-stack-navigation.d.ts +5 -0
  134. package/build/vue/composables/use-stack-navigation.js +18 -0
  135. package/build/vue/composables/use-tab-navigation.d.ts +5 -0
  136. package/build/vue/composables/use-tab-navigation.js +15 -0
  137. package/build/vue/drawer/index.d.ts +19 -0
  138. package/build/vue/drawer/index.js +390 -0
  139. package/build/vue/drawer-screen.d.ts +14 -0
  140. package/build/vue/drawer-screen.js +12 -0
  141. package/build/vue/index.d.ts +21 -0
  142. package/build/vue/index.js +19 -0
  143. package/build/vue/linking.d.ts +3 -0
  144. package/build/vue/linking.js +57 -0
  145. package/build/vue/navigation-context.d.ts +16 -0
  146. package/build/vue/navigation-context.js +65 -0
  147. package/build/vue/screen.d.ts +21 -0
  148. package/build/vue/screen.js +12 -0
  149. package/build/vue/stack/index.d.ts +9 -0
  150. package/build/vue/stack/index.js +279 -0
  151. package/build/vue/tab-screen.d.ts +14 -0
  152. package/build/vue/tab-screen.js +11 -0
  153. package/build/vue/tabs/index.d.ts +10 -0
  154. package/build/vue/tabs/index.js +209 -0
  155. package/package.json +3 -3
@@ -0,0 +1,263 @@
1
+ // Tab, the Angular lifecycle half. The focused-index router (tab-router-state) and the tab-bar
2
+ // Descriptor builder (render-tabs) live in @symbiote-native/navigation core, shared verbatim with
3
+ // the React/Vue adapters; here Angular supplies the lifecycle - a signal for the focused index, a
4
+ // per-instance counter for route-key generation, `jumpTo`/`setParams` as plain public methods
5
+ // directly on the class (no ref forwarding needed, see stack.ts's header, same reasoning: `Tab
6
+ // implements ITabNavigatorHandle`) - plus the descriptor bridge (`symbiote-descriptor-outlet`,
7
+ // `@symbiote-native/angular`) for the tab-bar leaf, exactly like Stack bridges its header config.
8
+ // Unlike Stack, a bottom-tabs bar is a PURE-JS UI: it paints
9
+ // ordinary `symbiote-view`/`symbiote-text` primitives via the shared render fn, so there is no
10
+ // react-native-screens ViewConfig to register - Tab needs no `../register` import. Every tag this
11
+ // template names (`View`, `symbiote-descriptor-outlet`) is a REAL imported Angular component (no
12
+ // raw non-dashed native tag names the way stack.ts needs `NO_ERRORS_SCHEMA` for), so no loosened
13
+ // schema is needed here at all.
14
+ //
15
+ // RESOLVED (see stack.ts's header, identical reasoning): `'Tab'` has an `ANCHOR_HOST_COMPONENTS`
16
+ // entry in `adapters/angular/src/renderer.ts`, so a real device build paints `<Tab>` correctly as
17
+ // a nested tag.
18
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
19
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
20
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
21
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
22
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
23
+ var _, done = false;
24
+ for (var i = decorators.length - 1; i >= 0; i--) {
25
+ var context = {};
26
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
27
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
28
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
29
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
30
+ if (kind === "accessor") {
31
+ if (result === void 0) continue;
32
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
33
+ if (_ = accept(result.get)) descriptor.get = _;
34
+ if (_ = accept(result.set)) descriptor.set = _;
35
+ if (_ = accept(result.init)) initializers.unshift(_);
36
+ }
37
+ else if (_ = accept(result)) {
38
+ if (kind === "field") initializers.unshift(_);
39
+ else descriptor[key] = _;
40
+ }
41
+ }
42
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
43
+ done = true;
44
+ };
45
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
46
+ var useValue = arguments.length > 2;
47
+ for (var i = 0; i < initializers.length; i++) {
48
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
49
+ }
50
+ return useValue ? value : void 0;
51
+ };
52
+ import { ChangeDetectionStrategy, Component, ContentChildren, Input, QueryList, signal, untracked, } from '@angular/core';
53
+ import { NgComponentOutlet } from '@angular/common';
54
+ import { dlog } from '@symbiote-native/engine';
55
+ import { DescriptorOutlet, View } from '@symbiote-native/angular';
56
+ import { NAVIGATION_EVENT_BLUR, NAVIGATION_EVENT_FOCUS, createInitialTabState, createNavigationEmitter, diffFocusedRoute, isFocusedRoute, renderTabBar, tabRouterReducer, } from '../../core/index.js';
57
+ import { NavigationScopeDirective } from '../navigation-scope.directive.js';
58
+ import { TabScreenDirective } from '../tab-screen.directive.js';
59
+ const TAB_ROOT_STYLE = { flex: 1 };
60
+ const TAB_CONTENT_STYLE = { flex: 1 };
61
+ let tabInstanceCounter = 0;
62
+ let Tab = (() => {
63
+ let _classDecorators = [Component({
64
+ selector: 'Tab',
65
+ standalone: true,
66
+ imports: [NgComponentOutlet, NavigationScopeDirective, DescriptorOutlet, View],
67
+ changeDetection: ChangeDetectionStrategy.OnPush,
68
+ template: `
69
+ <View [style]="rootStyle">
70
+ <View [style]="contentStyle">
71
+ @if (focusedRoute(); as route) {
72
+ @if (componentForRoute(route); as component) {
73
+ <ng-container
74
+ [symbioteNavigationScope]="route"
75
+ [navigation]="this"
76
+ [emitter]="focusedRouteEmitter()"
77
+ >
78
+ <ng-container *ngComponentOutlet="component" />
79
+ </ng-container>
80
+ }
81
+ }
82
+ </View>
83
+ <symbiote-descriptor-outlet [node]="tabBarDescriptor()" />
84
+ </View>
85
+ `,
86
+ })];
87
+ let _classDescriptor;
88
+ let _classExtraInitializers = [];
89
+ let _classThis;
90
+ let _tabScreenChildren_decorators;
91
+ let _tabScreenChildren_initializers = [];
92
+ let _tabScreenChildren_extraInitializers = [];
93
+ let _initialRouteName_decorators;
94
+ let _initialRouteName_initializers = [];
95
+ let _initialRouteName_extraInitializers = [];
96
+ let _screenOptions_decorators;
97
+ let _screenOptions_initializers = [];
98
+ let _screenOptions_extraInitializers = [];
99
+ var Tab = class {
100
+ static { _classThis = this; }
101
+ static {
102
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
103
+ _tabScreenChildren_decorators = [ContentChildren(TabScreenDirective)];
104
+ _initialRouteName_decorators = [Input()];
105
+ _screenOptions_decorators = [Input()];
106
+ __esDecorate(null, null, _tabScreenChildren_decorators, { kind: "field", name: "tabScreenChildren", static: false, private: false, access: { has: obj => "tabScreenChildren" in obj, get: obj => obj.tabScreenChildren, set: (obj, value) => { obj.tabScreenChildren = value; } }, metadata: _metadata }, _tabScreenChildren_initializers, _tabScreenChildren_extraInitializers);
107
+ __esDecorate(null, null, _initialRouteName_decorators, { kind: "field", name: "initialRouteName", static: false, private: false, access: { has: obj => "initialRouteName" in obj, get: obj => obj.initialRouteName, set: (obj, value) => { obj.initialRouteName = value; } }, metadata: _metadata }, _initialRouteName_initializers, _initialRouteName_extraInitializers);
108
+ __esDecorate(null, null, _screenOptions_decorators, { kind: "field", name: "screenOptions", static: false, private: false, access: { has: obj => "screenOptions" in obj, get: obj => obj.screenOptions, set: (obj, value) => { obj.screenOptions = value; } }, metadata: _metadata }, _screenOptions_initializers, _screenOptions_extraInitializers);
109
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
110
+ Tab = _classThis = _classDescriptor.value;
111
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
112
+ __runInitializers(_classThis, _classExtraInitializers);
113
+ }
114
+ tabScreenChildren = __runInitializers(this, _tabScreenChildren_initializers, void 0);
115
+ initialRouteName = (__runInitializers(this, _tabScreenChildren_extraInitializers), __runInitializers(this, _initialRouteName_initializers, void 0));
116
+ screenOptions = (__runInitializers(this, _initialRouteName_extraInitializers), __runInitializers(this, _screenOptions_initializers, void 0));
117
+ rootStyle = (__runInitializers(this, _screenOptions_extraInitializers), TAB_ROOT_STYLE);
118
+ contentStyle = TAB_CONTENT_STYLE;
119
+ routeIdPrefix = `tab-${(tabInstanceCounter += 1)}`;
120
+ // Keyed by name -> the LIVE TabScreenDirective instance - see stack.ts's matching comment for
121
+ // why a snapshot copy would go stale on an in-place `[options]`/`[component]` change.
122
+ registry = new Map();
123
+ tabScreenChildrenSubscription;
124
+ stateSignal = signal(undefined);
125
+ state = this.stateSignal.asReadonly();
126
+ currentEmitterKey;
127
+ currentEmitter;
128
+ jumpTo = (name, params) => this.dispatch({ type: 'jumpTo', name, params });
129
+ setParams = (params, key) => this.dispatch({ type: 'setParams', key, params });
130
+ ngAfterContentInit() {
131
+ this.rebuildRegistry();
132
+ this.initializeState();
133
+ this.tabScreenChildrenSubscription = this.tabScreenChildren.changes.subscribe(() => {
134
+ this.rebuildRegistry();
135
+ });
136
+ }
137
+ ngOnDestroy() {
138
+ this.tabScreenChildrenSubscription?.unsubscribe();
139
+ if (this.currentEmitter)
140
+ this.currentEmitter.emit(NAVIGATION_EVENT_BLUR);
141
+ }
142
+ rebuildRegistry() {
143
+ this.registry.clear();
144
+ for (const screen of this.tabScreenChildren) {
145
+ this.registry.set(screen.name, screen);
146
+ }
147
+ }
148
+ routesFromRegistry() {
149
+ return Array.from(this.registry.entries()).map(([name, entry]) => ({
150
+ key: `${this.routeIdPrefix}-${name}`,
151
+ name,
152
+ params: entry.initialParams,
153
+ }));
154
+ }
155
+ initializeState() {
156
+ if (this.stateSignal() !== undefined)
157
+ return;
158
+ const routes = this.routesFromRegistry();
159
+ if (routes.length === 0)
160
+ dlog('Tab: no <ng-template symbioteTabScreen> children registered');
161
+ this.stateSignal.set(createInitialTabState(routes, this.initialRouteName));
162
+ }
163
+ dispatch(action) {
164
+ const current = this.stateSignal();
165
+ if (current === undefined)
166
+ return;
167
+ this.stateSignal.set(tabRouterReducer(current, action));
168
+ }
169
+ resolveTabOptions(entry, route) {
170
+ const props = { route, navigation: this };
171
+ const own = typeof entry.options === 'function' ? entry.options(props) : entry.options;
172
+ return { ...this.screenOptions, ...own };
173
+ }
174
+ focusedRoute() {
175
+ const state = this.stateSignal();
176
+ return state?.routes[state.index];
177
+ }
178
+ componentForRoute(route) {
179
+ return this.registry.get(route.name)?.component ?? null;
180
+ }
181
+ // Lazily creates/replaces the focused route's emitter and synthesizes focus/blur - Tab paints
182
+ // its own bar in pure JS (no native onAppear/onDisappear the way Stack's RNSScreen has), so
183
+ // focus/blur is synthesized here exactly like react/tabs.ts's own useEffect does, just idempotent
184
+ // per read (called repeatedly with the same key during one CD pass) instead of dependency-array
185
+ // gated. Keyed on the route KEY, not the object, so a setParams-only change doesn't re-fire.
186
+ //
187
+ // Called from the template ([emitter]="focusedRouteEmitter()"), which runs inside Angular's
188
+ // reactive-read tracking context for the current CD pass - unlike React's useEffect, which runs
189
+ // in a separate post-commit phase. Two consequences of that, both fixed below:
190
+ //
191
+ // 1. Writing a signal from inside a tracked template read throws Angular's NG600 ("signal write
192
+ // during a template execution") - untracked() opts this whole synthesis out of that tracking
193
+ // context, matching what the post-commit useEffect phase gives React for free.
194
+ // 2. The NEW route's screen component (injectIsFocused's listener source) is created by
195
+ // *ngComponentOutlet AFTER this binding is evaluated, but still within the SAME synchronous
196
+ // template refresh - so emitting FOCUS right here fires to zero listeners and is silently
197
+ // lost forever (isFocused stays false permanently, even for the very first-focused screen).
198
+ // queueMicrotask defers the FOCUS emit past the end of the current synchronous refresh
199
+ // (which includes the new screen's construction), guaranteeing its injectIsFocused()
200
+ // listener is already attached by the time it fires. BLUR doesn't need this: the outgoing
201
+ // screen's listener was already attached on an earlier tick, so it's safe to fire now.
202
+ focusedRouteEmitter() {
203
+ const key = this.focusedRoute()?.key;
204
+ return untracked(() => {
205
+ if (key === this.currentEmitterKey && this.currentEmitter)
206
+ return this.currentEmitter;
207
+ const { blurKey, focusKey } = diffFocusedRoute(this.currentEmitterKey, key);
208
+ if (blurKey !== undefined && this.currentEmitter) {
209
+ dlog('Tab: previous route blurred');
210
+ this.currentEmitter.emit(NAVIGATION_EVENT_BLUR);
211
+ }
212
+ const emitter = createNavigationEmitter();
213
+ this.currentEmitter = emitter;
214
+ this.currentEmitterKey = key;
215
+ if (focusKey !== undefined) {
216
+ queueMicrotask(() => {
217
+ if (this.currentEmitter !== emitter)
218
+ return; // superseded by a later switch
219
+ dlog(`Tab: route "${this.focusedRoute()?.name}" focused`);
220
+ emitter.emit(NAVIGATION_EVENT_FOCUS);
221
+ });
222
+ }
223
+ return emitter;
224
+ });
225
+ }
226
+ tabBarDescriptor() {
227
+ const state = this.stateSignal();
228
+ const routes = state?.routes ?? [];
229
+ const focusedIndex = state?.index ?? 0;
230
+ const items = routes.map((route, index) => {
231
+ const entry = this.registry.get(route.name);
232
+ const focused = isFocusedRoute(index, focusedIndex);
233
+ if (!entry) {
234
+ dlog(`Tab: no screen registered for route name "${route.name}"`);
235
+ return { key: route.key, focused, label: route.name, passthrough: {} };
236
+ }
237
+ const options = this.resolveTabOptions(entry, route);
238
+ return {
239
+ key: route.key,
240
+ focused,
241
+ label: options.tabBarLabel ?? options.title ?? route.name,
242
+ icon: options.tabBarIcon,
243
+ badge: options.tabBarBadge,
244
+ activeTintColor: options.tabBarActiveTintColor,
245
+ inactiveTintColor: options.tabBarInactiveTintColor,
246
+ passthrough: {
247
+ onPress: () => this.jumpTo(route.name),
248
+ accessibilityRole: 'tab',
249
+ accessibilityState: { selected: focused },
250
+ },
251
+ };
252
+ });
253
+ const focusedRoute = this.focusedRoute();
254
+ const focusedEntry = focusedRoute ? this.registry.get(focusedRoute.name) : undefined;
255
+ const focusedOptions = focusedEntry && focusedRoute
256
+ ? this.resolveTabOptions(focusedEntry, focusedRoute)
257
+ : this.screenOptions;
258
+ return renderTabBar({ items, style: focusedOptions?.tabBarStyle, passthrough: {} });
259
+ }
260
+ };
261
+ return Tab = _classThis;
262
+ })();
263
+ export { Tab };
@@ -0,0 +1,27 @@
1
+ export declare const RNS_SCREEN_VIEW_NAME = "RNSScreen";
2
+ export declare const RNS_MODAL_SCREEN_VIEW_NAME = "RNSModalScreen";
3
+ export declare const RNS_SCREEN_STACK_VIEW_NAME = "RNSScreenStack";
4
+ export declare const RNS_SCREEN_STACK_HEADER_CONFIG_VIEW_NAME = "RNSScreenStackHeaderConfig";
5
+ export declare const RNS_SCREEN_STACK_HEADER_SUBVIEW_VIEW_NAME = "RNSScreenStackHeaderSubview";
6
+ export declare const RNS_SEARCH_BAR_VIEW_NAME = "RNSSearchBar";
7
+ export declare const RNS_SCREEN_CONTENT_WRAPPER_VIEW_NAME = "RNSScreenContentWrapper";
8
+ export declare const SCREEN_ON_APPEAR = "onAppear";
9
+ export declare const SCREEN_ON_DISAPPEAR = "onDisappear";
10
+ export declare const SCREEN_ON_WILL_APPEAR = "onWillAppear";
11
+ export declare const SCREEN_ON_WILL_DISAPPEAR = "onWillDisappear";
12
+ export declare const SCREEN_ON_DISMISSED = "onDismissed";
13
+ export declare const SCREEN_ON_HEADER_BACK_BUTTON_CLICKED = "onHeaderBackButtonClicked";
14
+ export declare const STACK_ON_FINISH_TRANSITIONING = "onFinishTransitioning";
15
+ export declare const HEADER_ON_PRESS_BAR_BUTTON_ITEM = "onPressHeaderBarButtonItem";
16
+ export declare const HEADER_ON_PRESS_BAR_BUTTON_MENU_ITEM = "onPressHeaderBarButtonMenuItem";
17
+ export declare const SEARCH_BAR_ON_FOCUS = "onSearchFocus";
18
+ export declare const SEARCH_BAR_ON_BLUR = "onSearchBlur";
19
+ export declare const SEARCH_BAR_ON_SEARCH_BUTTON_PRESS = "onSearchButtonPress";
20
+ export declare const SEARCH_BAR_ON_CANCEL_BUTTON_PRESS = "onCancelButtonPress";
21
+ export declare const SEARCH_BAR_ON_CHANGE_TEXT = "onChangeText";
22
+ export declare const SEARCH_BAR_ON_CLOSE = "onClose";
23
+ export declare const SEARCH_BAR_ON_OPEN = "onOpen";
24
+ export declare const SCREEN_ACTIVITY_STATE_FOCUSED = 2;
25
+ export declare const SCREEN_ACTIVITY_STATE_INACTIVE = 0;
26
+ export declare const STACK_DEFAULT_ANIMATION = "default";
27
+ export declare const STACK_DEFAULT_PRESENTATION = "push";
@@ -0,0 +1,61 @@
1
+ // Framework-agnostic constants for the react-native-screens native stack primitives. symbiote
2
+ // ships zero ViewConfig metadata for these views at runtime - the engine derives events and
3
+ // color processors from react-native-screens' own codegen ViewConfig - this file only names the
4
+ // Fabric view names and event prop keys every adapter binds against, plus the RNS activityState
5
+ // convention (mirrors @react-navigation/native-stack's internal screen focus signaling).
6
+ // The Fabric view names react-native-screens' codegen components register. descriptorFor passes
7
+ // any non-`symbiote-` string through as a raw Fabric name, so the render fns emit these directly.
8
+ export const RNS_SCREEN_VIEW_NAME = 'RNSScreen';
9
+ // The modal-presentation twin of RNSScreen - see resolveScreenViewName's comment in
10
+ // render-stack.ts for why a formSheet/modal screen MUST use this different Fabric name.
11
+ export const RNS_MODAL_SCREEN_VIEW_NAME = 'RNSModalScreen';
12
+ export const RNS_SCREEN_STACK_VIEW_NAME = 'RNSScreenStack';
13
+ export const RNS_SCREEN_STACK_HEADER_CONFIG_VIEW_NAME = 'RNSScreenStackHeaderConfig';
14
+ // The header-subview leaf react-native-screens itself uses to host arbitrary framework children
15
+ // (left/right/back/title/center/searchBar) inside the header. Registered here for ground-truth
16
+ // parity, but unconsumed by any fold in v1 scope: renderHeaderConfig's Descriptor never takes
17
+ // framework children (see render-stack.ts), so mounting a subview child is left to the adapter.
18
+ export const RNS_SCREEN_STACK_HEADER_SUBVIEW_VIEW_NAME = 'RNSScreenStackHeaderSubview';
19
+ export const RNS_SEARCH_BAR_VIEW_NAME = 'RNSSearchBar';
20
+ // react-native-screens' own JS layer (ScreenStackItem -> DebugContainer -> ScreenContentWrapper)
21
+ // ALWAYS wraps a screen's content in this native view, `collapsable: false` so Fabric can never
22
+ // flatten it away. RNSScreen.mm's `registerContentWrapper:contentHeightErrata:` is a no-op for
23
+ // `push` (early-returns NO), but for `formSheet` it's how the native side ever learns the
24
+ // content's real size - RNSScreenContentWrapper's `willMoveToWindow` walks up to the ancestor
25
+ // RNSScreen and registers itself, and its `receivedReactFrame:` delegate callback is what feeds
26
+ // the sheet's own height/detent machinery. Skipping this wrapper is harmless for `push` (why it
27
+ // went unnoticed) but leaves a formSheet screen's native side with nothing to attach to at all.
28
+ export const RNS_SCREEN_CONTENT_WRAPPER_VIEW_NAME = 'RNSScreenContentWrapper';
29
+ // RNSScreen event prop keys (all DirectEventHandler in the codegen spec).
30
+ export const SCREEN_ON_APPEAR = 'onAppear';
31
+ export const SCREEN_ON_DISAPPEAR = 'onDisappear';
32
+ export const SCREEN_ON_WILL_APPEAR = 'onWillAppear';
33
+ export const SCREEN_ON_WILL_DISAPPEAR = 'onWillDisappear';
34
+ export const SCREEN_ON_DISMISSED = 'onDismissed';
35
+ export const SCREEN_ON_HEADER_BACK_BUTTON_CLICKED = 'onHeaderBackButtonClicked';
36
+ // RNSScreenStack event prop key.
37
+ export const STACK_ON_FINISH_TRANSITIONING = 'onFinishTransitioning';
38
+ // RNSScreenStackHeaderConfig event prop keys (both DirectEventHandler in the codegen spec): a
39
+ // plain bar-button-item press, and a press on a (possibly nested) menu action of a 'menu'-type
40
+ // bar-button item.
41
+ export const HEADER_ON_PRESS_BAR_BUTTON_ITEM = 'onPressHeaderBarButtonItem';
42
+ export const HEADER_ON_PRESS_BAR_BUTTON_MENU_ITEM = 'onPressHeaderBarButtonMenuItem';
43
+ // RNSSearchBar event prop keys (all DirectEventHandler in the codegen spec). onClose/onOpen are
44
+ // Android-only per the codegen spec's own comment.
45
+ export const SEARCH_BAR_ON_FOCUS = 'onSearchFocus';
46
+ export const SEARCH_BAR_ON_BLUR = 'onSearchBlur';
47
+ export const SEARCH_BAR_ON_SEARCH_BUTTON_PRESS = 'onSearchButtonPress';
48
+ export const SEARCH_BAR_ON_CANCEL_BUTTON_PRESS = 'onCancelButtonPress';
49
+ export const SEARCH_BAR_ON_CHANGE_TEXT = 'onChangeText';
50
+ export const SEARCH_BAR_ON_CLOSE = 'onClose';
51
+ export const SEARCH_BAR_ON_OPEN = 'onOpen';
52
+ // activityState RNS reads to decide which screen is attached to the native hierarchy. Per
53
+ // react-native-screens' native RNSScreen.mm (maybeAssertActivityStateProgressionOldValue), an
54
+ // already-mounted NativeStack screen's activityState can never DECREASE via a plain prop update -
55
+ // only 0 (detached) and 2 (attached + focused) are used; there is no "1 = transitioning" value in
56
+ // @react-navigation/native-stack's actual algorithm (NativeStackView.native.tsx computes
57
+ // `activityState={isInactive ? 0 : 2}`, never 1). See computeActivityState for the full rationale.
58
+ export const SCREEN_ACTIVITY_STATE_FOCUSED = 2;
59
+ export const SCREEN_ACTIVITY_STATE_INACTIVE = 0;
60
+ export const STACK_DEFAULT_ANIMATION = 'default';
61
+ export const STACK_DEFAULT_PRESENTATION = 'push';
@@ -0,0 +1,64 @@
1
+ import type { IPanResponderGestureState, ISymbioteEvent } from '@symbiote-native/engine';
2
+ export type IDrawerType = 'front' | 'back' | 'slide' | 'permanent';
3
+ export type IDrawerPosition = 'left' | 'right';
4
+ export interface IDrawerOptions {
5
+ drawerType?: IDrawerType;
6
+ drawerPosition?: IDrawerPosition;
7
+ drawerWidth?: number;
8
+ overlayColor?: string;
9
+ swipeEnabled?: boolean;
10
+ swipeEdgeWidth?: number;
11
+ swipeMinDistance?: number;
12
+ swipeMinVelocity?: number;
13
+ }
14
+ export interface IDrawerScreenOptions {
15
+ title?: string;
16
+ drawerLabel?: string;
17
+ }
18
+ export declare const DRAWER_DEFAULT_TYPE: IDrawerType;
19
+ export declare const DRAWER_DEFAULT_POSITION: IDrawerPosition;
20
+ export declare const DRAWER_DEFAULT_WIDTH = 280;
21
+ export declare const DRAWER_DEFAULT_OVERLAY_COLOR = "rgba(0, 0, 0, 0.5)";
22
+ export declare const DRAWER_DEFAULT_SWIPE_ENABLED = true;
23
+ export declare const DRAWER_DEFAULT_SWIPE_EDGE_WIDTH = 32;
24
+ export declare const DRAWER_DEFAULT_SWIPE_MIN_DISTANCE = 60;
25
+ export declare const DRAWER_DEFAULT_SWIPE_MIN_VELOCITY = 0.5;
26
+ export declare function resolveDrawerWidth(options: IDrawerOptions): number;
27
+ export declare function resolveDrawerType(options: IDrawerOptions): IDrawerType;
28
+ export declare function resolveDrawerPosition(options: IDrawerOptions): IDrawerPosition;
29
+ export type IDrawerGeometry = {
30
+ panelTranslateXClosed: number;
31
+ panelTranslateXOpen: number;
32
+ contentTranslateXClosed: number;
33
+ contentTranslateXOpen: number;
34
+ overlayOpacityClosed: number;
35
+ overlayOpacityOpen: number;
36
+ };
37
+ export declare function isDrawerAnimated(options: IDrawerOptions): boolean;
38
+ export declare function isDrawerOverlayVisible(options: IDrawerOptions): boolean;
39
+ export declare function resolveDrawerGeometry(options: IDrawerOptions): IDrawerGeometry;
40
+ export type IDrawerInterpolationRange = {
41
+ inputRange: readonly [number, number];
42
+ outputRange: readonly [number, number];
43
+ };
44
+ export type IDrawerContentSlotInterpolation = {
45
+ translateX: IDrawerInterpolationRange;
46
+ };
47
+ export type IDrawerOverlaySlotInterpolation = {
48
+ opacity: IDrawerInterpolationRange;
49
+ translateX: IDrawerInterpolationRange;
50
+ };
51
+ export type IDrawerPanelSlotInterpolation = {
52
+ translateX: IDrawerInterpolationRange;
53
+ };
54
+ export declare function resolveDrawerSlotInterpolation(geometry: IDrawerGeometry, slot: 'content'): IDrawerContentSlotInterpolation;
55
+ export declare function resolveDrawerSlotInterpolation(geometry: IDrawerGeometry, slot: 'overlay'): IDrawerOverlaySlotInterpolation;
56
+ export declare function resolveDrawerSlotInterpolation(geometry: IDrawerGeometry, slot: 'panel'): IDrawerPanelSlotInterpolation;
57
+ export declare function isSwipeStartInEdge(startX: number, screenWidth: number, isOpen: boolean, options: IDrawerOptions): boolean;
58
+ export declare function isHorizontalDrag(gestureState: IPanResponderGestureState): boolean;
59
+ export type ISwipeIntent = 'open' | 'close';
60
+ export declare function resolveSwipeIntent(gestureState: IPanResponderGestureState, isOpen: boolean, options: IDrawerOptions): ISwipeIntent;
61
+ export declare function clamp01(value: number): number;
62
+ export declare function startPageXOf(event: ISymbioteEvent): number | undefined;
63
+ export declare function resolveDragProgress(gestureState: IPanResponderGestureState, dragStartProgress: number, options: IDrawerOptions): number;
64
+ export declare function shouldClaimDrawerSwipe(event: ISymbioteEvent, gestureState: IPanResponderGestureState, screenWidth: number, isOpen: boolean, options: IDrawerOptions, phase: 'start' | 'move'): boolean;
@@ -0,0 +1,200 @@
1
+ // Drawer option types + the pure swipe/geometry math. Framework-agnostic (numbers and
2
+ // booleans only, per CLAUDE.md <prop_types_split_agnostic_vs_per_adapter>), shared verbatim by
3
+ // every adapter. Mirrors react-navigation's Drawer prop surface (confirmed against the current
4
+ // @react-navigation/drawer docs): drawerType 'front'/'back'/'slide'/'permanent', drawerPosition,
5
+ // drawerWidth, overlayColor, swipeEnabled + the three swipe-tuning knobs. What is NOT ported:
6
+ // `configureGestureHandler` - a react-native-gesture-handler-specific escape hatch with no
7
+ // PanResponder equivalent (see packages/navigation's README / the drawer feasibility note for the
8
+ // full gap list).
9
+ import { isRecord } from '../guards.js';
10
+ export const DRAWER_DEFAULT_TYPE = 'front';
11
+ export const DRAWER_DEFAULT_POSITION = 'left';
12
+ export const DRAWER_DEFAULT_WIDTH = 280;
13
+ export const DRAWER_DEFAULT_OVERLAY_COLOR = 'rgba(0, 0, 0, 0.5)';
14
+ export const DRAWER_DEFAULT_SWIPE_ENABLED = true;
15
+ export const DRAWER_DEFAULT_SWIPE_EDGE_WIDTH = 32;
16
+ export const DRAWER_DEFAULT_SWIPE_MIN_DISTANCE = 60;
17
+ // react-navigation's 500 is px/second; gestureState.vx is px/ms, so the default converts.
18
+ export const DRAWER_DEFAULT_SWIPE_MIN_VELOCITY = 0.5;
19
+ // Exported so render-drawer.ts and the adapter can resolve the same defaults without
20
+ // re-declaring the `??` fallback (a second copy would drift the instant a default changes).
21
+ export function resolveDrawerWidth(options) {
22
+ return options.drawerWidth ?? DRAWER_DEFAULT_WIDTH;
23
+ }
24
+ export function resolveDrawerType(options) {
25
+ return options.drawerType ?? DRAWER_DEFAULT_TYPE;
26
+ }
27
+ export function resolveDrawerPosition(options) {
28
+ return options.drawerPosition ?? DRAWER_DEFAULT_POSITION;
29
+ }
30
+ export function isDrawerAnimated(options) {
31
+ return resolveDrawerType(options) !== 'permanent';
32
+ }
33
+ export function isDrawerOverlayVisible(options) {
34
+ const type = resolveDrawerType(options);
35
+ return type === 'front' || type === 'slide';
36
+ }
37
+ // front: only the panel moves (-width -> 0), content stays put, overlay fades in.
38
+ // back: only the content moves, away from the position edge, to reveal the stationary panel;
39
+ // no overlay (the panel sits fully behind the content, nothing to dim).
40
+ // slide: panel AND content move together by the same delta, overlay fades in (content still
41
+ // covers the panel partially as they slide in tandem, same as front visually at rest).
42
+ // permanent: static, all zero (isDrawerAnimated() gates callers off this path already).
43
+ export function resolveDrawerGeometry(options) {
44
+ const width = resolveDrawerWidth(options);
45
+ const type = resolveDrawerType(options);
46
+ const sign = resolveDrawerPosition(options) === 'left' ? 1 : -1;
47
+ const closedPanelX = -sign * width;
48
+ const openContentX = sign * width;
49
+ switch (type) {
50
+ case 'back':
51
+ return {
52
+ panelTranslateXClosed: 0,
53
+ panelTranslateXOpen: 0,
54
+ contentTranslateXClosed: 0,
55
+ contentTranslateXOpen: openContentX,
56
+ overlayOpacityClosed: 0,
57
+ overlayOpacityOpen: 0,
58
+ };
59
+ case 'slide':
60
+ return {
61
+ panelTranslateXClosed: closedPanelX,
62
+ panelTranslateXOpen: 0,
63
+ contentTranslateXClosed: 0,
64
+ contentTranslateXOpen: openContentX,
65
+ overlayOpacityClosed: 0,
66
+ overlayOpacityOpen: 1,
67
+ };
68
+ case 'front':
69
+ case 'permanent':
70
+ default:
71
+ return {
72
+ panelTranslateXClosed: closedPanelX,
73
+ panelTranslateXOpen: 0,
74
+ contentTranslateXClosed: 0,
75
+ contentTranslateXOpen: 0,
76
+ overlayOpacityClosed: 0,
77
+ overlayOpacityOpen: 1,
78
+ };
79
+ }
80
+ }
81
+ const DRAWER_PROGRESS_INPUT_RANGE = [0, 1];
82
+ export function resolveDrawerSlotInterpolation(geometry, slot) {
83
+ switch (slot) {
84
+ case 'content':
85
+ return {
86
+ translateX: {
87
+ inputRange: DRAWER_PROGRESS_INPUT_RANGE,
88
+ outputRange: [geometry.contentTranslateXClosed, geometry.contentTranslateXOpen],
89
+ },
90
+ };
91
+ case 'overlay':
92
+ return {
93
+ opacity: {
94
+ inputRange: DRAWER_PROGRESS_INPUT_RANGE,
95
+ outputRange: [geometry.overlayOpacityClosed, geometry.overlayOpacityOpen],
96
+ },
97
+ translateX: {
98
+ inputRange: DRAWER_PROGRESS_INPUT_RANGE,
99
+ outputRange: [geometry.contentTranslateXClosed, geometry.contentTranslateXOpen],
100
+ },
101
+ };
102
+ case 'panel':
103
+ return {
104
+ translateX: {
105
+ inputRange: DRAWER_PROGRESS_INPUT_RANGE,
106
+ outputRange: [geometry.panelTranslateXClosed, geometry.panelTranslateXOpen],
107
+ },
108
+ };
109
+ }
110
+ }
111
+ // onStartShouldSetPanResponder gate: while closed, a swipe must START within swipeEdgeWidth of
112
+ // the drawer's position edge (mirrors react-navigation's swipeEdgeWidth). While open, the whole
113
+ // content + overlay area is fair game to drag-close, matching the intuitive "swipe anywhere to
114
+ // dismiss an open drawer" behavior every drawer implementation (including RN's dropped native
115
+ // DrawerLayoutAndroid) shares.
116
+ export function isSwipeStartInEdge(startX, screenWidth, isOpen, options) {
117
+ if (isOpen)
118
+ return true;
119
+ const edgeWidth = options.swipeEdgeWidth ?? DRAWER_DEFAULT_SWIPE_EDGE_WIDTH;
120
+ return resolveDrawerPosition(options) === 'left'
121
+ ? startX <= edgeWidth
122
+ : startX >= screenWidth - edgeWidth;
123
+ }
124
+ // onMoveShouldSetPanResponder gate: only claim the gesture once it reads as a horizontal drag
125
+ // (RN's own PanResponder examples use this exact dominant-axis check to stay out of a vertical
126
+ // ScrollView's way).
127
+ const MOVE_CLAIM_THRESHOLD = 5;
128
+ export function isHorizontalDrag(gestureState) {
129
+ return (Math.abs(gestureState.dx) > MOVE_CLAIM_THRESHOLD &&
130
+ Math.abs(gestureState.dx) > Math.abs(gestureState.dy));
131
+ }
132
+ // The release-time decision, structurally the same shape as switch.ts's shouldSnapBack: a pure
133
+ // predicate over the accumulated gesture plus the option thresholds, no framework. Distance OR
134
+ // velocity past its threshold, in the direction that would move the drawer toward the opposite
135
+ // of its current state, snaps it there; anything else snaps back to where it started.
136
+ export function resolveSwipeIntent(gestureState, isOpen, options) {
137
+ const sign = resolveDrawerPosition(options) === 'left' ? 1 : -1;
138
+ // Positive `signedDelta` means "dragging toward open" for a left drawer's rightward drag (and
139
+ // symmetrically for a right drawer's leftward drag).
140
+ const signedDx = sign * gestureState.dx;
141
+ const signedVx = sign * gestureState.vx;
142
+ const minDistance = options.swipeMinDistance ?? DRAWER_DEFAULT_SWIPE_MIN_DISTANCE;
143
+ const minVelocity = options.swipeMinVelocity ?? DRAWER_DEFAULT_SWIPE_MIN_VELOCITY;
144
+ const pastDistance = Math.abs(signedDx) >= minDistance;
145
+ const pastVelocity = Math.abs(signedVx) >= minVelocity;
146
+ if (!pastDistance && !pastVelocity)
147
+ return isOpen ? 'open' : 'close';
148
+ // Velocity wins the direction call when it clears its own threshold (a fast flick can reverse
149
+ // a short drag); otherwise the drag distance's sign decides.
150
+ const towardOpen = pastVelocity ? signedVx > 0 : signedDx > 0;
151
+ return towardOpen ? 'open' : 'close';
152
+ }
153
+ export function clamp01(value) {
154
+ return Math.min(1, Math.max(0, value));
155
+ }
156
+ function toFiniteNumber(value) {
157
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
158
+ }
159
+ // The touch's real starting page-X, read off the raw event rather than gestureState.x0: the
160
+ // engine's capture phase (core/engine/src/pan-responder/index.ts's
161
+ // onStartShouldSetResponderCapture/onMoveShouldSetResponderCapture) resets gestureState before
162
+ // the bubble-phase onStartShouldSetPanResponder/onMoveShouldSetPanResponder gates run, so x0 is
163
+ // only populated later, inside onResponderGrant. Shaped like core/engine/src/events/index.ts's
164
+ // readTouchPoint but not the same function: that one requires both pageX AND pageY and returns
165
+ // an {x, y} pair for touch-history bookkeeping; this only needs pageX and is private to the
166
+ // SET-gate below, so it stays a separate, narrower helper rather than forcing a shared contract
167
+ // onto two callers with different needs.
168
+ export function startPageXOf(event) {
169
+ const { nativeEvent } = event;
170
+ const direct = toFiniteNumber(nativeEvent.pageX);
171
+ if (direct !== undefined)
172
+ return direct;
173
+ const touches = nativeEvent.touches;
174
+ if (Array.isArray(touches) && isRecord(touches[0]))
175
+ return toFiniteNumber(touches[0].pageX);
176
+ return undefined;
177
+ }
178
+ // onPanResponderMove's drag-to-progress math: how far the touch has traveled, in progress units
179
+ // (0..1), added to wherever the drag started from. Mirrors the SAME sign convention as
180
+ // resolveSwipeIntent (left drawer: rightward drag is positive-toward-open).
181
+ export function resolveDragProgress(gestureState, dragStartProgress, options) {
182
+ const width = resolveDrawerWidth(options);
183
+ const sign = resolveDrawerPosition(options) === 'right' ? -1 : 1;
184
+ const delta = (sign * gestureState.dx) / width;
185
+ return clamp01(dragStartProgress + delta);
186
+ }
187
+ // The composed onStartShouldSetPanResponder/onMoveShouldSetPanResponder gate: swipeEnabled ->
188
+ // isDrawerAnimated -> isSwipeStartInEdge, then (move only) isHorizontalDrag on top. The start
189
+ // gate omits the dominant-axis check on purpose (react-navigation's own edge-swipe idiom: claim
190
+ // on entering the edge, confirm direction only once movement exists to measure).
191
+ export function shouldClaimDrawerSwipe(event, gestureState, screenWidth, isOpen, options, phase) {
192
+ if ((options.swipeEnabled ?? DRAWER_DEFAULT_SWIPE_ENABLED) === false)
193
+ return false;
194
+ if (!isDrawerAnimated(options))
195
+ return false;
196
+ const startX = startPageXOf(event) ?? gestureState.x0;
197
+ if (!isSwipeStartInEdge(startX, screenWidth, isOpen, options))
198
+ return false;
199
+ return phase === 'start' ? true : isHorizontalDrag(gestureState);
200
+ }
@@ -0,0 +1,21 @@
1
+ import type { IRoute } from '../navigator-state';
2
+ export type { IRoute } from '../navigator-state';
3
+ type IRouteEntry = IRoute<unknown>;
4
+ export type IDrawerRouterState = Readonly<{
5
+ routes: readonly IRouteEntry[];
6
+ index: number;
7
+ isOpen: boolean;
8
+ }>;
9
+ export type IDrawerRouterAction = {
10
+ type: 'jumpTo';
11
+ name: string;
12
+ } | {
13
+ type: 'openDrawer';
14
+ } | {
15
+ type: 'closeDrawer';
16
+ } | {
17
+ type: 'toggleDrawer';
18
+ };
19
+ export declare function createInitialDrawerRouterState(routes: readonly IRouteEntry[], initialRouteName?: string): IDrawerRouterState;
20
+ export declare function drawerRouterReducer(state: IDrawerRouterState, action: IDrawerRouterAction): IDrawerRouterState;
21
+ export declare function focusedDrawerRoute(state: IDrawerRouterState): IRouteEntry | undefined;