@symbiote-native/navigation 2.1.0 → 2.2.0

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 (234) hide show
  1. package/README.md +42 -12
  2. package/build/angular/drawer/index.js +23 -9
  3. package/build/angular/index.d.ts +4 -4
  4. package/build/angular/injectors/inject-navigation.js +1 -1
  5. package/build/angular/navigation-scope.directive.js +1 -1
  6. package/build/angular/search-bar-ref.directive.js +1 -1
  7. package/build/angular/stack/index.js +20 -6
  8. package/build/angular/tabs/index.js +21 -7
  9. package/build/core/drawer-options/index.js +19 -5
  10. package/build/core/drawer-router-state/index.js +8 -2
  11. package/build/core/guards.js +3 -1
  12. package/build/core/header-bar-buttons.js +14 -4
  13. package/build/core/index.d.ts +4 -4
  14. package/build/core/index.js +2 -2
  15. package/build/core/navigator-state/index.js +12 -4
  16. package/build/core/render-drawer.js +4 -1
  17. package/build/core/render-stack.js +13 -4
  18. package/build/core/screen-options.js +1 -1
  19. package/build/core/search-bar-commands.js +1 -1
  20. package/build/core/state-persistence/index.js +6 -2
  21. package/build/core/tab-router-state/index.js +8 -2
  22. package/build/react/drawer/index.js +36 -13
  23. package/build/react/index.d.ts +7 -7
  24. package/build/react/index.js +2 -2
  25. package/build/react/stack/index.js +34 -8
  26. package/build/react/tabs/index.js +14 -4
  27. package/build/solid/drawer/index.d.ts +22 -0
  28. package/build/solid/drawer/index.js +394 -0
  29. package/build/solid/drawer-screen-props.d.ts +13 -0
  30. package/build/solid/drawer-screen-props.js +2 -0
  31. package/build/solid/host.d.ts +2 -0
  32. package/build/solid/host.js +23 -0
  33. package/build/solid/index.d.ts +19 -0
  34. package/build/solid/index.js +24 -0
  35. package/build/solid/linking.d.ts +3 -0
  36. package/build/solid/linking.js +55 -0
  37. package/build/solid/navigation-context.d.ts +13 -0
  38. package/build/solid/navigation-context.js +37 -0
  39. package/build/solid/primitives/create-focus-effect.d.ts +1 -0
  40. package/build/solid/primitives/create-focus-effect.js +30 -0
  41. package/build/solid/primitives/create-is-focused.d.ts +2 -0
  42. package/build/solid/primitives/create-is-focused.js +31 -0
  43. package/build/solid/primitives/create-navigation-state.d.ts +3 -0
  44. package/build/solid/primitives/create-navigation-state.js +30 -0
  45. package/build/solid/primitives/index.d.ts +12 -0
  46. package/build/solid/primitives/index.js +8 -0
  47. package/build/solid/primitives/use-drawer-navigation.d.ts +5 -0
  48. package/build/solid/primitives/use-drawer-navigation.js +15 -0
  49. package/build/solid/primitives/use-navigation.d.ts +8 -0
  50. package/build/solid/primitives/use-navigation.js +23 -0
  51. package/build/solid/primitives/use-route.d.ts +3 -0
  52. package/build/solid/primitives/use-route.js +14 -0
  53. package/build/solid/primitives/use-stack-navigation.d.ts +5 -0
  54. package/build/solid/primitives/use-stack-navigation.js +19 -0
  55. package/build/solid/primitives/use-tab-navigation.d.ts +5 -0
  56. package/build/solid/primitives/use-tab-navigation.js +15 -0
  57. package/build/solid/screen-props.d.ts +19 -0
  58. package/build/solid/screen-props.js +9 -0
  59. package/build/solid/screen-registry.d.ts +31 -0
  60. package/build/solid/screen-registry.js +86 -0
  61. package/build/solid/screen.d.ts +6 -0
  62. package/build/solid/screen.js +69 -0
  63. package/build/solid/stack/index.d.ts +15 -0
  64. package/build/solid/stack/index.js +348 -0
  65. package/build/solid/tab-screen-props.d.ts +13 -0
  66. package/build/solid/tab-screen-props.js +3 -0
  67. package/build/solid/tabs/index.d.ts +14 -0
  68. package/build/solid/tabs/index.js +228 -0
  69. package/build/svelte/descriptor-subtree.js +3 -1
  70. package/build/svelte/drawer/index.svelte +163 -25
  71. package/build/svelte/drawer-screen.svelte +2 -1
  72. package/build/svelte/index.d.ts +3 -3
  73. package/build/svelte/navigation-scope.svelte +4 -1
  74. package/build/svelte/runes/use-navigation.svelte.js +5 -1
  75. package/build/svelte/stack/index.svelte +43 -6
  76. package/build/svelte/stack/stack-screen.svelte +66 -9
  77. package/build/svelte/tabs/index.svelte +42 -20
  78. package/build/vue/composables/use-navigation.js +5 -1
  79. package/build/vue/drawer/index.js +30 -8
  80. package/build/vue/index.d.ts +3 -3
  81. package/build/vue/navigation-context.js +1 -1
  82. package/build/vue/stack/index.js +17 -6
  83. package/build/vue/tabs/index.js +18 -5
  84. package/build-ngc/angular/drawer/index.js +31 -11
  85. package/build-ngc/angular/drawer/index.js.map +1 -1
  86. package/build-ngc/angular/index.d.ts +4 -4
  87. package/build-ngc/angular/index.js.map +1 -1
  88. package/build-ngc/angular/injectors/inject-focus-effect.js.map +1 -1
  89. package/build-ngc/angular/injectors/inject-is-focused.js.map +1 -1
  90. package/build-ngc/angular/injectors/inject-navigation-state.js.map +1 -1
  91. package/build-ngc/angular/injectors/inject-navigation.js +1 -1
  92. package/build-ngc/angular/injectors/inject-navigation.js.map +1 -1
  93. package/build-ngc/angular/linking.js.map +1 -1
  94. package/build-ngc/angular/navigation-context.service.js.map +1 -1
  95. package/build-ngc/angular/navigation-scope.directive.js +1 -1
  96. package/build-ngc/angular/navigation-scope.directive.js.map +1 -1
  97. package/build-ngc/angular/screen.directive.js.map +1 -1
  98. package/build-ngc/angular/search-bar-ref.directive.js +1 -1
  99. package/build-ngc/angular/search-bar-ref.directive.js.map +1 -1
  100. package/build-ngc/angular/stack/index.js +26 -8
  101. package/build-ngc/angular/stack/index.js.map +1 -1
  102. package/build-ngc/angular/tab-screen.directive.js.map +1 -1
  103. package/build-ngc/angular/tabs/index.js +21 -7
  104. package/build-ngc/angular/tabs/index.js.map +1 -1
  105. package/build-ngc/core/constants.js.map +1 -1
  106. package/build-ngc/core/drawer-options/index.js +19 -5
  107. package/build-ngc/core/drawer-options/index.js.map +1 -1
  108. package/build-ngc/core/drawer-router-state/index.js +8 -2
  109. package/build-ngc/core/drawer-router-state/index.js.map +1 -1
  110. package/build-ngc/core/guards.js +3 -1
  111. package/build-ngc/core/guards.js.map +1 -1
  112. package/build-ngc/core/header-bar-buttons.js +14 -4
  113. package/build-ngc/core/header-bar-buttons.js.map +1 -1
  114. package/build-ngc/core/index.d.ts +4 -4
  115. package/build-ngc/core/index.js +2 -2
  116. package/build-ngc/core/index.js.map +1 -1
  117. package/build-ngc/core/linking-config/index.js.map +1 -1
  118. package/build-ngc/core/navigation-events/index.js.map +1 -1
  119. package/build-ngc/core/navigator-handles/index.js.map +1 -1
  120. package/build-ngc/core/navigator-state/index.js +12 -4
  121. package/build-ngc/core/navigator-state/index.js.map +1 -1
  122. package/build-ngc/core/render-drawer.js +4 -1
  123. package/build-ngc/core/render-drawer.js.map +1 -1
  124. package/build-ngc/core/render-stack.js +13 -4
  125. package/build-ngc/core/render-stack.js.map +1 -1
  126. package/build-ngc/core/render-tabs.js.map +1 -1
  127. package/build-ngc/core/screen-options.js +1 -1
  128. package/build-ngc/core/screen-options.js.map +1 -1
  129. package/build-ngc/core/search-bar-commands.js +1 -1
  130. package/build-ngc/core/search-bar-commands.js.map +1 -1
  131. package/build-ngc/core/stack-router-state/index.js.map +1 -1
  132. package/build-ngc/core/state-persistence/index.js +6 -2
  133. package/build-ngc/core/state-persistence/index.js.map +1 -1
  134. package/build-ngc/core/tab-router-state/index.js +8 -2
  135. package/build-ngc/core/tab-router-state/index.js.map +1 -1
  136. package/package.json +23 -9
  137. package/src/angular/drawer/index.ts +69 -25
  138. package/src/angular/index.ts +20 -4
  139. package/src/angular/injectors/inject-focus-effect.ts +8 -2
  140. package/src/angular/injectors/inject-is-focused.ts +6 -4
  141. package/src/angular/injectors/inject-navigation-state.ts +7 -4
  142. package/src/angular/injectors/inject-navigation.ts +12 -3
  143. package/src/angular/linking.ts +4 -1
  144. package/src/angular/navigation-context.service.ts +5 -2
  145. package/src/angular/navigation-scope.directive.ts +6 -2
  146. package/src/angular/screen.directive.ts +13 -3
  147. package/src/angular/search-bar-ref.directive.ts +10 -2
  148. package/src/angular/stack/index.ts +68 -22
  149. package/src/angular/tab-screen.directive.ts +3 -1
  150. package/src/angular/tabs/index.ts +36 -12
  151. package/src/core/constants.ts +6 -3
  152. package/src/core/drawer-options/index.ts +40 -13
  153. package/src/core/drawer-router-state/index.ts +11 -3
  154. package/src/core/guards.ts +3 -1
  155. package/src/core/header-bar-buttons.ts +41 -12
  156. package/src/core/index.ts +18 -4
  157. package/src/core/linking-config/index.ts +31 -10
  158. package/src/core/navigation-events/index.ts +8 -2
  159. package/src/core/navigator-handles/index.ts +8 -3
  160. package/src/core/navigator-props.ts +13 -4
  161. package/src/core/navigator-state/index.ts +24 -7
  162. package/src/core/render-drawer.ts +24 -9
  163. package/src/core/render-stack.ts +52 -13
  164. package/src/core/render-tabs.ts +24 -6
  165. package/src/core/screen-options.ts +15 -5
  166. package/src/core/search-bar-commands.ts +12 -3
  167. package/src/core/stack-router-state/index.ts +5 -2
  168. package/src/core/state-persistence/index.ts +13 -3
  169. package/src/core/tab-router-state/index.ts +10 -3
  170. package/src/react/drawer/index.ts +408 -344
  171. package/src/react/drawer-screen.ts +8 -2
  172. package/src/react/hooks/use-focus-effect.ts +8 -2
  173. package/src/react/hooks/use-is-focused.ts +6 -2
  174. package/src/react/hooks/use-navigation.ts +8 -2
  175. package/src/react/index.ts +37 -7
  176. package/src/react/linking.ts +4 -1
  177. package/src/react/navigation-context.ts +6 -2
  178. package/src/react/screen.ts +12 -3
  179. package/src/react/stack/index.ts +294 -231
  180. package/src/react/tab-screen.ts +3 -1
  181. package/src/react/tabs/index.ts +188 -158
  182. package/src/solid/drawer/index.ts +570 -0
  183. package/src/solid/drawer-screen-props.ts +24 -0
  184. package/src/solid/host.ts +29 -0
  185. package/src/solid/index.ts +87 -0
  186. package/src/solid/linking.ts +74 -0
  187. package/src/solid/navigation-context.ts +56 -0
  188. package/src/solid/primitives/create-focus-effect.ts +41 -0
  189. package/src/solid/primitives/create-is-focused.ts +41 -0
  190. package/src/solid/primitives/create-navigation-state.ts +44 -0
  191. package/src/solid/primitives/index.ts +12 -0
  192. package/src/solid/primitives/use-drawer-navigation.ts +25 -0
  193. package/src/solid/primitives/use-navigation.ts +44 -0
  194. package/src/solid/primitives/use-route.ts +18 -0
  195. package/src/solid/primitives/use-stack-navigation.ts +29 -0
  196. package/src/solid/primitives/use-tab-navigation.ts +25 -0
  197. package/src/solid/screen-props.ts +51 -0
  198. package/src/solid/screen-registry.ts +159 -0
  199. package/src/solid/screen.ts +80 -0
  200. package/src/solid/stack/index.ts +528 -0
  201. package/src/solid/tab-screen-props.ts +21 -0
  202. package/src/solid/tabs/index.ts +327 -0
  203. package/src/svelte/attachments.ts +3 -1
  204. package/src/svelte/descriptor-subtree.ts +3 -1
  205. package/src/svelte/drawer/index.svelte +163 -25
  206. package/src/svelte/drawer-screen-props.ts +8 -2
  207. package/src/svelte/drawer-screen.svelte +2 -1
  208. package/src/svelte/fabric-tree.test-helper.ts +37 -3
  209. package/src/svelte/index.ts +15 -3
  210. package/src/svelte/linking.svelte.ts +11 -5
  211. package/src/svelte/navigation-scope.svelte +4 -1
  212. package/src/svelte/runes/use-drawer-navigation.svelte.ts +3 -1
  213. package/src/svelte/runes/use-focus-effect.svelte.ts +8 -2
  214. package/src/svelte/runes/use-navigation-state.svelte.ts +3 -1
  215. package/src/svelte/runes/use-navigation.svelte.ts +13 -3
  216. package/src/svelte/runes/use-stack-navigation.svelte.ts +3 -1
  217. package/src/svelte/screen-props.ts +7 -2
  218. package/src/svelte/screen-registry.ts +9 -3
  219. package/src/svelte/stack/index.svelte +43 -6
  220. package/src/svelte/stack/stack-screen.svelte +66 -9
  221. package/src/svelte/svelte-compile.test-helper.ts +16 -12
  222. package/src/svelte/tab-screen-props.ts +3 -1
  223. package/src/svelte/tabs/index.svelte +42 -20
  224. package/src/vue/composables/use-navigation-state.ts +7 -4
  225. package/src/vue/composables/use-navigation.ts +13 -3
  226. package/src/vue/drawer/index.ts +122 -36
  227. package/src/vue/drawer-screen.ts +12 -5
  228. package/src/vue/index.ts +15 -3
  229. package/src/vue/linking.ts +7 -2
  230. package/src/vue/navigation-context.ts +17 -5
  231. package/src/vue/screen.ts +7 -2
  232. package/src/vue/stack/index.ts +82 -34
  233. package/src/vue/tab-screen.ts +3 -1
  234. package/src/vue/tabs/index.ts +51 -13
@@ -29,10 +29,21 @@ import {
29
29
  useRef,
30
30
  } from 'react';
31
31
  import type { ReactElement, ReactNode } from 'react';
32
- import { Animated, PanResponder, useWindowDimensions } from '@symbiote-native/react';
33
- import type { IPanResponderGestureState, ISymbioteEvent } from '@symbiote-native/react';
32
+ import {
33
+ Animated,
34
+ PanResponder,
35
+ useWindowDimensions,
36
+ } from '@symbiote-native/react';
37
+ import type {
38
+ IPanResponderGestureState,
39
+ ISymbioteEvent,
40
+ } from '@symbiote-native/react';
34
41
  import type { IDescriptor } from '@symbiote-native/components';
35
- import { dlog, type IStyleProp, type IViewStyle } from '@symbiote-native/engine';
42
+ import {
43
+ dlog,
44
+ type IStyleProp,
45
+ type IViewStyle,
46
+ } from '@symbiote-native/engine';
36
47
  import {
37
48
  DRAWER_DEFAULT_OVERLAY_COLOR,
38
49
  NAVIGATION_EVENT_BLUR,
@@ -62,7 +73,10 @@ import type {
62
73
  import { collectRegistry } from '../collect-registry';
63
74
  import { NavigationContext } from '../navigation-context';
64
75
  import { DrawerScreen } from '../drawer-screen';
65
- import type { IDrawerScreenOptionsArgs, IDrawerScreenProps } from '../drawer-screen';
76
+ import type {
77
+ IDrawerScreenOptionsArgs,
78
+ IDrawerScreenProps,
79
+ } from '../drawer-screen';
66
80
 
67
81
  export type { IDrawerNavigatorHandle, IDrawerDescriptorMap } from '../../core';
68
82
 
@@ -80,7 +94,9 @@ export type IDrawerProps = IDrawerOptions & {
80
94
 
81
95
  type IDrawerRegistryEntry = Omit<IDrawerScreenProps, 'name'>;
82
96
 
83
- function isDrawerScreenElement(child: ReactNode): child is ReactElement<IDrawerScreenProps> {
97
+ function isDrawerScreenElement(
98
+ child: ReactNode,
99
+ ): child is ReactElement<IDrawerScreenProps> {
84
100
  return isValidElement(child) && child.type === DrawerScreen;
85
101
  }
86
102
 
@@ -89,363 +105,411 @@ function resolveDrawerScreenOptions(
89
105
  optionsArgs: IDrawerScreenOptionsArgs,
90
106
  screenOptions: IDrawerScreenOptions | undefined,
91
107
  ): IDrawerScreenOptions {
92
- const own = typeof entry.options === 'function' ? entry.options(optionsArgs) : entry.options;
108
+ const own =
109
+ typeof entry.options === 'function'
110
+ ? entry.options(optionsArgs)
111
+ : entry.options;
93
112
  return { ...screenOptions, ...own };
94
113
  }
95
114
 
96
115
  const DRAWER_SNAP_DURATION = 250;
97
116
 
98
- const DrawerImpl = forwardRef<IDrawerNavigatorHandle, IDrawerProps>((props, forwardedRef) => {
99
- // Read BEFORE establishing this Drawer's own Context value below - becomes the `parent` link a
100
- // nested screen's useNavigation().getParent() walks (e.g. this Drawer rendered as a Stack
101
- // screen's content reaches that Stack via this value). undefined when this Drawer is the
102
- // nesting root.
103
- const ambientContext = useContext(NavigationContext);
104
- const {
105
- initialRouteName,
106
- screenOptions,
107
- renderDrawerContent,
108
- children,
109
- drawerStyle,
110
- drawerType,
111
- drawerPosition,
112
- drawerWidth,
113
- overlayColor,
114
- swipeEnabled,
115
- swipeEdgeWidth,
116
- swipeMinDistance,
117
- swipeMinVelocity,
118
- } = props;
119
-
120
- const options: IDrawerOptions = {
121
- drawerType,
122
- drawerPosition,
123
- drawerWidth,
124
- overlayColor,
125
- swipeEnabled,
126
- swipeEdgeWidth,
127
- swipeMinDistance,
128
- swipeMinVelocity,
129
- };
130
-
131
- const registry = useMemo(() => collectRegistry(children, isDrawerScreenElement), [children]);
132
- const routeIdPrefix = useId();
133
-
134
- const routes = useMemo<IRoute<unknown>[]>(
135
- () =>
136
- Array.from(registry.entries()).map(([name, entry]) => ({
137
- key: `${routeIdPrefix}-${name}`,
138
- name,
139
- params: entry.initialParams,
140
- })),
141
- [registry, routeIdPrefix],
142
- );
143
-
144
- const [state, dispatch] = useReducer(drawerRouterReducer, undefined, () =>
145
- createInitialDrawerRouterState(routes, initialRouteName),
146
- );
147
-
148
- if (routes.length === 0) dlog('Drawer: no <Drawer.Screen> children registered');
149
-
150
- const { width: screenWidth } = useWindowDimensions();
151
-
152
- // progress: 0 closed -> 1 open, the single Animated.Value every slide/opacity transform below
153
- // interpolates from.
154
- const progress = useRef(new Animated.Value(state.isOpen ? 1 : 0)).current;
155
- // Where a drag STARTS from, in progress units. Always exactly 0 or 1: a gesture only ever
156
- // begins at rest, since terminate/release always snap the value back to a resting state before
157
- // another grant can fire.
158
- const dragStartProgress = useRef(0);
159
-
160
- // Refs so the PanResponder's callbacks - built ONCE below, RN's own idiom - always read the
161
- // CURRENT render's values without forcing a new PanResponder identity on every state/prop
162
- // change (recreating panHandlers mid-gesture would drop the in-flight touch).
163
- const optionsRef = useRef(options);
164
- optionsRef.current = options;
165
- const screenWidthRef = useRef(screenWidth);
166
- screenWidthRef.current = screenWidth;
167
- // The whole router state, not just its isOpen bit: jumpTo below has to re-run the reducer to
168
- // learn what the dispatch actually produced, and that needs the routes too.
169
- const stateRef = useRef(state);
170
- stateRef.current = state;
171
-
172
- const animateProgressTo = useCallback(
173
- (open: boolean): void => {
174
- // Investigation instrumentation (Drawer openDrawer-no-op / toggleDrawer-no-animation bug):
175
- // every imperative caller (openDrawer/closeDrawer/toggleDrawer/jumpTo) funnels through here,
176
- // so this single seam proves whether Animated.timing is actually being started at all, and
177
- // with what toValue - a live-only failure (e.g. a stale progress ref, or Animated.timing
178
- // silently short-circuiting) would show up as this log firing with no visible motion. Kept
179
- // behind DEBUG per <keep_logs_gate_behind_DEBUG>, never removed.
180
- dlog(`Drawer: animateProgressTo(open=${open}) starting at t=${Date.now()}`);
181
- Animated.timing(progress, {
182
- toValue: open ? 1 : 0,
183
- duration: DRAWER_SNAP_DURATION,
184
- // Native-driver wiring (the AnimatedComponent passthrough opt-in ADR 0017 defines) is
185
- // deferred for v1 - see this file's header feasibility note. The JS timing loop still
186
- // drives every frame, same as any other non-native-driven Animated.timing in this codebase.
187
- useNativeDriver: false,
188
- }).start();
189
- },
190
- [progress],
191
- );
192
-
193
- const handle = useMemo<IDrawerNavigatorHandle>(
194
- () => ({
195
- openDrawer: () => {
196
- dlog(`Drawer: openDrawer() called, isOpen=${stateRef.current.isOpen} at t=${Date.now()}`);
197
- animateProgressTo(true);
198
- dispatch({ type: 'openDrawer' });
199
- },
200
- closeDrawer: () => {
201
- dlog(`Drawer: closeDrawer() called, isOpen=${stateRef.current.isOpen} at t=${Date.now()}`);
202
- animateProgressTo(false);
203
- dispatch({ type: 'closeDrawer' });
204
- },
205
- toggleDrawer: () => {
206
- dlog(`Drawer: toggleDrawer() called, isOpen=${stateRef.current.isOpen} at t=${Date.now()}`);
207
- animateProgressTo(!stateRef.current.isOpen);
208
- dispatch({ type: 'toggleDrawer' });
209
- },
210
- jumpTo: (name: string) => {
211
- // An unregistered name is a documented reducer no-op that hands the SAME state back, so
212
- // animating off `isOpen` alone would slide the panel shut while the router still says
213
- // open. useReducer's dispatch is async - stateRef only catches up on the next render - so
214
- // the reducer (a pure function) is re-run here to learn what the dispatch produces, and
215
- // the animation runs only on a genuine open -> closed transition.
216
- const action: IDrawerRouterAction = { type: 'jumpTo', name };
217
- const current = stateRef.current;
218
- const next = drawerRouterReducer(current, action);
219
- dispatch(action);
220
- if (current.isOpen && !next.isOpen) animateProgressTo(false);
221
- },
222
- }),
223
- [animateProgressTo],
224
- );
225
-
226
- useImperativeHandle(forwardedRef, () => handle, [handle]);
227
-
228
- const panResponder = useRef(
229
- PanResponder.create({
230
- onStartShouldSetPanResponder: (
231
- event: ISymbioteEvent,
232
- gestureState: IPanResponderGestureState,
233
- ): boolean =>
234
- shouldClaimDrawerSwipe(
235
- event,
236
- gestureState,
237
- screenWidthRef.current,
238
- stateRef.current.isOpen,
239
- optionsRef.current,
240
- 'start',
241
- ),
242
- onMoveShouldSetPanResponder: (
243
- event: ISymbioteEvent,
244
- gestureState: IPanResponderGestureState,
245
- ): boolean =>
246
- shouldClaimDrawerSwipe(
247
- event,
248
- gestureState,
249
- screenWidthRef.current,
250
- stateRef.current.isOpen,
251
- optionsRef.current,
252
- 'move',
253
- ),
254
- onPanResponderGrant: (): void => {
255
- dlog('Drawer: gesture grant');
256
- dragStartProgress.current = stateRef.current.isOpen ? 1 : 0;
257
- },
258
- onPanResponderMove: (
259
- _event: ISymbioteEvent,
260
- gestureState: IPanResponderGestureState,
261
- ): void => {
262
- const nextProgress = resolveDragProgress(
263
- gestureState,
264
- dragStartProgress.current,
265
- optionsRef.current,
266
- );
267
- progress.setValue(nextProgress);
268
- },
269
- onPanResponderRelease: (
270
- _event: ISymbioteEvent,
271
- gestureState: IPanResponderGestureState,
272
- ): void => {
273
- const intent = resolveSwipeIntent(
274
- gestureState,
275
- stateRef.current.isOpen,
276
- optionsRef.current,
117
+ const DrawerImpl = forwardRef<IDrawerNavigatorHandle, IDrawerProps>(
118
+ (props, forwardedRef) => {
119
+ // Read BEFORE establishing this Drawer's own Context value below - becomes the `parent` link a
120
+ // nested screen's useNavigation().getParent() walks (e.g. this Drawer rendered as a Stack
121
+ // screen's content reaches that Stack via this value). undefined when this Drawer is the
122
+ // nesting root.
123
+ const ambientContext = useContext(NavigationContext);
124
+ const {
125
+ initialRouteName,
126
+ screenOptions,
127
+ renderDrawerContent,
128
+ children,
129
+ drawerStyle,
130
+ drawerType,
131
+ drawerPosition,
132
+ drawerWidth,
133
+ overlayColor,
134
+ swipeEnabled,
135
+ swipeEdgeWidth,
136
+ swipeMinDistance,
137
+ swipeMinVelocity,
138
+ } = props;
139
+
140
+ const options: IDrawerOptions = {
141
+ drawerType,
142
+ drawerPosition,
143
+ drawerWidth,
144
+ overlayColor,
145
+ swipeEnabled,
146
+ swipeEdgeWidth,
147
+ swipeMinDistance,
148
+ swipeMinVelocity,
149
+ };
150
+
151
+ const registry = useMemo(
152
+ () => collectRegistry(children, isDrawerScreenElement),
153
+ [children],
154
+ );
155
+ const routeIdPrefix = useId();
156
+
157
+ const routes = useMemo<IRoute<unknown>[]>(
158
+ () =>
159
+ Array.from(registry.entries()).map(([name, entry]) => ({
160
+ key: `${routeIdPrefix}-${name}`,
161
+ name,
162
+ params: entry.initialParams,
163
+ })),
164
+ [registry, routeIdPrefix],
165
+ );
166
+
167
+ const [state, dispatch] = useReducer(drawerRouterReducer, undefined, () =>
168
+ createInitialDrawerRouterState(routes, initialRouteName),
169
+ );
170
+
171
+ if (routes.length === 0)
172
+ dlog('Drawer: no <Drawer.Screen> children registered');
173
+
174
+ const { width: screenWidth } = useWindowDimensions();
175
+
176
+ // progress: 0 closed -> 1 open, the single Animated.Value every slide/opacity transform below
177
+ // interpolates from.
178
+ const progress = useRef(new Animated.Value(state.isOpen ? 1 : 0)).current;
179
+ // Where a drag STARTS from, in progress units. Always exactly 0 or 1: a gesture only ever
180
+ // begins at rest, since terminate/release always snap the value back to a resting state before
181
+ // another grant can fire.
182
+ const dragStartProgress = useRef(0);
183
+
184
+ // Refs so the PanResponder's callbacks - built ONCE below, RN's own idiom - always read the
185
+ // CURRENT render's values without forcing a new PanResponder identity on every state/prop
186
+ // change (recreating panHandlers mid-gesture would drop the in-flight touch).
187
+ const optionsRef = useRef(options);
188
+ optionsRef.current = options;
189
+ const screenWidthRef = useRef(screenWidth);
190
+ screenWidthRef.current = screenWidth;
191
+ // The whole router state, not just its isOpen bit: jumpTo below has to re-run the reducer to
192
+ // learn what the dispatch actually produced, and that needs the routes too.
193
+ const stateRef = useRef(state);
194
+ stateRef.current = state;
195
+
196
+ const animateProgressTo = useCallback(
197
+ (open: boolean): void => {
198
+ // Investigation instrumentation (Drawer openDrawer-no-op / toggleDrawer-no-animation bug):
199
+ // every imperative caller (openDrawer/closeDrawer/toggleDrawer/jumpTo) funnels through here,
200
+ // so this single seam proves whether Animated.timing is actually being started at all, and
201
+ // with what toValue - a live-only failure (e.g. a stale progress ref, or Animated.timing
202
+ // silently short-circuiting) would show up as this log firing with no visible motion. Kept
203
+ // behind DEBUG per <keep_logs_gate_behind_DEBUG>, never removed.
204
+ dlog(
205
+ `Drawer: animateProgressTo(open=${open}) starting at t=${Date.now()}`,
277
206
  );
278
- const open = intent === 'open';
279
- dlog(`Drawer: gesture release -> ${open ? 'open' : 'close'}`);
280
- animateProgressTo(open);
281
- dispatch(open ? { type: 'openDrawer' } : { type: 'closeDrawer' });
207
+ Animated.timing(progress, {
208
+ toValue: open ? 1 : 0,
209
+ duration: DRAWER_SNAP_DURATION,
210
+ // Native-driver wiring (the AnimatedComponent passthrough opt-in ADR 0017 defines) is
211
+ // deferred for v1 - see this file's header feasibility note. The JS timing loop still
212
+ // drives every frame, same as any other non-native-driven Animated.timing in this codebase.
213
+ useNativeDriver: false,
214
+ }).start();
282
215
  },
283
- onPanResponderTerminate: (): void => {
284
- dlog('Drawer: gesture terminated, snapping back');
285
- animateProgressTo(stateRef.current.isOpen);
216
+ [progress],
217
+ );
218
+
219
+ const handle = useMemo<IDrawerNavigatorHandle>(
220
+ () => ({
221
+ openDrawer: () => {
222
+ dlog(
223
+ `Drawer: openDrawer() called, isOpen=${stateRef.current.isOpen} at t=${Date.now()}`,
224
+ );
225
+ animateProgressTo(true);
226
+ dispatch({ type: 'openDrawer' });
227
+ },
228
+ closeDrawer: () => {
229
+ dlog(
230
+ `Drawer: closeDrawer() called, isOpen=${stateRef.current.isOpen} at t=${Date.now()}`,
231
+ );
232
+ animateProgressTo(false);
233
+ dispatch({ type: 'closeDrawer' });
234
+ },
235
+ toggleDrawer: () => {
236
+ dlog(
237
+ `Drawer: toggleDrawer() called, isOpen=${stateRef.current.isOpen} at t=${Date.now()}`,
238
+ );
239
+ animateProgressTo(!stateRef.current.isOpen);
240
+ dispatch({ type: 'toggleDrawer' });
241
+ },
242
+ jumpTo: (name: string) => {
243
+ // An unregistered name is a documented reducer no-op that hands the SAME state back, so
244
+ // animating off `isOpen` alone would slide the panel shut while the router still says
245
+ // open. useReducer's dispatch is async - stateRef only catches up on the next render - so
246
+ // the reducer (a pure function) is re-run here to learn what the dispatch produces, and
247
+ // the animation runs only on a genuine open -> closed transition.
248
+ const action: IDrawerRouterAction = { type: 'jumpTo', name };
249
+ const current = stateRef.current;
250
+ const next = drawerRouterReducer(current, action);
251
+ dispatch(action);
252
+ if (current.isOpen && !next.isOpen) animateProgressTo(false);
253
+ },
254
+ }),
255
+ [animateProgressTo],
256
+ );
257
+
258
+ useImperativeHandle(forwardedRef, () => handle, [handle]);
259
+
260
+ const panResponder = useRef(
261
+ PanResponder.create({
262
+ onStartShouldSetPanResponder: (
263
+ event: ISymbioteEvent,
264
+ gestureState: IPanResponderGestureState,
265
+ ): boolean =>
266
+ shouldClaimDrawerSwipe(
267
+ event,
268
+ gestureState,
269
+ screenWidthRef.current,
270
+ stateRef.current.isOpen,
271
+ optionsRef.current,
272
+ 'start',
273
+ ),
274
+ onMoveShouldSetPanResponder: (
275
+ event: ISymbioteEvent,
276
+ gestureState: IPanResponderGestureState,
277
+ ): boolean =>
278
+ shouldClaimDrawerSwipe(
279
+ event,
280
+ gestureState,
281
+ screenWidthRef.current,
282
+ stateRef.current.isOpen,
283
+ optionsRef.current,
284
+ 'move',
285
+ ),
286
+ onPanResponderGrant: (): void => {
287
+ dlog('Drawer: gesture grant');
288
+ dragStartProgress.current = stateRef.current.isOpen ? 1 : 0;
289
+ },
290
+ onPanResponderMove: (
291
+ _event: ISymbioteEvent,
292
+ gestureState: IPanResponderGestureState,
293
+ ): void => {
294
+ const nextProgress = resolveDragProgress(
295
+ gestureState,
296
+ dragStartProgress.current,
297
+ optionsRef.current,
298
+ );
299
+ progress.setValue(nextProgress);
300
+ },
301
+ onPanResponderRelease: (
302
+ _event: ISymbioteEvent,
303
+ gestureState: IPanResponderGestureState,
304
+ ): void => {
305
+ const intent = resolveSwipeIntent(
306
+ gestureState,
307
+ stateRef.current.isOpen,
308
+ optionsRef.current,
309
+ );
310
+ const open = intent === 'open';
311
+ dlog(`Drawer: gesture release -> ${open ? 'open' : 'close'}`);
312
+ animateProgressTo(open);
313
+ dispatch(open ? { type: 'openDrawer' } : { type: 'closeDrawer' });
314
+ },
315
+ onPanResponderTerminate: (): void => {
316
+ dlog('Drawer: gesture terminated, snapping back');
317
+ animateProgressTo(stateRef.current.isOpen);
318
+ },
319
+ }),
320
+ ).current;
321
+
322
+ const animated = isDrawerAnimated(options);
323
+ const geometry = useMemo(
324
+ () => resolveDrawerGeometry(options),
325
+ [options.drawerType, options.drawerPosition, options.drawerWidth],
326
+ );
327
+
328
+ const panelSlot = animated
329
+ ? resolveDrawerSlotInterpolation(geometry, 'panel')
330
+ : undefined;
331
+ const contentSlot = animated
332
+ ? resolveDrawerSlotInterpolation(geometry, 'content')
333
+ : undefined;
334
+ const overlaySlot = animated
335
+ ? resolveDrawerSlotInterpolation(geometry, 'overlay')
336
+ : undefined;
337
+
338
+ const panelTranslateX = panelSlot
339
+ ? progress.interpolate(panelSlot.translateX)
340
+ : undefined;
341
+ const contentTranslateX = contentSlot
342
+ ? progress.interpolate(contentSlot.translateX)
343
+ : undefined;
344
+ const overlayOpacity = overlaySlot
345
+ ? progress.interpolate(overlaySlot.opacity)
346
+ : undefined;
347
+ // The overlay is a full-screen absolutely-positioned sibling BELOW content in paint order
348
+ // (see render-drawer.ts's drawerChildOrder) - for 'front' that's fine since content never moves,
349
+ // but for 'slide' content itself translates away by contentTranslateX, and without following it
350
+ // the overlay stays pinned full-screen, dimming (and touch-capturing) the now-revealed panel
351
+ // underneath instead of just the content sliver it's meant to dim. Tying overlay to the SAME
352
+ // translateX as content keeps it registered exactly under content, wherever content actually is
353
+ // (resolveDrawerSlotInterpolation's 'overlay' overload returns that same content delta).
354
+ const overlayTranslateX = overlaySlot
355
+ ? progress.interpolate(overlaySlot.translateX)
356
+ : undefined;
357
+
358
+ const focusedRoute = state.routes[state.index];
359
+ const focusedEntry = focusedRoute
360
+ ? registry.get(focusedRoute.name)
361
+ : undefined;
362
+ if (focusedRoute && !focusedEntry) {
363
+ dlog(
364
+ `Drawer: no screen registered for route name "${focusedRoute.name}"`,
365
+ );
366
+ }
367
+
368
+ // Only the focused route's screen is ever mounted (like Tab, unlike Stack which keeps every
369
+ // pushed route alive), so a fresh emitter per focus change is sufficient - see tabs.ts's
370
+ // matching comment for why no per-route emitter map is needed. Keyed on the route KEY, not the
371
+ // route object, so a jumpTo-with-no-params re-focus of the ALREADY-focused route (a no-op in
372
+ // drawerRouterReducer) and any future params merge don't spuriously re-fire focus/blur.
373
+ const focusedRouteKey = focusedRoute?.key;
374
+ const routeEmitter = useMemo(
375
+ () => createNavigationEmitter(),
376
+ [focusedRouteKey],
377
+ );
378
+
379
+ // Drawer paints its own panel in pure JS - there is no native onAppear/onDisappear to hook
380
+ // (unlike Stack's RNSScreen), so focus/blur is synthesized here: mount = focus, cleanup = blur,
381
+ // exactly what an effect keyed on focusedRouteKey already encodes - no diffFocusedRoute
382
+ // indirection needed (unlike Vue/Angular, which diff real prev/next keys inside an imperative
383
+ // watch/CD callback that has no mount/cleanup pairing of its own).
384
+ useEffect(() => {
385
+ if (focusedRouteKey === undefined) return undefined;
386
+ dlog(`Drawer: route "${focusedRoute?.name}" focused`);
387
+ routeEmitter.emit(NAVIGATION_EVENT_FOCUS);
388
+ return () => {
389
+ dlog(`Drawer: route "${focusedRoute?.name}" blurred`);
390
+ routeEmitter.emit(NAVIGATION_EVENT_BLUR);
391
+ };
392
+ // focusedRoute omitted deliberately: only its .key (tracked via focusedRouteKey) should
393
+ // re-run this effect - see the comment above on focusedRouteKey.
394
+ // eslint-disable-next-line react-hooks/exhaustive-deps
395
+ }, [routeEmitter, focusedRouteKey]);
396
+
397
+ const content =
398
+ focusedEntry && focusedRoute
399
+ ? createElement(
400
+ NavigationContext.Provider,
401
+ {
402
+ value: {
403
+ route: focusedRoute,
404
+ navigation: handle,
405
+ emitter: routeEmitter,
406
+ parent: ambientContext,
407
+ },
408
+ },
409
+ createElement(focusedEntry.component),
410
+ )
411
+ : null;
412
+
413
+ const descriptors: IDrawerDescriptorMap = {};
414
+ for (const route of state.routes) {
415
+ const entry = registry.get(route.name);
416
+ if (entry === undefined) continue;
417
+ descriptors[route.key] = {
418
+ options: resolveDrawerScreenOptions(
419
+ entry,
420
+ { route, navigation: handle },
421
+ screenOptions,
422
+ ),
423
+ navigation: handle,
424
+ };
425
+ }
426
+
427
+ const drawerContent =
428
+ renderDrawerContent?.({ state, descriptors, navigation: handle }) ?? null;
429
+
430
+ const root = renderDrawer(
431
+ {
432
+ overlayColor: overlayColor ?? DRAWER_DEFAULT_OVERLAY_COLOR,
433
+ drawerStyle,
434
+ contentPassthrough: {},
435
+ overlayPassthrough: animated
436
+ ? {
437
+ pointerEvents: state.isOpen ? 'auto' : 'none',
438
+ onStartShouldSetResponder: () => true,
439
+ onResponderRelease: () => {
440
+ animateProgressTo(false);
441
+ dispatch({ type: 'closeDrawer' });
442
+ },
443
+ }
444
+ : {},
445
+ panelPassthrough: {},
286
446
  },
287
- }),
288
- ).current;
289
-
290
- const animated = isDrawerAnimated(options);
291
- const geometry = useMemo(
292
- () => resolveDrawerGeometry(options),
293
- [options.drawerType, options.drawerPosition, options.drawerWidth],
294
- );
295
-
296
- const panelSlot = animated ? resolveDrawerSlotInterpolation(geometry, 'panel') : undefined;
297
- const contentSlot = animated ? resolveDrawerSlotInterpolation(geometry, 'content') : undefined;
298
- const overlaySlot = animated ? resolveDrawerSlotInterpolation(geometry, 'overlay') : undefined;
299
-
300
- const panelTranslateX = panelSlot ? progress.interpolate(panelSlot.translateX) : undefined;
301
- const contentTranslateX = contentSlot ? progress.interpolate(contentSlot.translateX) : undefined;
302
- const overlayOpacity = overlaySlot ? progress.interpolate(overlaySlot.opacity) : undefined;
303
- // The overlay is a full-screen absolutely-positioned sibling BELOW content in paint order
304
- // (see render-drawer.ts's drawerChildOrder) - for 'front' that's fine since content never moves,
305
- // but for 'slide' content itself translates away by contentTranslateX, and without following it
306
- // the overlay stays pinned full-screen, dimming (and touch-capturing) the now-revealed panel
307
- // underneath instead of just the content sliver it's meant to dim. Tying overlay to the SAME
308
- // translateX as content keeps it registered exactly under content, wherever content actually is
309
- // (resolveDrawerSlotInterpolation's 'overlay' overload returns that same content delta).
310
- const overlayTranslateX = overlaySlot ? progress.interpolate(overlaySlot.translateX) : undefined;
311
-
312
- const focusedRoute = state.routes[state.index];
313
- const focusedEntry = focusedRoute ? registry.get(focusedRoute.name) : undefined;
314
- if (focusedRoute && !focusedEntry) {
315
- dlog(`Drawer: no screen registered for route name "${focusedRoute.name}"`);
316
- }
317
-
318
- // Only the focused route's screen is ever mounted (like Tab, unlike Stack which keeps every
319
- // pushed route alive), so a fresh emitter per focus change is sufficient - see tabs.ts's
320
- // matching comment for why no per-route emitter map is needed. Keyed on the route KEY, not the
321
- // route object, so a jumpTo-with-no-params re-focus of the ALREADY-focused route (a no-op in
322
- // drawerRouterReducer) and any future params merge don't spuriously re-fire focus/blur.
323
- const focusedRouteKey = focusedRoute?.key;
324
- const routeEmitter = useMemo(() => createNavigationEmitter(), [focusedRouteKey]);
325
-
326
- // Drawer paints its own panel in pure JS - there is no native onAppear/onDisappear to hook
327
- // (unlike Stack's RNSScreen), so focus/blur is synthesized here: mount = focus, cleanup = blur,
328
- // exactly what an effect keyed on focusedRouteKey already encodes - no diffFocusedRoute
329
- // indirection needed (unlike Vue/Angular, which diff real prev/next keys inside an imperative
330
- // watch/CD callback that has no mount/cleanup pairing of its own).
331
- useEffect(() => {
332
- if (focusedRouteKey === undefined) return undefined;
333
- dlog(`Drawer: route "${focusedRoute?.name}" focused`);
334
- routeEmitter.emit(NAVIGATION_EVENT_FOCUS);
335
- return () => {
336
- dlog(`Drawer: route "${focusedRoute?.name}" blurred`);
337
- routeEmitter.emit(NAVIGATION_EVENT_BLUR);
447
+ options,
448
+ );
449
+
450
+ const order = drawerChildOrder(options);
451
+ const slots = new Map<IDrawerSlot, IDescriptor>();
452
+ order.forEach((slot, index) => {
453
+ const descriptor = root.children[index];
454
+ if (typeof descriptor !== 'string') slots.set(slot, descriptor);
455
+ });
456
+
457
+ const slotChildren: Record<IDrawerSlot, ReactNode> = {
458
+ content,
459
+ overlay: null,
460
+ panel: drawerContent,
338
461
  };
339
- // focusedRoute omitted deliberately: only its .key (tracked via focusedRouteKey) should
340
- // re-run this effect - see the comment above on focusedRouteKey.
341
- // eslint-disable-next-line react-hooks/exhaustive-deps
342
- }, [routeEmitter, focusedRouteKey]);
343
-
344
- const content =
345
- focusedEntry && focusedRoute
346
- ? createElement(
347
- NavigationContext.Provider,
348
- {
349
- value: {
350
- route: focusedRoute,
351
- navigation: handle,
352
- emitter: routeEmitter,
353
- parent: ambientContext,
462
+ // Each animated style holds an AnimatedInterpolation node, not a plain number/color - it feeds
463
+ // only Animated.View's deliberately permissive `style?: unknown` (see create-animated-component.tsx),
464
+ // never the plain-IViewStyle branch below, so this stays untyped rather than widening IViewStyle.
465
+ const slotAnimatedStyle: Record<IDrawerSlot, unknown> = {
466
+ content:
467
+ contentTranslateX === undefined
468
+ ? undefined
469
+ : { transform: [{ translateX: contentTranslateX }] },
470
+ overlay:
471
+ overlayOpacity === undefined
472
+ ? undefined
473
+ : {
474
+ opacity: overlayOpacity,
475
+ transform: [{ translateX: overlayTranslateX }],
354
476
  },
355
- },
356
- createElement(focusedEntry.component),
357
- )
358
- : null;
359
-
360
- const descriptors: IDrawerDescriptorMap = {};
361
- for (const route of state.routes) {
362
- const entry = registry.get(route.name);
363
- if (entry === undefined) continue;
364
- descriptors[route.key] = {
365
- options: resolveDrawerScreenOptions(entry, { route, navigation: handle }, screenOptions),
366
- navigation: handle,
477
+ panel:
478
+ panelTranslateX === undefined
479
+ ? undefined
480
+ : { transform: [{ translateX: panelTranslateX }] },
367
481
  };
368
- }
369
-
370
- const drawerContent = renderDrawerContent?.({ state, descriptors, navigation: handle }) ?? null;
371
482
 
372
- const root = renderDrawer(
373
- {
374
- overlayColor: overlayColor ?? DRAWER_DEFAULT_OVERLAY_COLOR,
375
- drawerStyle,
376
- contentPassthrough: {},
377
- overlayPassthrough: animated
378
- ? {
379
- pointerEvents: state.isOpen ? 'auto' : 'none',
380
- onStartShouldSetResponder: () => true,
381
- onResponderRelease: () => {
382
- animateProgressTo(false);
383
- dispatch({ type: 'closeDrawer' });
384
- },
385
- }
386
- : {},
387
- panelPassthrough: {},
388
- },
389
- options,
390
- );
391
-
392
- const order = drawerChildOrder(options);
393
- const slots = new Map<IDrawerSlot, IDescriptor>();
394
- order.forEach((slot, index) => {
395
- const descriptor = root.children[index];
396
- if (typeof descriptor !== 'string') slots.set(slot, descriptor);
397
- });
398
-
399
- const slotChildren: Record<IDrawerSlot, ReactNode> = {
400
- content,
401
- overlay: null,
402
- panel: drawerContent,
403
- };
404
- // Each animated style holds an AnimatedInterpolation node, not a plain number/color - it feeds
405
- // only Animated.View's deliberately permissive `style?: unknown` (see create-animated-component.tsx),
406
- // never the plain-IViewStyle branch below, so this stays untyped rather than widening IViewStyle.
407
- const slotAnimatedStyle: Record<IDrawerSlot, unknown> = {
408
- content:
409
- contentTranslateX === undefined
410
- ? undefined
411
- : { transform: [{ translateX: contentTranslateX }] },
412
- overlay:
413
- overlayOpacity === undefined
414
- ? undefined
415
- : { opacity: overlayOpacity, transform: [{ translateX: overlayTranslateX }] },
416
- panel:
417
- panelTranslateX === undefined ? undefined : { transform: [{ translateX: panelTranslateX }] },
418
- };
419
-
420
- function renderSlot(slot: IDrawerSlot): ReactElement | null {
421
- const descriptor = slots.get(slot);
422
- if (descriptor === undefined) return null;
423
- const animatedStyle = slotAnimatedStyle[slot];
424
- if (animatedStyle === undefined) {
483
+ function renderSlot(slot: IDrawerSlot): ReactElement | null {
484
+ const descriptor = slots.get(slot);
485
+ if (descriptor === undefined) return null;
486
+ const animatedStyle = slotAnimatedStyle[slot];
487
+ if (animatedStyle === undefined) {
488
+ return createElement(
489
+ descriptor.type,
490
+ { key: descriptor.key, ...descriptor.props },
491
+ slotChildren[slot],
492
+ );
493
+ }
494
+ const style = [descriptor.props.style, animatedStyle];
425
495
  return createElement(
426
- descriptor.type,
427
- { key: descriptor.key, ...descriptor.props },
496
+ Animated.View,
497
+ { key: descriptor.key, ...descriptor.props, style },
428
498
  slotChildren[slot],
429
499
  );
430
500
  }
431
- const style = [descriptor.props.style, animatedStyle];
501
+
502
+ const drawerChildren = order
503
+ .map(renderSlot)
504
+ .filter((element): element is ReactElement => element !== null);
505
+
432
506
  return createElement(
433
- Animated.View,
434
- { key: descriptor.key, ...descriptor.props, style },
435
- slotChildren[slot],
507
+ 'symbiote-view',
508
+ { style: root.props.style, ...panResponder.panHandlers },
509
+ ...drawerChildren,
436
510
  );
437
- }
438
-
439
- const drawerChildren = order
440
- .map(renderSlot)
441
- .filter((element): element is ReactElement => element !== null);
442
-
443
- return createElement(
444
- 'symbiote-view',
445
- { style: root.props.style, ...panResponder.panHandlers },
446
- ...drawerChildren,
447
- );
448
- });
511
+ },
512
+ );
449
513
 
450
514
  export const Drawer = Object.assign(DrawerImpl, { Screen: DrawerScreen });
451
515