@symbiote-native/navigation 2.0.3 → 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 (320) hide show
  1. package/README.md +42 -12
  2. package/build/angular/drawer/index.js +29 -13
  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.d.ts +2 -0
  8. package/build/angular/stack/index.js +43 -9
  9. package/build/angular/tabs/index.d.ts +1 -1
  10. package/build/angular/tabs/index.js +40 -13
  11. package/build/core/drawer-options/index.js +19 -5
  12. package/build/core/drawer-router-state/index.js +8 -2
  13. package/build/core/guards.js +3 -1
  14. package/build/core/header-bar-buttons.js +14 -4
  15. package/build/core/index.d.ts +6 -5
  16. package/build/core/index.js +7 -3
  17. package/build/core/navigator-state/index.js +12 -4
  18. package/build/core/render-drawer.js +4 -1
  19. package/build/core/render-stack.js +13 -4
  20. package/build/core/screen-options.js +1 -1
  21. package/build/core/search-bar-commands.js +1 -1
  22. package/build/core/stack-router-state/index.d.ts +2 -0
  23. package/build/core/stack-router-state/index.js +44 -0
  24. package/build/core/state-persistence/index.js +6 -2
  25. package/build/core/tab-router-state/index.d.ts +1 -0
  26. package/build/core/tab-router-state/index.js +27 -2
  27. package/build/react/drawer/index.js +59 -26
  28. package/build/react/index.d.ts +7 -7
  29. package/build/react/index.js +2 -2
  30. package/build/react/stack/index.js +47 -10
  31. package/build/react/tabs/index.js +36 -12
  32. package/build/solid/drawer/index.d.ts +22 -0
  33. package/build/solid/drawer/index.js +394 -0
  34. package/build/solid/drawer-screen-props.d.ts +13 -0
  35. package/build/solid/drawer-screen-props.js +2 -0
  36. package/build/solid/host.d.ts +2 -0
  37. package/build/solid/host.js +23 -0
  38. package/build/solid/index.d.ts +19 -0
  39. package/build/solid/index.js +24 -0
  40. package/build/solid/linking.d.ts +3 -0
  41. package/build/solid/linking.js +55 -0
  42. package/build/solid/navigation-context.d.ts +13 -0
  43. package/build/solid/navigation-context.js +37 -0
  44. package/build/solid/primitives/create-focus-effect.d.ts +1 -0
  45. package/build/solid/primitives/create-focus-effect.js +30 -0
  46. package/build/solid/primitives/create-is-focused.d.ts +2 -0
  47. package/build/solid/primitives/create-is-focused.js +31 -0
  48. package/build/solid/primitives/create-navigation-state.d.ts +3 -0
  49. package/build/solid/primitives/create-navigation-state.js +30 -0
  50. package/build/solid/primitives/index.d.ts +12 -0
  51. package/build/solid/primitives/index.js +8 -0
  52. package/build/solid/primitives/use-drawer-navigation.d.ts +5 -0
  53. package/build/solid/primitives/use-drawer-navigation.js +15 -0
  54. package/build/solid/primitives/use-navigation.d.ts +8 -0
  55. package/build/solid/primitives/use-navigation.js +23 -0
  56. package/build/solid/primitives/use-route.d.ts +3 -0
  57. package/build/solid/primitives/use-route.js +14 -0
  58. package/build/solid/primitives/use-stack-navigation.d.ts +5 -0
  59. package/build/solid/primitives/use-stack-navigation.js +19 -0
  60. package/build/solid/primitives/use-tab-navigation.d.ts +5 -0
  61. package/build/solid/primitives/use-tab-navigation.js +15 -0
  62. package/build/solid/screen-props.d.ts +19 -0
  63. package/build/solid/screen-props.js +9 -0
  64. package/build/solid/screen-registry.d.ts +31 -0
  65. package/build/solid/screen-registry.js +86 -0
  66. package/build/solid/screen.d.ts +6 -0
  67. package/build/solid/screen.js +69 -0
  68. package/build/solid/stack/index.d.ts +15 -0
  69. package/build/solid/stack/index.js +348 -0
  70. package/build/solid/tab-screen-props.d.ts +13 -0
  71. package/build/solid/tab-screen-props.js +3 -0
  72. package/build/solid/tabs/index.d.ts +14 -0
  73. package/build/solid/tabs/index.js +228 -0
  74. package/build/svelte/attachments.d.ts +5 -0
  75. package/build/svelte/attachments.js +61 -0
  76. package/build/svelte/descriptor-subtree.d.ts +3 -0
  77. package/build/svelte/descriptor-subtree.js +39 -0
  78. package/build/svelte/drawer/drawer-props.d.ts +14 -0
  79. package/build/svelte/drawer/drawer-props.js +3 -0
  80. package/build/svelte/drawer/index.d.ts +10 -0
  81. package/build/svelte/drawer/index.js +6 -0
  82. package/build/svelte/drawer/index.svelte +559 -0
  83. package/build/svelte/drawer/index.svelte.d.ts +10 -0
  84. package/build/svelte/drawer-screen-props.d.ts +13 -0
  85. package/build/svelte/drawer-screen-props.js +4 -0
  86. package/build/svelte/drawer-screen.svelte +25 -0
  87. package/build/svelte/drawer-screen.svelte.d.ts +4 -0
  88. package/build/svelte/index.d.ts +21 -0
  89. package/build/svelte/index.js +20 -0
  90. package/build/svelte/linking.svelte.d.ts +2 -0
  91. package/build/svelte/linking.svelte.js +55 -0
  92. package/build/svelte/navigation-context.d.ts +14 -0
  93. package/build/svelte/navigation-context.js +44 -0
  94. package/build/svelte/navigation-scope.svelte +27 -0
  95. package/build/svelte/navigation-scope.svelte.d.ts +9 -0
  96. package/build/svelte/registry-host.d.ts +1 -0
  97. package/build/svelte/registry-host.js +26 -0
  98. package/build/svelte/runes/index.d.ts +12 -0
  99. package/build/svelte/runes/index.js +12 -0
  100. package/build/svelte/runes/use-drawer-navigation.svelte.d.ts +6 -0
  101. package/build/svelte/runes/use-drawer-navigation.svelte.js +19 -0
  102. package/build/svelte/runes/use-focus-effect.svelte.d.ts +1 -0
  103. package/build/svelte/runes/use-focus-effect.svelte.js +31 -0
  104. package/build/svelte/runes/use-is-focused.svelte.d.ts +3 -0
  105. package/build/svelte/runes/use-is-focused.svelte.js +34 -0
  106. package/build/svelte/runes/use-navigation-state.svelte.d.ts +4 -0
  107. package/build/svelte/runes/use-navigation-state.svelte.js +39 -0
  108. package/build/svelte/runes/use-navigation.svelte.d.ts +9 -0
  109. package/build/svelte/runes/use-navigation.svelte.js +26 -0
  110. package/build/svelte/runes/use-route.svelte.d.ts +4 -0
  111. package/build/svelte/runes/use-route.svelte.js +12 -0
  112. package/build/svelte/runes/use-stack-navigation.svelte.d.ts +6 -0
  113. package/build/svelte/runes/use-stack-navigation.svelte.js +22 -0
  114. package/build/svelte/runes/use-tab-navigation.svelte.d.ts +6 -0
  115. package/build/svelte/runes/use-tab-navigation.svelte.js +19 -0
  116. package/build/svelte/screen-props.d.ts +21 -0
  117. package/build/svelte/screen-props.js +6 -0
  118. package/build/svelte/screen-registry.d.ts +17 -0
  119. package/build/svelte/screen-registry.js +57 -0
  120. package/build/svelte/screen.svelte +28 -0
  121. package/build/svelte/screen.svelte.d.ts +4 -0
  122. package/build/svelte/stack/index.d.ts +14 -0
  123. package/build/svelte/stack/index.js +7 -0
  124. package/build/svelte/stack/index.svelte +261 -0
  125. package/build/svelte/stack/index.svelte.d.ts +15 -0
  126. package/build/svelte/stack/stack-props.d.ts +20 -0
  127. package/build/svelte/stack/stack-props.js +4 -0
  128. package/build/svelte/stack/stack-screen.svelte +205 -0
  129. package/build/svelte/stack/stack-screen.svelte.d.ts +4 -0
  130. package/build/svelte/tab-screen-props.d.ts +13 -0
  131. package/build/svelte/tab-screen-props.js +4 -0
  132. package/build/svelte/tab-screen.svelte +26 -0
  133. package/build/svelte/tab-screen.svelte.d.ts +4 -0
  134. package/build/svelte/tabs/index.d.ts +8 -0
  135. package/build/svelte/tabs/index.js +6 -0
  136. package/build/svelte/tabs/index.svelte +269 -0
  137. package/build/svelte/tabs/index.svelte.d.ts +8 -0
  138. package/build/svelte/tabs/tab-props.d.ts +7 -0
  139. package/build/svelte/tabs/tab-props.js +3 -0
  140. package/build/vue/composables/use-navigation.js +5 -1
  141. package/build/vue/drawer/index.js +36 -13
  142. package/build/vue/index.d.ts +3 -3
  143. package/build/vue/navigation-context.js +1 -1
  144. package/build/vue/stack/index.js +43 -18
  145. package/build/vue/tabs/index.js +65 -39
  146. package/build-ngc/angular/drawer/index.js +40 -18
  147. package/build-ngc/angular/drawer/index.js.map +1 -1
  148. package/build-ngc/angular/drawer-screen.directive.js +3 -3
  149. package/build-ngc/angular/index.d.ts +4 -4
  150. package/build-ngc/angular/index.js.map +1 -1
  151. package/build-ngc/angular/injectors/inject-focus-effect.js.map +1 -1
  152. package/build-ngc/angular/injectors/inject-is-focused.js.map +1 -1
  153. package/build-ngc/angular/injectors/inject-navigation-state.js.map +1 -1
  154. package/build-ngc/angular/injectors/inject-navigation.js +1 -1
  155. package/build-ngc/angular/injectors/inject-navigation.js.map +1 -1
  156. package/build-ngc/angular/linking.js.map +1 -1
  157. package/build-ngc/angular/navigation-context.service.js +3 -3
  158. package/build-ngc/angular/navigation-context.service.js.map +1 -1
  159. package/build-ngc/angular/navigation-scope.directive.js +4 -4
  160. package/build-ngc/angular/navigation-scope.directive.js.map +1 -1
  161. package/build-ngc/angular/screen.directive.js +3 -3
  162. package/build-ngc/angular/screen.directive.js.map +1 -1
  163. package/build-ngc/angular/search-bar-ref.directive.js +4 -4
  164. package/build-ngc/angular/search-bar-ref.directive.js.map +1 -1
  165. package/build-ngc/angular/stack/index.d.ts +2 -0
  166. package/build-ngc/angular/stack/index.js +52 -14
  167. package/build-ngc/angular/stack/index.js.map +1 -1
  168. package/build-ngc/angular/tab-screen.directive.js +3 -3
  169. package/build-ngc/angular/tab-screen.directive.js.map +1 -1
  170. package/build-ngc/angular/tabs/index.d.ts +1 -1
  171. package/build-ngc/angular/tabs/index.js +43 -16
  172. package/build-ngc/angular/tabs/index.js.map +1 -1
  173. package/build-ngc/core/constants.js.map +1 -1
  174. package/build-ngc/core/drawer-options/index.js +19 -5
  175. package/build-ngc/core/drawer-options/index.js.map +1 -1
  176. package/build-ngc/core/drawer-router-state/index.js +8 -2
  177. package/build-ngc/core/drawer-router-state/index.js.map +1 -1
  178. package/build-ngc/core/guards.js +3 -1
  179. package/build-ngc/core/guards.js.map +1 -1
  180. package/build-ngc/core/header-bar-buttons.js +14 -4
  181. package/build-ngc/core/header-bar-buttons.js.map +1 -1
  182. package/build-ngc/core/index.d.ts +6 -5
  183. package/build-ngc/core/index.js +7 -3
  184. package/build-ngc/core/index.js.map +1 -1
  185. package/build-ngc/core/linking-config/index.js.map +1 -1
  186. package/build-ngc/core/navigation-events/index.js.map +1 -1
  187. package/build-ngc/core/navigator-handles/index.js.map +1 -1
  188. package/build-ngc/core/navigator-state/index.js +12 -4
  189. package/build-ngc/core/navigator-state/index.js.map +1 -1
  190. package/build-ngc/core/render-drawer.js +4 -1
  191. package/build-ngc/core/render-drawer.js.map +1 -1
  192. package/build-ngc/core/render-stack.js +13 -4
  193. package/build-ngc/core/render-stack.js.map +1 -1
  194. package/build-ngc/core/render-tabs.js.map +1 -1
  195. package/build-ngc/core/screen-options.js +1 -1
  196. package/build-ngc/core/screen-options.js.map +1 -1
  197. package/build-ngc/core/search-bar-commands.js +1 -1
  198. package/build-ngc/core/search-bar-commands.js.map +1 -1
  199. package/build-ngc/core/stack-router-state/index.d.ts +2 -0
  200. package/build-ngc/core/stack-router-state/index.js +45 -0
  201. package/build-ngc/core/stack-router-state/index.js.map +1 -0
  202. package/build-ngc/core/state-persistence/index.js +6 -2
  203. package/build-ngc/core/state-persistence/index.js.map +1 -1
  204. package/build-ngc/core/tab-router-state/index.d.ts +1 -0
  205. package/build-ngc/core/tab-router-state/index.js +27 -2
  206. package/build-ngc/core/tab-router-state/index.js.map +1 -1
  207. package/package.json +44 -16
  208. package/src/angular/drawer/index.ts +75 -29
  209. package/src/angular/index.ts +20 -4
  210. package/src/angular/injectors/inject-focus-effect.ts +8 -2
  211. package/src/angular/injectors/inject-is-focused.ts +6 -4
  212. package/src/angular/injectors/inject-navigation-state.ts +7 -4
  213. package/src/angular/injectors/inject-navigation.ts +12 -3
  214. package/src/angular/linking.ts +4 -1
  215. package/src/angular/navigation-context.service.ts +5 -2
  216. package/src/angular/navigation-scope.directive.ts +6 -2
  217. package/src/angular/screen.directive.ts +13 -3
  218. package/src/angular/search-bar-ref.directive.ts +10 -2
  219. package/src/angular/stack/index.ts +91 -24
  220. package/src/angular/tab-screen.directive.ts +3 -1
  221. package/src/angular/tabs/index.ts +54 -16
  222. package/src/core/constants.ts +6 -3
  223. package/src/core/drawer-options/index.ts +40 -13
  224. package/src/core/drawer-router-state/index.ts +11 -3
  225. package/src/core/guards.ts +3 -1
  226. package/src/core/header-bar-buttons.ts +41 -12
  227. package/src/core/index.ts +29 -5
  228. package/src/core/linking-config/index.ts +31 -10
  229. package/src/core/navigation-events/index.ts +8 -2
  230. package/src/core/navigator-handles/index.ts +8 -3
  231. package/src/core/navigator-props.ts +13 -4
  232. package/src/core/navigator-state/index.ts +24 -7
  233. package/src/core/render-drawer.ts +24 -9
  234. package/src/core/render-stack.ts +52 -13
  235. package/src/core/render-tabs.ts +24 -6
  236. package/src/core/screen-options.ts +15 -5
  237. package/src/core/search-bar-commands.ts +12 -3
  238. package/src/core/stack-router-state/index.ts +52 -0
  239. package/src/core/state-persistence/index.ts +13 -3
  240. package/src/core/tab-router-state/index.ts +35 -3
  241. package/src/react/drawer/index.ts +409 -330
  242. package/src/react/drawer-screen.ts +8 -2
  243. package/src/react/hooks/use-focus-effect.ts +8 -2
  244. package/src/react/hooks/use-is-focused.ts +6 -2
  245. package/src/react/hooks/use-navigation.ts +8 -2
  246. package/src/react/index.ts +37 -7
  247. package/src/react/linking.ts +4 -1
  248. package/src/react/navigation-context.ts +6 -2
  249. package/src/react/screen.ts +12 -3
  250. package/src/react/stack/index.ts +297 -220
  251. package/src/react/tab-screen.ts +3 -1
  252. package/src/react/tabs/index.ts +202 -139
  253. package/src/solid/drawer/index.ts +570 -0
  254. package/src/solid/drawer-screen-props.ts +24 -0
  255. package/src/solid/host.ts +29 -0
  256. package/src/solid/index.ts +87 -0
  257. package/src/solid/linking.ts +74 -0
  258. package/src/solid/navigation-context.ts +56 -0
  259. package/src/solid/primitives/create-focus-effect.ts +41 -0
  260. package/src/solid/primitives/create-is-focused.ts +41 -0
  261. package/src/solid/primitives/create-navigation-state.ts +44 -0
  262. package/src/solid/primitives/index.ts +12 -0
  263. package/src/solid/primitives/use-drawer-navigation.ts +25 -0
  264. package/src/solid/primitives/use-navigation.ts +44 -0
  265. package/src/solid/primitives/use-route.ts +18 -0
  266. package/src/solid/primitives/use-stack-navigation.ts +29 -0
  267. package/src/solid/primitives/use-tab-navigation.ts +25 -0
  268. package/src/solid/screen-props.ts +51 -0
  269. package/src/solid/screen-registry.ts +159 -0
  270. package/src/solid/screen.ts +80 -0
  271. package/src/solid/stack/index.ts +528 -0
  272. package/src/solid/tab-screen-props.ts +21 -0
  273. package/src/solid/tabs/index.ts +327 -0
  274. package/src/svelte/attachments.ts +70 -0
  275. package/src/svelte/descriptor-subtree.ts +51 -0
  276. package/src/svelte/drawer/drawer-props.ts +28 -0
  277. package/src/svelte/drawer/index.svelte +559 -0
  278. package/src/svelte/drawer/index.ts +10 -0
  279. package/src/svelte/drawer-screen-props.ts +26 -0
  280. package/src/svelte/drawer-screen.svelte +25 -0
  281. package/src/svelte/fabric-tree.test-helper.ts +95 -0
  282. package/src/svelte/index.ts +83 -0
  283. package/src/svelte/linking.svelte.ts +72 -0
  284. package/src/svelte/navigation-context.ts +62 -0
  285. package/src/svelte/navigation-scope.svelte +27 -0
  286. package/src/svelte/registry-host.ts +26 -0
  287. package/src/svelte/runes/index.ts +17 -0
  288. package/src/svelte/runes/use-drawer-navigation.svelte.ts +31 -0
  289. package/src/svelte/runes/use-focus-effect.svelte.ts +41 -0
  290. package/src/svelte/runes/use-is-focused.svelte.ts +38 -0
  291. package/src/svelte/runes/use-navigation-state.svelte.ts +48 -0
  292. package/src/svelte/runes/use-navigation.svelte.ts +49 -0
  293. package/src/svelte/runes/use-route.svelte.ts +16 -0
  294. package/src/svelte/runes/use-stack-navigation.svelte.ts +34 -0
  295. package/src/svelte/runes/use-tab-navigation.svelte.ts +29 -0
  296. package/src/svelte/screen-props.ts +50 -0
  297. package/src/svelte/screen-registry.ts +98 -0
  298. package/src/svelte/screen.svelte +28 -0
  299. package/src/svelte/stack/index.svelte +261 -0
  300. package/src/svelte/stack/index.ts +11 -0
  301. package/src/svelte/stack/stack-props.ts +31 -0
  302. package/src/svelte/stack/stack-screen.svelte +205 -0
  303. package/src/svelte/svelte-ambient.d.ts +6 -0
  304. package/src/svelte/svelte-compile.test-helper.ts +147 -0
  305. package/src/svelte/tab-screen-props.ts +22 -0
  306. package/src/svelte/tab-screen.svelte +26 -0
  307. package/src/svelte/tabs/index.svelte +269 -0
  308. package/src/svelte/tabs/index.ts +10 -0
  309. package/src/svelte/tabs/tab-props.ts +13 -0
  310. package/src/vue/composables/use-navigation-state.ts +7 -4
  311. package/src/vue/composables/use-navigation.ts +13 -3
  312. package/src/vue/drawer/index.ts +128 -41
  313. package/src/vue/drawer-screen.ts +12 -5
  314. package/src/vue/index.ts +15 -3
  315. package/src/vue/linking.ts +7 -2
  316. package/src/vue/navigation-context.ts +17 -5
  317. package/src/vue/screen.ts +7 -2
  318. package/src/vue/stack/index.ts +108 -43
  319. package/src/vue/tab-screen.ts +3 -1
  320. package/src/vue/tabs/index.ts +109 -68
@@ -0,0 +1,559 @@
1
+ <script lang="ts" module>
2
+ // Drawer, the Svelte lifecycle half. The open/closed + focused-route router
3
+ // (drawer-router-state) and the pure swipe/geometry math (drawer-options) live in
4
+ // @symbiote-native/navigation core, shared verbatim with the React/Vue/Angular entries; here
5
+ // Svelte supplies the lifecycle - `$state.raw` for the router (its twin of useReducer), a
6
+ // PanResponder for the swipe gesture (built ONCE, its callbacks reading options and open-state
7
+ // LIVE off `$props()`/`$state` at gesture time), an AnimatedValue driving the slide/opacity
8
+ // transforms, and `export function`s for the open/close/toggle/jumpTo handle - mirroring
9
+ // tabs/index.svelte's shape (Tab is the closer sibling: both are fixed-route-list,
10
+ // no-react-native-screens navigators; Stack's push/pop + native-screen bridging don't apply).
11
+ //
12
+ // FEASIBILITY NOTE (mirrored from the React/Vue twins): the REAL @react-navigation/drawer is
13
+ // built on react-native-gesture-handler + react-native-reanimated, neither of which this
14
+ // codebase depends on. What's built here reaches the same swipe-to-open/close +
15
+ // front/back/slide/permanent behavior using only PanResponder + Animated (both already in
16
+ // @symbiote-native/engine), which is sufficient for a solid drawer but NOT byte-for-byte parity
17
+ // - see the explicit gap list at the bottom of this file (verbatim from the React twin - the
18
+ // gaps are architectural, not adapter-specific).
19
+ let drawerInstanceCounter = 0;
20
+
21
+ const DRAWER_SNAP_DURATION = 250;
22
+ </script>
23
+
24
+ <script lang="ts">
25
+ import type { Component } from 'svelte';
26
+ import { onDestroy, tick } from 'svelte';
27
+ import {
28
+ AnimatedValue,
29
+ Dimensions,
30
+ PanResponder,
31
+ dlog,
32
+ timing,
33
+ } from '@symbiote-native/engine';
34
+ import type {
35
+ IPanResponderGestureState,
36
+ ISymbioteEvent,
37
+ } from '@symbiote-native/engine';
38
+ // The ONLY import from the adapter's main barrel: `Animated.View` is a compiled `.svelte`
39
+ // component, so unlike PanResponder/Dimensions/AnimatedValue (pure engine re-exports, taken
40
+ // from the engine directly here) it has no `.svelte`-free home to import from.
41
+ import { Animated } from '@symbiote-native/svelte';
42
+ import {
43
+ DRAWER_DEFAULT_OVERLAY_COLOR,
44
+ NAVIGATION_EVENT_BLUR,
45
+ NAVIGATION_EVENT_FOCUS,
46
+ createInitialDrawerRouterState,
47
+ createNavigationEmitter,
48
+ diffFocusedRoute,
49
+ drawerChildOrder,
50
+ drawerRouterReducer,
51
+ isDrawerAnimated,
52
+ renderDrawer,
53
+ resolveDragProgress,
54
+ resolveDrawerGeometry,
55
+ resolveDrawerSlotInterpolation,
56
+ resolveSwipeIntent,
57
+ shouldClaimDrawerSwipe,
58
+ } from '../../core';
59
+ import type {
60
+ IDescriptor,
61
+ IDrawerDescriptorMap,
62
+ IDrawerNavigatorHandle,
63
+ IDrawerOptions,
64
+ IDrawerRouterAction,
65
+ IDrawerRouterState,
66
+ IDrawerScreenOptions,
67
+ IDrawerSlot,
68
+ INavigationEmitter,
69
+ IRoute,
70
+ } from '../../core';
71
+ import { getNavigationScope } from '../navigation-context';
72
+ import type { INavigationScopeValue } from '../navigation-context';
73
+ import NavigationScope from '../navigation-scope.svelte';
74
+ import { SCREEN_REGISTRY_HOST_PROPS } from '../registry-host';
75
+ import {
76
+ setScreenCollector,
77
+ toRegistry,
78
+ withoutScreen,
79
+ } from '../screen-registry';
80
+ import type { IRegisteredScreen } from '../screen-registry';
81
+ import type { IDrawerScreenProps } from '../drawer-screen-props';
82
+ import type { IDrawerProps } from './drawer-props';
83
+
84
+ let {
85
+ initialRouteName,
86
+ drawerStyle,
87
+ drawerType,
88
+ drawerPosition,
89
+ drawerWidth,
90
+ overlayColor,
91
+ swipeEnabled,
92
+ swipeEdgeWidth,
93
+ swipeMinDistance,
94
+ swipeMinVelocity,
95
+ children,
96
+ drawerContent,
97
+ }: IDrawerProps = $props();
98
+
99
+ // Read BEFORE this Drawer establishes its own per-screen NavigationScope - becomes the `parent`
100
+ // link a nested screen's useNavigation().getParent() walks. undefined at the nesting root.
101
+ const parentScope = getNavigationScope();
102
+
103
+ const routeIdPrefix = `drawer-${(drawerInstanceCounter += 1)}`;
104
+
105
+ // Unlike Vue (which reads everything off untyped `attrs` and needs a per-field guard to get
106
+ // back into the typed core surface), Svelte's `$props()` is already typed by IDrawerProps, so
107
+ // this is a plain re-assembly with no narrowing at all.
108
+ const options = $derived<IDrawerOptions>({
109
+ drawerType,
110
+ drawerPosition,
111
+ drawerWidth,
112
+ overlayColor,
113
+ swipeEnabled,
114
+ swipeEdgeWidth,
115
+ swipeMinDistance,
116
+ swipeMinVelocity,
117
+ });
118
+
119
+ let screens = $state.raw<IRegisteredScreen<IDrawerScreenProps['options']>[]>(
120
+ [],
121
+ );
122
+ setScreenCollector<IDrawerScreenProps['options']>({
123
+ kind: 'drawer',
124
+ register: screen => {
125
+ screens = [...screens, screen];
126
+ },
127
+ unregister: screen => {
128
+ screens = withoutScreen(screens, screen);
129
+ },
130
+ });
131
+
132
+ const registry = $derived(toRegistry(screens));
133
+
134
+ // Same seed-once-then-dispatch shape Stack and Tab use.
135
+ let dispatchedState = $state.raw<IDrawerRouterState | null>(null);
136
+ let seededState: IDrawerRouterState | undefined;
137
+ let routeSequence = 0;
138
+
139
+ function seedState(): IDrawerRouterState {
140
+ if (seededState !== undefined) return seededState;
141
+ const routes: IRoute<unknown>[] = [...registry.entries()].map(
142
+ ([name, entry]) => {
143
+ routeSequence += 1;
144
+ return {
145
+ key: `${routeIdPrefix}-${name}-${routeSequence}`,
146
+ name,
147
+ params: entry.initialParams,
148
+ };
149
+ },
150
+ );
151
+ if (routes.length === 0) {
152
+ dlog('Drawer: no <Drawer.Screen> children registered');
153
+ // Deliberately not memoized - markers may still be registering.
154
+ return createInitialDrawerRouterState(routes, initialRouteName);
155
+ }
156
+ seededState = createInitialDrawerRouterState(routes, initialRouteName);
157
+ return seededState;
158
+ }
159
+
160
+ const state = $derived(dispatchedState ?? seedState());
161
+
162
+ function dispatch(action: IDrawerRouterAction): void {
163
+ dispatchedState = drawerRouterReducer(state, action);
164
+ }
165
+
166
+ // progress: 0 closed -> 1 open, the single AnimatedValue every slide/opacity transform below
167
+ // interpolates from. A plain `const`: a Svelte component's script runs once, so this needs no
168
+ // re-creation guard the way React's useRef(new Animated.Value(...)).current does.
169
+ const progress = new AnimatedValue(state.isOpen ? 1 : 0);
170
+ // Where a drag STARTS from, in progress units. Always exactly 0 or 1: a gesture only ever
171
+ // begins at rest, since terminate/release always snap the value back to a resting state before
172
+ // another grant can fire.
173
+ let dragStartProgress = 0;
174
+
175
+ function animateProgressTo(open: boolean): void {
176
+ dlog(`Drawer: animateProgressTo(open=${open}) starting at t=${Date.now()}`);
177
+ timing(progress, {
178
+ toValue: open ? 1 : 0,
179
+ duration: DRAWER_SNAP_DURATION,
180
+ // Native-driver wiring is deferred for v1 - see this file's header feasibility note. The
181
+ // JS timing loop still drives every frame, same as any other non-native-driven timing in
182
+ // this codebase.
183
+ useNativeDriver: false,
184
+ }).start();
185
+ }
186
+
187
+ export function openDrawer(): void {
188
+ dlog(
189
+ `Drawer: openDrawer() called, isOpen=${state.isOpen} at t=${Date.now()}`,
190
+ );
191
+ animateProgressTo(true);
192
+ dispatch({ type: 'openDrawer' });
193
+ }
194
+ export function closeDrawer(): void {
195
+ dlog(
196
+ `Drawer: closeDrawer() called, isOpen=${state.isOpen} at t=${Date.now()}`,
197
+ );
198
+ animateProgressTo(false);
199
+ dispatch({ type: 'closeDrawer' });
200
+ }
201
+ export function toggleDrawer(): void {
202
+ dlog(
203
+ `Drawer: toggleDrawer() called, isOpen=${state.isOpen} at t=${Date.now()}`,
204
+ );
205
+ animateProgressTo(!state.isOpen);
206
+ dispatch({ type: 'toggleDrawer' });
207
+ }
208
+ export function jumpTo(name: string): void {
209
+ // Both sides of the dispatch, because an unregistered name is a documented reducer no-op that
210
+ // hands the SAME state back: animating off the pre-dispatch snapshot alone would slide the
211
+ // panel shut while the router still says isOpen. `state` is a $derived recomputed
212
+ // synchronously off the reassigned `dispatchedState`, so the second read is already the
213
+ // reducer's own answer.
214
+ const wasOpen = state.isOpen;
215
+ dispatch({ type: 'jumpTo', name });
216
+ if (wasOpen && !state.isOpen) animateProgressTo(false);
217
+ }
218
+
219
+ const handle: IDrawerNavigatorHandle = {
220
+ openDrawer,
221
+ closeDrawer,
222
+ toggleDrawer,
223
+ jumpTo,
224
+ };
225
+
226
+ // The window width is read fresh AT GESTURE TIME rather than through a reactive
227
+ // window-dimensions rune: it is only ever consumed inside these PanResponder callbacks (never
228
+ // rendered), so a live `Dimensions.get` is both simpler and strictly more current than a
229
+ // subscription that has to round-trip a re-render first.
230
+ const panResponder = PanResponder.create({
231
+ onStartShouldSetPanResponder: (
232
+ event: ISymbioteEvent,
233
+ gestureState: IPanResponderGestureState,
234
+ ): boolean =>
235
+ shouldClaimDrawerSwipe(
236
+ event,
237
+ gestureState,
238
+ Dimensions.get('window').width,
239
+ state.isOpen,
240
+ options,
241
+ 'start',
242
+ ),
243
+ onMoveShouldSetPanResponder: (
244
+ event: ISymbioteEvent,
245
+ gestureState: IPanResponderGestureState,
246
+ ): boolean =>
247
+ shouldClaimDrawerSwipe(
248
+ event,
249
+ gestureState,
250
+ Dimensions.get('window').width,
251
+ state.isOpen,
252
+ options,
253
+ 'move',
254
+ ),
255
+ onPanResponderGrant: (): void => {
256
+ dlog('Drawer: gesture grant');
257
+ dragStartProgress = state.isOpen ? 1 : 0;
258
+ },
259
+ onPanResponderMove: (
260
+ _event: ISymbioteEvent,
261
+ gestureState: IPanResponderGestureState,
262
+ ): void => {
263
+ progress.setValue(
264
+ resolveDragProgress(gestureState, dragStartProgress, options),
265
+ );
266
+ },
267
+ onPanResponderRelease: (
268
+ _event: ISymbioteEvent,
269
+ gestureState: IPanResponderGestureState,
270
+ ): void => {
271
+ const intent = resolveSwipeIntent(gestureState, state.isOpen, options);
272
+ const open = intent === 'open';
273
+ dlog(`Drawer: gesture release -> ${open ? 'open' : 'close'}`);
274
+ animateProgressTo(open);
275
+ dispatch(open ? { type: 'openDrawer' } : { type: 'closeDrawer' });
276
+ },
277
+ onPanResponderTerminate: (): void => {
278
+ dlog('Drawer: gesture terminated, snapping back');
279
+ animateProgressTo(state.isOpen);
280
+ },
281
+ });
282
+
283
+ // Only the focused route's screen is ever mounted (like Tab, unlike Stack which keeps every
284
+ // pushed route alive). One emitter per route.key, created lazily and cached for the navigator's
285
+ // whole lifetime.
286
+ const emitters = new Map<string, INavigationEmitter>();
287
+ function emitterFor(routeKey: string): INavigationEmitter {
288
+ let emitter = emitters.get(routeKey);
289
+ if (emitter === undefined) {
290
+ emitter = createNavigationEmitter();
291
+ emitters.set(routeKey, emitter);
292
+ }
293
+ return emitter;
294
+ }
295
+
296
+ // Focus/blur synthesis, deferred to `tick()` for the same reason tabs/index.svelte documents:
297
+ // the newly-focused screen's own subscriptions are created during THIS flush, after this
298
+ // effect was.
299
+ let previousFocusedKey: string | undefined;
300
+ $effect(() => {
301
+ const nextKey = state.routes[state.index]?.key;
302
+ const { blurKey, focusKey } = diffFocusedRoute(previousFocusedKey, nextKey);
303
+ if (blurKey === undefined && focusKey === undefined) return;
304
+ previousFocusedKey = nextKey;
305
+ void tick().then(() => {
306
+ if (blurKey !== undefined) {
307
+ dlog(`Drawer: route "${blurKey}" blurred at t=${Date.now()}`);
308
+ emitterFor(blurKey).emit(NAVIGATION_EVENT_BLUR);
309
+ }
310
+ if (focusKey !== undefined) {
311
+ dlog(`Drawer: route "${focusKey}" focused at t=${Date.now()}`);
312
+ emitterFor(focusKey).emit(NAVIGATION_EVENT_FOCUS);
313
+ }
314
+ });
315
+ });
316
+
317
+ onDestroy(() => {
318
+ if (previousFocusedKey !== undefined) {
319
+ emitterFor(previousFocusedKey).emit(NAVIGATION_EVENT_BLUR);
320
+ }
321
+ });
322
+
323
+ const animated = $derived(isDrawerAnimated(options));
324
+ const geometry = $derived(resolveDrawerGeometry(options));
325
+
326
+ const panelTranslateX = $derived(
327
+ animated
328
+ ? progress.interpolate(
329
+ resolveDrawerSlotInterpolation(geometry, 'panel').translateX,
330
+ )
331
+ : undefined,
332
+ );
333
+ const contentTranslateX = $derived(
334
+ animated
335
+ ? progress.interpolate(
336
+ resolveDrawerSlotInterpolation(geometry, 'content').translateX,
337
+ )
338
+ : undefined,
339
+ );
340
+ // The overlay is a full-screen absolutely-positioned sibling BELOW content in paint order (see
341
+ // render-drawer.ts's drawerChildOrder) - for 'front' that's fine since content never moves, but
342
+ // for 'slide' content itself translates away by contentTranslateX, and without following it the
343
+ // overlay stays pinned full-screen. resolveDrawerSlotInterpolation's 'overlay' branch ties its
344
+ // translateX to the SAME range as content's for exactly this reason, so overlayOpacity and
345
+ // overlayTranslateX below share one resolved config.
346
+ const overlayInterpolation = $derived(
347
+ animated ? resolveDrawerSlotInterpolation(geometry, 'overlay') : undefined,
348
+ );
349
+ const overlayStyle = $derived(
350
+ overlayInterpolation === undefined
351
+ ? undefined
352
+ : {
353
+ opacity: progress.interpolate(overlayInterpolation.opacity),
354
+ transform: [
355
+ {
356
+ translateX: progress.interpolate(overlayInterpolation.translateX),
357
+ },
358
+ ],
359
+ },
360
+ );
361
+ const contentStyle = $derived(
362
+ contentTranslateX === undefined
363
+ ? undefined
364
+ : { transform: [{ translateX: contentTranslateX }] },
365
+ );
366
+ const panelStyle = $derived(
367
+ panelTranslateX === undefined
368
+ ? undefined
369
+ : { transform: [{ translateX: panelTranslateX }] },
370
+ );
371
+
372
+ const focusedRoute = $derived(state.routes[state.index]);
373
+ const focusedScreen = $derived.by<
374
+ { scope: INavigationScopeValue; component: Component } | undefined
375
+ >(() => {
376
+ if (focusedRoute === undefined) return undefined;
377
+ const entry = registry.get(focusedRoute.name);
378
+ if (entry === undefined) {
379
+ dlog(
380
+ `Drawer: no screen registered for route name "${focusedRoute.name}"`,
381
+ );
382
+ return undefined;
383
+ }
384
+ return {
385
+ component: entry.component,
386
+ scope: {
387
+ route: focusedRoute,
388
+ navigation: handle,
389
+ emitter: emitterFor(focusedRoute.key),
390
+ parent: parentScope?.current,
391
+ },
392
+ };
393
+ });
394
+
395
+ function resolveDrawerScreenOptions(
396
+ entry: IRegisteredScreen<IDrawerScreenProps['options']>,
397
+ route: IRoute<unknown>,
398
+ ): IDrawerScreenOptions {
399
+ if (typeof entry.options === 'function')
400
+ return entry.options({ route, navigation: handle });
401
+ return entry.options ?? {};
402
+ }
403
+
404
+ const descriptors = $derived.by<IDrawerDescriptorMap>(() => {
405
+ const map: IDrawerDescriptorMap = {};
406
+ for (const route of state.routes) {
407
+ const entry = registry.get(route.name);
408
+ if (entry === undefined) continue;
409
+ map[route.key] = {
410
+ options: resolveDrawerScreenOptions(entry, route),
411
+ navigation: handle,
412
+ };
413
+ }
414
+ return map;
415
+ });
416
+
417
+ const root = $derived.by<IDescriptor>(() =>
418
+ renderDrawer(
419
+ {
420
+ overlayColor: overlayColor ?? DRAWER_DEFAULT_OVERLAY_COLOR,
421
+ drawerStyle,
422
+ contentPassthrough: {},
423
+ overlayPassthrough: animated
424
+ ? {
425
+ pointerEvents: state.isOpen ? 'auto' : 'none',
426
+ onStartShouldSetResponder: () => true,
427
+ onResponderRelease: () => {
428
+ animateProgressTo(false);
429
+ dispatch({ type: 'closeDrawer' });
430
+ },
431
+ }
432
+ : {},
433
+ panelPassthrough: {},
434
+ },
435
+ options,
436
+ ),
437
+ );
438
+
439
+ const order = $derived(drawerChildOrder(options));
440
+
441
+ // renderDrawer emits its slots as root children IN `drawerChildOrder`'s order, so the two zip
442
+ // together - the same index-pairing Vue's own render loop does.
443
+ const slotDescriptors = $derived.by<
444
+ Partial<Record<IDrawerSlot, IDescriptor>>
445
+ >(() => {
446
+ const map: Partial<Record<IDrawerSlot, IDescriptor>> = {};
447
+ order.forEach((slot, index) => {
448
+ const descriptor = root.children[index];
449
+ if (typeof descriptor !== 'string' && descriptor !== undefined)
450
+ map[slot] = descriptor;
451
+ });
452
+ return map;
453
+ });
454
+
455
+ const rootProps = $derived<Record<string, unknown>>({
456
+ style: root.props.style,
457
+ ...panResponder.panHandlers,
458
+ });
459
+ </script>
460
+
461
+ {#snippet screenContent()}
462
+ {#if focusedScreen !== undefined}
463
+ {@const FocusedComponent = focusedScreen.component}
464
+ <NavigationScope value={focusedScreen.scope}>
465
+ <FocusedComponent />
466
+ </NavigationScope>
467
+ {/if}
468
+ {/snippet}
469
+
470
+ {#snippet contentSlot()}
471
+ {@const descriptor = slotDescriptors.content}
472
+ {#if descriptor !== undefined}
473
+ {#if contentStyle !== undefined}
474
+ <Animated.View
475
+ {...descriptor.props}
476
+ style={[descriptor.props.style, contentStyle]}
477
+ >
478
+ {@render screenContent()}
479
+ </Animated.View>
480
+ {:else}
481
+ <symbiote-view p={descriptor.props}>
482
+ {@render screenContent()}
483
+ </symbiote-view>
484
+ {/if}
485
+ {/if}
486
+ {/snippet}
487
+
488
+ {#snippet overlaySlot()}
489
+ {@const descriptor = slotDescriptors.overlay}
490
+ {#if descriptor !== undefined}
491
+ {#if overlayStyle !== undefined}
492
+ <Animated.View
493
+ {...descriptor.props}
494
+ style={[descriptor.props.style, overlayStyle]}
495
+ />
496
+ {:else}
497
+ <symbiote-view p={descriptor.props} />
498
+ {/if}
499
+ {/if}
500
+ {/snippet}
501
+
502
+ {#snippet panelSlot()}
503
+ {@const descriptor = slotDescriptors.panel}
504
+ {#if descriptor !== undefined}
505
+ {#if panelStyle !== undefined}
506
+ <Animated.View
507
+ {...descriptor.props}
508
+ style={[descriptor.props.style, panelStyle]}
509
+ >
510
+ {@render drawerContent?.({
511
+ state,
512
+ descriptors,
513
+ navigation: handle,
514
+ })}
515
+ </Animated.View>
516
+ {:else}
517
+ <symbiote-view p={descriptor.props}>
518
+ {@render drawerContent?.({
519
+ state,
520
+ descriptors,
521
+ navigation: handle,
522
+ })}
523
+ </symbiote-view>
524
+ {/if}
525
+ {/if}
526
+ {/snippet}
527
+
528
+ <symbiote-view p={rootProps}>
529
+ <symbiote-text p={SCREEN_REGISTRY_HOST_PROPS}>
530
+ {@render children?.()}
531
+ </symbiote-text>
532
+ {#each order as slot (slot)}
533
+ {#if slot === 'content'}
534
+ {@render contentSlot()}
535
+ {:else if slot === 'overlay'}
536
+ {@render overlaySlot()}
537
+ {:else}
538
+ {@render panelSlot()}
539
+ {/if}
540
+ {/each}
541
+ </symbiote-view>
542
+
543
+ <!-- --- Explicit gap list vs the real react-native-gesture-handler + react-native-reanimated
544
+ @react-navigation/drawer (confirmed against its current docs, mirrored verbatim from the
545
+ React twin - the gaps are architectural, not adapter-specific) ---
546
+ 1. `configureGestureHandler` - a raw react-native-gesture-handler `Gesture` object escape
547
+ hatch. No PanResponder equivalent exists; not ported.
548
+ 2. Simultaneous/failure gesture RELATIONSHIPS (gesture-handler's declarative composition vs
549
+ a nested ScrollView, another PanResponder, etc.) - PanResponder only offers negotiation
550
+ via the should-set boolean gates used here (edge-start + dominant-axis), which is more
551
+ prone to an accidental hijack of a nested horizontal ScrollView/Swiper than
552
+ gesture-handler's system.
553
+ 3. `useDrawerProgress` - a Reanimated SharedValue read on the UI thread. `progress` here is a
554
+ JS-thread AnimatedValue; interpolating it for consumer-facing content animation works, but
555
+ without native-driver wiring (gap noted above) it does not carry the same synchronous
556
+ UI-thread guarantee under JS-thread load.
557
+ 4. `hideStatusBarOnOpen` / `keyboardDismissMode` / `statusBarAnimation` / `overlayStyle` -
558
+ not wired in this pass; straightforward additions once StatusBar/Keyboard module wiring is
559
+ needed here (not a PanResponder/Animated limitation, just unscoped for v1). -->
@@ -0,0 +1,10 @@
1
+ // `Drawer.Screen` alongside the standalone `DrawerScreen` export, matching React's and Vue's
2
+ // barrels - see ../stack/index.ts for why Object.assign is the right mechanism on a compiled
3
+ // Svelte component.
4
+ import DrawerImpl from './index.svelte';
5
+ import DrawerScreen from '../drawer-screen.svelte';
6
+
7
+ export const Drawer = Object.assign(DrawerImpl, { Screen: DrawerScreen });
8
+
9
+ export type { IDrawerNavigatorHandle, IDrawerDescriptorMap } from '../../core';
10
+ export type { IDrawerContentSlotProps, IDrawerProps } from './drawer-props';
@@ -0,0 +1,26 @@
1
+ // Drawer.Screen's public prop surface - mirrors tab-screen-props.ts (Tab is the closer sibling:
2
+ // both are fixed-route-list, no-push navigators). Split out of `drawer-screen.svelte` for the
3
+ // same tsc-cannot-see-a-`.svelte`-named-export reason screen-props.ts documents.
4
+
5
+ import type { Component } from 'svelte';
6
+ import type {
7
+ IDrawerNavigatorHandle,
8
+ IDrawerScreenOptions,
9
+ IRoute,
10
+ } from '../core';
11
+
12
+ export type IDrawerScreenOptionsArgs = {
13
+ route: IRoute<unknown>;
14
+ navigation: IDrawerNavigatorHandle;
15
+ };
16
+
17
+ export type IDrawerScreenOptionsResolver = (
18
+ args: IDrawerScreenOptionsArgs,
19
+ ) => IDrawerScreenOptions;
20
+
21
+ export type IDrawerScreenProps = {
22
+ name: string;
23
+ component: Component;
24
+ options?: IDrawerScreenOptions | IDrawerScreenOptionsResolver;
25
+ initialParams?: unknown;
26
+ };
@@ -0,0 +1,25 @@
1
+ <script lang="ts">
2
+ // Drawer.Screen: a declarative marker that paints nothing, registered through the same
3
+ // context-based collector Stack.Screen/Tab.Screen use (screen-registry.ts). See CLAUDE.md
4
+ // <third_party_rn_packages_are_react_only> - this and Drawer import nothing from react-native.
5
+ import { collectScreen } from './screen-registry';
6
+ import type { IDrawerScreenProps } from './drawer-screen-props';
7
+
8
+ let { name, component, options, initialParams }: IDrawerScreenProps =
9
+ $props();
10
+
11
+ collectScreen('drawer', {
12
+ get name(): IDrawerScreenProps['name'] {
13
+ return name;
14
+ },
15
+ get component(): IDrawerScreenProps['component'] {
16
+ return component;
17
+ },
18
+ get options(): IDrawerScreenProps['options'] {
19
+ return options;
20
+ },
21
+ get initialParams(): unknown {
22
+ return initialParams;
23
+ },
24
+ });
25
+ </script>