@apexnative/navigation 1.0.0-alpha.2 → 1.0.0-alpha.4

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 (226) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/android/CLAUDE.md +38 -9
  3. package/android/CORE.md +11 -4
  4. package/android/RECIPES.md +9 -3
  5. package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +29 -1
  6. package/android/src/main/java/dev/apexnative/navigation/NavigationApplication.kt +34 -13
  7. package/android/src/main/java/dev/apexnative/navigation/layout/LayoutChecks.kt +5 -1
  8. package/android/src/main/java/dev/apexnative/navigation/layout/LayoutNode.kt +6 -2
  9. package/android/src/main/java/dev/apexnative/navigation/nativescreen/NativeScreenContext.kt +2 -2
  10. package/android/src/main/java/dev/apexnative/navigation/options/ButtonMenuOptions.kt +2 -2
  11. package/android/src/main/java/dev/apexnative/navigation/options/ButtonOptions.kt +2 -2
  12. package/android/src/main/java/dev/apexnative/navigation/options/OptionsCache.kt +8 -4
  13. package/android/src/main/java/dev/apexnative/navigation/options/StatusBarOptions.kt +7 -3
  14. package/android/src/main/java/dev/apexnative/navigation/options/UserChoices.kt +159 -0
  15. package/android/src/main/java/dev/apexnative/navigation/react/CommandAnswer.kt +2 -24
  16. package/android/src/main/java/dev/apexnative/navigation/react/CommandContract.kt +59 -0
  17. package/android/src/main/java/dev/apexnative/navigation/react/Hinge.kt +27 -19
  18. package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +5 -4
  19. package/android/src/main/java/dev/apexnative/navigation/react/PendingCalls.kt +48 -0
  20. package/android/src/main/java/dev/apexnative/navigation/react/events/AppearanceDispatcher.kt +17 -7
  21. package/android/src/main/java/dev/apexnative/navigation/slots/SlotContainers.kt +10 -7
  22. package/android/src/main/java/dev/apexnative/navigation/slots/SlotView.kt +4 -3
  23. package/android/src/main/java/dev/apexnative/navigation/slots/SlotViews.kt +40 -0
  24. package/android/src/main/java/dev/apexnative/navigation/utils/ImageKey.kt +66 -0
  25. package/android/src/main/java/dev/apexnative/navigation/utils/ImageLoader.kt +7 -89
  26. package/android/src/main/java/dev/apexnative/navigation/utils/ImageRequests.kt +76 -0
  27. package/android/src/main/java/dev/apexnative/navigation/utils/OptionImages.kt +262 -0
  28. package/android/src/main/java/dev/apexnative/navigation/utils/ViewController.kt +3 -1
  29. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/SecondaryScreenPresenter.kt +13 -5
  30. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/AppliedTabCalls.kt +49 -0
  31. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +92 -19
  32. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsPresenter.kt +25 -24
  33. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentPresenter.kt +28 -7
  34. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ContentReadiness.kt +1 -1
  35. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/RenderWait.kt +27 -7
  36. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenFrame.kt +1 -1
  37. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalAnimations.kt +22 -24
  38. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +79 -13
  39. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalRecord.kt +21 -6
  40. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +29 -12
  41. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +22 -18
  42. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackState.kt +4 -0
  43. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +45 -6
  44. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt +35 -9
  45. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/overlay/OverlayCoordinator.kt +10 -8
  46. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/Capabilities.kt +11 -4
  47. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ContainerBackground.kt +3 -3
  48. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ParentController.kt +3 -0
  49. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuController.kt +5 -1
  50. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitPanes.kt +23 -0
  51. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitViewController.kt +72 -16
  52. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/BackPreview.kt +287 -0
  53. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackCommands.kt +28 -18
  54. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +164 -13
  55. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackDisplay.kt +26 -1
  56. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +136 -40
  57. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackTransitions.kt +26 -8
  58. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +107 -4
  59. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt +7 -5
  60. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +3 -3
  61. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/MenuChecks.kt +13 -56
  62. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +131 -9
  63. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ViewController.kt +75 -21
  64. package/android/src/main/java/dev/apexnative/navigation/views/animations/Curves.kt +29 -11
  65. package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabs.kt +4 -2
  66. package/android/src/main/java/dev/apexnative/navigation/views/stack/ScreenEdge.kt +15 -0
  67. package/android/src/main/java/dev/apexnative/navigation/views/stack/StackLayout.kt +137 -1
  68. package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/Fab.kt +14 -10
  69. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TitleBar.kt +8 -1
  70. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TopBar.kt +26 -4
  71. package/cli/doctor/checks/app.js +61 -13
  72. package/cli/migrate/native.js +18 -0
  73. package/cli/setup/source.js +2 -1
  74. package/cli/setup/steps/mainApplication.js +134 -19
  75. package/docs-md/architecture/transitions.md +12 -7
  76. package/docs-md/containers/modals.md +4 -2
  77. package/docs-md/containers/overlays.md +3 -1
  78. package/docs-md/containers/side-menu.md +4 -2
  79. package/docs-md/containers/tabs.md +2 -0
  80. package/docs-md/flows/forms-in-a-modal.md +1 -1
  81. package/docs-md/help/glossary.md +2 -2
  82. package/docs-md/help/troubleshooting.md +20 -3
  83. package/docs-md/index.json +12 -0
  84. package/docs-md/migration/from-react-native-navigation.md +8 -2
  85. package/docs-md/screen-ui/back-and-gestures.md +8 -2
  86. package/docs-md/screen-ui/system-ui.md +1 -1
  87. package/docs-md/start/installation.md +3 -1
  88. package/ios/CLAUDE.md +17 -5
  89. package/ios/CORE.md +13 -8
  90. package/ios/Contract/ANNCommandAnswer.mm +5 -20
  91. package/ios/Contract/ANNCommandContract.h +15 -0
  92. package/ios/Contract/ANNCommandContract.mm +78 -0
  93. package/ios/Core/ANNAppearanceDispatcher.mm +1 -1
  94. package/ios/Core/ANNErrorHandler.h +1 -1
  95. package/ios/Core/ANNErrorHandler.mm +3 -1
  96. package/ios/Core/ANNEventEmitter.h +1 -1
  97. package/ios/Core/ANNEventEmitter.mm +2 -2
  98. package/ios/Core/ANNJavaScriptCalls.h +34 -0
  99. package/ios/Core/ANNJavaScriptCalls.mm +61 -0
  100. package/ios/Core/ANNNavigationState.h +7 -3
  101. package/ios/Core/ANNNavigationState.mm +10 -5
  102. package/ios/Core/ANNNodeHelper.h +32 -12
  103. package/ios/Core/ANNNodeHelper.mm +100 -3
  104. package/ios/Core/ANNRenderWait.h +15 -1
  105. package/ios/Core/ANNRenderWait.mm +119 -17
  106. package/ios/Core/UIViewController+LayoutProtocol.h +9 -2
  107. package/ios/Core/UIViewController+LayoutProtocol.mm +8 -0
  108. package/ios/Layouts/Modal/ANNModalCoordinator.h +1 -1
  109. package/ios/Layouts/Modal/ANNModalCoordinator.mm +55 -14
  110. package/ios/Layouts/Screen/ANNComponentViewController.h +8 -0
  111. package/ios/Layouts/Screen/ANNComponentViewController.mm +32 -5
  112. package/ios/Layouts/Screen/ANNScreenPlace.h +41 -0
  113. package/ios/Layouts/Screen/ANNScreenPlace.mm +175 -0
  114. package/ios/Layouts/SplitView/ANNSplitViewCommands.h +3 -2
  115. package/ios/Layouts/SplitView/ANNSplitViewCommands.mm +105 -39
  116. package/ios/Layouts/SplitView/ANNSplitViewController.mm +4 -2
  117. package/ios/Layouts/Stack/ANNStackCommands.mm +1 -0
  118. package/ios/Layouts/Stack/ANNStackController.mm +26 -3
  119. package/ios/Layouts/Stack/ANNStackQueue.h +3 -1
  120. package/ios/Layouts/Stack/ANNStackQueue.mm +56 -43
  121. package/ios/Layouts/Stack/UINavigationController+ANNCommands.h +4 -2
  122. package/ios/Layouts/Stack/UINavigationController+ANNCommands.mm +2 -3
  123. package/ios/Layouts/Tabs/ANNBottomTabItemBuilder.h +4 -0
  124. package/ios/Layouts/Tabs/ANNBottomTabItemBuilder.mm +11 -0
  125. package/ios/Layouts/Tabs/ANNBottomTabsController.mm +13 -5
  126. package/ios/Layouts/Tabs/ANNBottomTabsPresenter.h +2 -2
  127. package/ios/Layouts/Tabs/ANNBottomTabsPresenter.mm +109 -7
  128. package/ios/Options/ANNOptionsCache.h +7 -6
  129. package/ios/Options/ANNSearchBarOptions.h +4 -3
  130. package/ios/Options/ANNUserChoice.h +57 -0
  131. package/ios/Options/ANNUserChoice.mm +44 -0
  132. package/ios/Options/Params/ANNImageSource.h +61 -0
  133. package/ios/Options/Params/ANNImageSource.mm +101 -0
  134. package/ios/Options/Params/ANNOptionImages.h +37 -0
  135. package/ios/Options/Params/ANNOptionImages.mm +284 -0
  136. package/ios/Options/Params/Image.h +11 -2
  137. package/ios/Options/Params/Image.mm +15 -2
  138. package/ios/Options/Params/ImageParser.h +5 -5
  139. package/ios/Options/Params/ImageParser.mm +28 -46
  140. package/ios/Public/ApexNavigation/ApexNavigation.h +1 -1
  141. package/ios/RECIPES.md +11 -5
  142. package/ios/React/Slots/ANNSlotComponentView.mm +24 -17
  143. package/ios/React/Slots/ANNSlotContainers.h +14 -6
  144. package/ios/React/Slots/ANNSlotContainers.mm +75 -2
  145. package/ios/TopBar/ANNBarButtons.h +9 -5
  146. package/ios/TopBar/ANNBarButtons.mm +211 -63
  147. package/ios/TopBar/ANNIconButtonImage.h +1 -0
  148. package/ios/TopBar/ANNIconButtonImage.mm +34 -1
  149. package/ios/TopBar/ANNLargeTitlePresenter.h +13 -0
  150. package/ios/TopBar/ANNLargeTitlePresenter.mm +26 -3
  151. package/ios/TopBar/ANNSearchPresenter.mm +170 -19
  152. package/ios/TopBar/ANNTopBarPresenter.h +5 -0
  153. package/ios/TopBar/ANNTopBarPresenter.mm +60 -11
  154. package/ios/TurboModules/ANNTurboModule.mm +39 -21
  155. package/lib/module/ARCHITECTURE.md +18 -18
  156. package/lib/module/adapters/NativeANNTurboModule.js.map +1 -1
  157. package/lib/module/api/hooks/useNavigation.js +8 -3
  158. package/lib/module/api/hooks/useNavigation.js.map +1 -1
  159. package/lib/module/api/navigation.js.map +1 -1
  160. package/lib/module/engine/commands.js +9 -7
  161. package/lib/module/engine/commands.js.map +1 -1
  162. package/lib/module/engine/layout.js +13 -9
  163. package/lib/module/engine/layout.js.map +1 -1
  164. package/lib/module/engine/options.js +44 -11
  165. package/lib/module/engine/options.js.map +1 -1
  166. package/lib/module/engine/places.js +44 -7
  167. package/lib/module/engine/places.js.map +1 -1
  168. package/lib/module/engine/props.js +30 -1
  169. package/lib/module/engine/props.js.map +1 -1
  170. package/lib/module/engine/rememberedOptions.js +1 -1
  171. package/lib/module/engine/rememberedOptions.js.map +1 -1
  172. package/lib/module/engine/screens.js +22 -12
  173. package/lib/module/engine/screens.js.map +1 -1
  174. package/lib/module/interfaces/contract/commands.json +101 -0
  175. package/lib/typescript/adapters/NativeANNTurboModule.d.ts +5 -2
  176. package/lib/typescript/adapters/NativeANNTurboModule.d.ts.map +1 -1
  177. package/lib/typescript/api/events/navigationEvents.d.ts +1 -1
  178. package/lib/typescript/api/events/navigationEvents.d.ts.map +1 -1
  179. package/lib/typescript/api/hooks/useNavigation.d.ts +1 -1
  180. package/lib/typescript/api/hooks/useNavigation.d.ts.map +1 -1
  181. package/lib/typescript/api/navigation.d.ts +4 -1
  182. package/lib/typescript/api/navigation.d.ts.map +1 -1
  183. package/lib/typescript/engine/commands.d.ts.map +1 -1
  184. package/lib/typescript/engine/options.d.ts +5 -2
  185. package/lib/typescript/engine/options.d.ts.map +1 -1
  186. package/lib/typescript/engine/places.d.ts +5 -2
  187. package/lib/typescript/engine/places.d.ts.map +1 -1
  188. package/lib/typescript/engine/props.d.ts +8 -1
  189. package/lib/typescript/engine/props.d.ts.map +1 -1
  190. package/lib/typescript/engine/rememberedOptions.d.ts.map +1 -1
  191. package/lib/typescript/engine/screens.d.ts.map +1 -1
  192. package/lib/typescript/interfaces/ComponentEvents.d.ts +2 -2
  193. package/lib/typescript/interfaces/ComponentEvents.d.ts.map +1 -1
  194. package/lib/typescript/interfaces/Events.d.ts +19 -10
  195. package/lib/typescript/interfaces/Events.d.ts.map +1 -1
  196. package/lib/typescript/interfaces/options/animations.d.ts +15 -7
  197. package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
  198. package/lib/typescript/interfaces/options/buttons.d.ts +13 -5
  199. package/lib/typescript/interfaces/options/buttons.d.ts.map +1 -1
  200. package/lib/typescript/interfaces/options/modals.d.ts +18 -13
  201. package/lib/typescript/interfaces/options/modals.d.ts.map +1 -1
  202. package/lib/typescript/interfaces/options/system.d.ts +6 -3
  203. package/lib/typescript/interfaces/options/system.d.ts.map +1 -1
  204. package/lib/typescript/interfaces/options/topBar.d.ts +22 -9
  205. package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
  206. package/package.json +1 -1
  207. package/src/ARCHITECTURE.md +18 -18
  208. package/src/adapters/NativeANNTurboModule.ts +5 -2
  209. package/src/api/events/navigationEvents.ts +1 -1
  210. package/src/api/hooks/useNavigation.ts +9 -4
  211. package/src/api/navigation.ts +4 -1
  212. package/src/engine/commands.ts +19 -7
  213. package/src/engine/layout.ts +16 -9
  214. package/src/engine/options.ts +39 -11
  215. package/src/engine/places.ts +56 -7
  216. package/src/engine/props.ts +30 -1
  217. package/src/engine/rememberedOptions.ts +4 -1
  218. package/src/engine/screens.tsx +23 -13
  219. package/src/interfaces/ComponentEvents.ts +2 -2
  220. package/src/interfaces/Events.ts +19 -10
  221. package/src/interfaces/contract/commands.json +101 -0
  222. package/src/interfaces/options/animations.ts +15 -7
  223. package/src/interfaces/options/buttons.ts +13 -5
  224. package/src/interfaces/options/modals.ts +18 -13
  225. package/src/interfaces/options/system.ts +6 -3
  226. package/src/interfaces/options/topBar.ts +22 -9
@@ -46,6 +46,8 @@ navigation.showModal('Player', {
46
46
  });
47
47
  ```
48
48
 
49
+ The top bar of a pushed screen goes with the screen on Android and stays in place on iOS: see [the bar in a transition of your own](#custom-bar).
50
+
49
51
  `presentation` of `createNavigation` gives every push or modal of the app its transition: see [Defaults for the whole app](../fundamentals/moving-between-screens#presentation). New roots have presets of their own: see [How a new root comes in](../fundamentals/switching-roots#transitions).
50
52
 
51
53
  ## Transitions out of a view
@@ -144,7 +146,11 @@ navigation.push('Details', {
144
146
 
145
147
  Details rises 300 points as it fades in. Each property animates from a value to a value over a time, with a delay and an interpolation if it needs them; the [API](#api) lists every one.
146
148
 
147
- The same goes for `pop`, `showModal`, `dismissModal`, `setStackRoot` and `setRoot`. The swipe back is the exception: (iOS) it always follows the finger with the system's slide, so a custom `pop` plays for the back button and `pop()` only.
149
+ The same goes for `pop`, `showModal`, `dismissModal`, `setStackRoot` and `setRoot`. The swipe back is the exception: (iOS) it always follows the finger with the system's slide, so a custom `pop` plays for the back button and `pop()` only. (Android) A pop with an animation of its own shows no preview under the swipe: the screen pops when the swipe ends.
150
+
151
+ ### The top bar
152
+
153
+ The top bar goes its platform's way in a preset or an animation of your own. (Android) It goes with its screen, as if it were part of it: with `slideUp` the new screen comes up with its bar over the old one and its bar, and on the way back it goes down with its bar, uncovering the bar below. With `fade` or `scale` the bar fades or grows with its screen. (iOS) UIKit keeps the bar in place and changes its title and buttons as the transition starts; only the screens under it move.
148
154
 
149
155
  ## Which to pick
150
156
 
@@ -162,11 +168,10 @@ A screen can come in before its React content has drawn, or wait for it. Coming
162
168
 
163
169
  | | By default |
164
170
  | --- | --- |
165
- | (iOS) push | Doesn't wait for the content: the screen slides in with its bar, and the content draws as it comes. It waits only for components in the bar. |
166
- | (Android) push | Doesn't wait. With `appProvider` a React screen is rendered before the push starts, so it comes in drawn from the first frame. A screen with a React root of its own comes in at once, and its content draws as it comes; so does a native screen. The components of the bar draw as the screen comes in. |
171
+ | Push | Waits for the screen's first mount, half a second at most, so it comes in drawn, with what it set as it rendered (a title from `useScreenOptions`) already in its bar. A native screen comes in at once. (iOS) The components of the bar wait too, each to its own limit from the start of the wait. (Android) The components of the bar draw as the screen comes in; with `appProvider` the screen is rendered before the push starts ([below](#wait-one-tree)). |
167
172
  | (Android) modal | Waits for the content and the components of the bar, half a second at most, so the screen comes in drawn. |
168
173
 
169
- `waitForRender` of an animation changes it. `true` waits as long as it takes; `false` comes in at once (a push on Android with `appProvider` [still renders the screen first](#wait-one-tree)):
174
+ `waitForRender` of an animation changes it. `true` waits as long as it takes; `false` comes in at once, the content drawing as it comes (a push on Android with `appProvider` [still renders the screen first](#wait-one-tree)):
170
175
 
171
176
  ```ts
172
177
  navigation.push('Details', {
@@ -174,15 +179,15 @@ navigation.push('Details', {
174
179
  });
175
180
  ```
176
181
 
177
- A screen that draws nothing at first doesn't hold its transition back: it comes in empty, with its bar.
182
+ A screen that draws nothing at first holds its transition back half a second at most, then comes in with its bar.
178
183
 
179
184
  ### A push on Android, in one tree
180
185
 
181
- (Android) With `appProvider` a push doesn't wait for the screen, and the screen still comes in drawn: nothing of it mounts during the transition, which would drop frames. It goes in three steps:
186
+ (Android) With `appProvider` the screen comes in drawn without a wait of its own: nothing of it mounts during the transition, which would drop frames. It goes in three steps:
182
187
 
183
188
  1. The native side lays the screen out in its stack, out of sight, and gives React its size and its safe area.
184
189
  2. React renders the screen at that size with its content, at once.
185
- 3. The push starts in the frame in which that content appears; if it doesn't appear, after half a second.
190
+ 3. The push starts right after that content appears, once what the screen set as it rendered (its `useScreenOptions`) is applied; if it doesn't appear, after half a second.
186
191
 
187
192
  So the push starts later by the time React takes to render the screen, as a push in a native app starts once its screen is built. `waitForRender: false` goes the same way here; `true` waits for the content as long as it takes, and for the components of the bar, half a second at most.
188
193
 
@@ -70,6 +70,8 @@ navigation.showModal('Compose', { style: 'fullScreen' });
70
70
 
71
71
  Without `style`, a modal is a sheet on (iOS) and covers the whole screen on (Android).
72
72
 
73
+ `'pageSheet'` and `'formSheet'` are sheets on both platforms: on (Android) the Material bottom sheet over the dimmed screen, at the full height under the status bar and at most 640 dp wide. `'fullScreen'` and `'overFullScreen'` come up from the bottom over the whole screen; on Android with the system's motion of a window opening over another, without a fade.
74
+
73
75
  `'overFullScreen'` keeps the screen under the modal in place, so it shows through wherever the modal draws nothing: a dialog of your own, a card over a dimmed app. Give the modal's screen `backgroundColor('transparent')` in its options, so its own background doesn't hide the screen under it.
74
76
 
75
77
  ### A form on iPad
@@ -83,7 +85,7 @@ navigation.showModal('EditProfile', {
83
85
  });
84
86
  ```
85
87
 
86
- On iPhone a form sheet is a sheet, and `contentSize` does nothing.
88
+ On iPhone and on Android a form sheet is a sheet, and `contentSize` does nothing.
87
89
 
88
90
  ### A popover
89
91
 
@@ -95,7 +97,7 @@ navigation.showModal('Filters', {
95
97
  });
96
98
  ```
97
99
 
98
- On iPhone it becomes a sheet, unless `adaptive: false` keeps it a popover. (Android) has no popovers: the modal covers the whole screen, as without `style`.
100
+ On iPhone it becomes a sheet, unless `adaptive: false` keeps it a popover. (Android) has no popovers: the modal is the bottom sheet of `'pageSheet'`.
99
101
 
100
102
  ## Sheets
101
103
 
@@ -24,7 +24,9 @@ This page answers:
24
24
 
25
25
  A push or a modal moves the user somewhere. An overlay doesn't move anyone: it puts a screen **on top of everything**, and the app goes on under it.
26
26
 
27
- An overlay is a screen of `screens` like any other: a component, its props, its options. It shows above the tabs, the bars and the modals; on (iOS) it is in a window of its own. Its background is transparent, so the app shows wherever the overlay draws nothing. What overlays are for:
27
+ An overlay is a screen of `screens` like any other: a component, its props, its options. It shows above the tabs, the bars and the modals; on (iOS) it is in a window of its own. Its background is transparent, so the app shows wherever the overlay draws nothing.
28
+
29
+ An overlay covers the whole window, **the status bar included**: a dialog's dimmed backdrop dims the status bar too, as a system dialog does. Its safe area keeps the content off the bars, so a toast at the top of the overlay sits below the status bar. On (Android) a status bar with a color of its own (`statusBar.backgroundColor`) stays over the backdrop, and `statusBar.drawBehind: false` starts the overlay below the status bar. What overlays are for:
28
30
 
29
31
  ## Showing and closing
30
32
 
@@ -74,7 +74,7 @@ A tap on the button opens the menu. A button of the options gets the navigation
74
74
 
75
75
  ## A pick in the menu
76
76
 
77
- A menu is a screen without a stack, so **its commands act on the center**, and the menu closes once the center has changed. A pick usually switches the section, so the center starts over with it:
77
+ A menu is a screen without a stack, so **its commands act on the center**, and the menu closes as the center changes: it starts to slide away at the tap, and a push slides in as it goes. The command resolves once both are done. A pick usually switches the section, so the center starts over with it:
78
78
 
79
79
  ```tsx
80
80
  import { Button, View } from 'react-native';
@@ -94,7 +94,7 @@ function Menu() {
94
94
  }
95
95
  ```
96
96
 
97
- A tap on Settings shows Settings in the center, and the menu closes. Which command to use depends on the way back:
97
+ A tap on Settings closes the menu and shows Settings in the center as it goes. Which command to use depends on the way back:
98
98
 
99
99
  | From the menu | The center |
100
100
  | --- | --- |
@@ -108,6 +108,8 @@ A tap on Settings shows Settings in the center, and the menu closes. Which comma
108
108
  | (iOS) | Over the content, which dims: `openMode: 'aboveContent'` of the side, the default. Or under it, with the content moved aside: `'pushContent'`. Then `animation(…)` says how the menu appears: `'parallax'`, `'slide'`, `'slide-and-scale'` or `'door'`. |
109
109
  | (Android) | The drawer of the platform: over the content, which dims. |
110
110
 
111
+ The menu's color is its screen's `layout.backgroundColor`: it fills the whole height of the menu, behind the status bar and the navigation bar, while the menu's content keeps clear of them. A background on the React content stops at the safe area.
112
+
111
113
  `.ios()` keeps the settings that only iOS has apart from the others:
112
114
 
113
115
  ```ts
@@ -90,6 +90,8 @@ This is the app's `navigation`: its `push` goes to the stack the user sees, now
90
90
 
91
91
  Inbox shows, and Chat comes in over it, in the stack of the Inbox tab.
92
92
 
93
+ On (Android) the content changes with Material's fade through, as in the system's Phone app: the tab that goes fades out, then the new one fades in and grows to its size, in 300 ms; the tab bar stays. On (iOS) the new tab shows at once, as the system's tab bar does.
94
+
93
95
  ### A press on a tab
94
96
 
95
97
  A second tap on the selected tab is a convention of both platforms: **back to the start of the tab**. The navigation does it without code:
@@ -299,7 +299,7 @@ navigation.showModal('EditProfile', {
299
299
  });
300
300
  ```
301
301
 
302
- On iPhone a form sheet is the same sheet as before, and on Android the modal is unchanged.
302
+ On iPhone a form sheet is the same sheet as before. On Android it is the Material bottom sheet, where the modal without a style covered the whole screen.
303
303
 
304
304
  ## The whole code
305
305
 
@@ -266,7 +266,7 @@ Two screens shown as one entry of a stack: the primary, whose bar shows, and the
266
266
 
267
267
  ### Popover
268
268
 
269
- (iPadOS) A modal that points at the bar button or the view that opened it. On iPhone it becomes a sheet; on Android the modal covers the screen. More: [A popover](../containers/modals#popover).
269
+ (iPadOS) A modal that points at the bar button or the view that opened it. On iPhone and on Android it becomes a sheet. More: [A popover](../containers/modals#popover).
270
270
 
271
271
  ### Presentation
272
272
 
@@ -370,7 +370,7 @@ The system bar at the top of the screen, with the time and the battery. Each scr
370
370
 
371
371
  ### Swipe back
372
372
 
373
- (iOS) The swipe from the left edge, and on iOS 26 from the content, that goes back following the finger. `popGesture` narrows it or turns it off. More: [The swipe back](../screen-ui/back-and-gestures#swipe).
373
+ (iOS) The swipe from the left edge, and on iOS 26 from the content, that goes back following the finger. `popGesture` narrows it or turns it off. (Android) 14+ The swipe of the system back shows the pop under the finger, and the user can stop halfway. More: [The swipe back](../screen-ui/back-and-gestures#swipe).
374
374
 
375
375
  ### System back
376
376
 
@@ -73,13 +73,30 @@ Options set in `static options` on a component no longer apply.
73
73
 
74
74
  **Fix:** move them into `defineScreen({ component, options })`; the object carries over as it is. More: [Options next to the screen](../migration/from-react-native-navigation#options).
75
75
 
76
+ ### The Android app crashes at start: React Native isn't loaded
77
+
78
+ (Android) The app closes at once with `NavigationApplication needs React Native loaded first`, and `doctor` reports `MainApplication.kt doesn't call loadReactNative(this) in onCreate`.
79
+
80
+ **Why:** `NavigationApplication` leaves the load of React Native to the template's `loadReactNative(this)`, which loads it as the app's Gradle files say (the New Architecture, edge-to-edge). Without the call nothing loads it.
81
+
82
+ **Fix:** keep the template's `onCreate`, or run `npx apex-navigation setup`, which adds it:
83
+
84
+ ```kotlin
85
+ override fun onCreate() {
86
+ super.onCreate()
87
+ loadReactNative(this)
88
+ }
89
+ ```
90
+
91
+ More: [Android files](../start/installation#android-files).
92
+
76
93
  ### The Android app crashes at start: React Native loads twice
77
94
 
78
- (Android) The app closes at once, and `doctor` reports `MainApplication.kt calls loadReactNative(this), NavigationApplication loads it too`.
95
+ (Android) The app closes at once, and `doctor` reports `MainApplication.kt loads React Native itself … next to loadReactNative(this)`.
79
96
 
80
- **Why:** `NavigationApplication` loads React Native itself, and a second load crashes the app.
97
+ **Why:** the application loads React Native on its own as well, with `SoLoader.init` or the New Architecture's entry point (`load()`, as apps from react-native-navigation have it), and a second load crashes the app.
81
98
 
82
- **Fix:** remove the `loadReactNative(this)` call from `MainApplication.kt`. More: [Android files](../start/installation#android-files).
99
+ **Fix:** keep only `loadReactNative(this)` in `onCreate`; `npx apex-navigation setup` removes the other load. More: [Android files](../start/installation#android-files).
83
100
 
84
101
  ### doctor warns about the JDK
85
102
 
@@ -974,6 +974,10 @@
974
974
  "heading": "The system back of Android",
975
975
  "anchor": "system-back"
976
976
  },
977
+ {
978
+ "heading": "The preview of a pop",
979
+ "anchor": "pop-preview"
980
+ },
977
981
  {
978
982
  "heading": "Back to the home screen",
979
983
  "anchor": "system-animation"
@@ -1323,6 +1327,10 @@
1323
1327
  "heading": "Animations of your own",
1324
1328
  "anchor": "custom"
1325
1329
  },
1330
+ {
1331
+ "heading": "The top bar",
1332
+ "anchor": "custom-bar"
1333
+ },
1326
1334
  {
1327
1335
  "heading": "Which to pick",
1328
1336
  "anchor": "which"
@@ -3944,6 +3952,10 @@
3944
3952
  "heading": "A screen ignores its static options",
3945
3953
  "anchor": "static-options"
3946
3954
  },
3955
+ {
3956
+ "heading": "The Android app crashes at start: React Native isn't loaded",
3957
+ "anchor": "not-loaded"
3958
+ },
3947
3959
  {
3948
3960
  "heading": "The Android app crashes at start: React Native loads twice",
3949
3961
  "anchor": "loads-twice"
@@ -140,7 +140,7 @@ Each piece of `react-native-navigation`, and what takes its place. The [steps](#
140
140
  | `AppRegistry` left out of `index.js` | `AppRegistry.registerComponent(name, Navigation)` of the package. |
141
141
  | `RNNAppDelegate`, `import ReactNativeNavigation` | `ANNAppDelegate`, `import ApexNavigation`. |
142
142
  | `com.reactnativenavigation.*` | `dev.apexnative.navigation.*`. |
143
- | `NavigationReactNativeHost` in `MainApplication` | The template's `reactHost`; `NavigationApplication` loads React Native itself. |
143
+ | `NavigationReactNativeHost` in `MainApplication` | The template's `reactHost`, and its `loadReactNative(this)` in `onCreate`. |
144
144
  | `RNNKotlinVersion` in `android/build.gradle` | Nothing: the library reads `kotlinVersion`. |
145
145
 
146
146
  ## The move, step by step
@@ -212,11 +212,12 @@ import dev.apexnative.navigation.NavigationActivity
212
212
  class MainActivity : NavigationActivity()
213
213
  ```
214
214
 
215
- The application drops `NavigationReactNativeHost` and keeps the `reactHost` of the React Native template. `NavigationApplication` loads React Native in its `onCreate`, so the app doesn't call `loadReactNative` again.
215
+ The application drops `NavigationReactNativeHost` and keeps the `reactHost` of the React Native template. react-native-navigation loaded React Native itself; the navigation leaves it to the template's `loadReactNative(this)` in `onCreate`, which `migrate` adds.
216
216
 
217
217
  ```kotlin
218
218
  import com.facebook.react.PackageList
219
219
  import com.facebook.react.ReactHost
220
+ import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
220
221
  import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
221
222
  import dev.apexnative.navigation.NavigationApplication
222
223
 
@@ -227,6 +228,11 @@ class MainApplication : NavigationApplication() {
227
228
  packageList = PackageList(this).packages,
228
229
  )
229
230
  }
231
+
232
+ override fun onCreate() {
233
+ super.onCreate()
234
+ loadReactNative(this)
235
+ }
230
236
  }
231
237
  ```
232
238
 
@@ -17,7 +17,7 @@ On iOS the back button of Details reads "Inbox". This page answers:
17
17
 
18
18
  1. [The ways back](#ways) on each platform.
19
19
  2. [The back button](#back-button): [its title](#title) and [its look](#look).
20
- 3. [The swipe back](#swipe) of iOS, and [the system back](#system-back) of Android.
20
+ 3. [The swipe back](#swipe) of both platforms, and [the system back](#system-back) of Android.
21
21
  4. [How to keep the user on a screen](#prevent) with unsaved changes.
22
22
 
23
23
  ## The ways back
@@ -85,7 +85,7 @@ export const MapScreen = defineScreen({
85
85
  | `'edge'` | From the edge only: a swipe on the content stays with the content. |
86
86
  | `false` | Off. The back button still works. |
87
87
 
88
- (Android) The system back swipes from either edge; the app doesn't change it.
88
+ (Android) The swipe is the system's back, from either edge, and `popGesture` doesn't apply to it. On Android 14+ it shows the pop before the user lets go: both screens shrink, the screen under comes in behind a dim, and the screen on top follows the finger. The user can stop halfway and stay, as on iOS. On Android 13 and older the screen pops when the swipe ends. When the preview shows and when it doesn't: [the preview of a pop](#pop-preview).
89
89
 
90
90
  ## The system back of Android
91
91
 
@@ -100,6 +100,12 @@ export const MapScreen = defineScreen({
100
100
  | The first screen of a tab | What `Tabs().backBehavior(…)` says: leave the app, go to the first tab, or to the tab before. See [Back on Android](../containers/tabs#back). |
101
101
  | The first screen of the app | Leaves the app, as every Android app does, with the system's own animation ([below](#system-animation)). `usePreventLeave` doesn't hold it; to ask first, see [Asking before the user leaves the app](/api/back-and-gestures/usePreventLeave#leaving-the-app). |
102
102
 
103
+ ### The preview of a pop
104
+
105
+ (Android) 14+ The back gesture on a pushed screen shows where it goes before the user lets go, as the system does between apps' screens: the screen shrinks toward the side of the swipe with rounded corners and follows the finger, and the screen under it shows behind it, dimmed, with its own bar. Letting go pops the screen; swiping back cancels, and the screen grows back into place.
106
+
107
+ The preview shows when back pops the screen with the default transition. It doesn't when back does something else first, or when the app takes it: a menu or a search that closes, a screen that holds back (`hardwareBackButton.popStackOnPress: false`, a [`BackHandler` listener](#back-handler), `usePreventLeave`), or a pop with an animation of its own (`animations.pop`). On Android 13 and older the screen pops when the swipe ends.
108
+
103
109
  ### Back to the home screen
104
110
 
105
111
  (Android) On the first screen of the app, with nothing left to go back to, the navigation leaves the back to the system. The system then plays its predictive back animation: as the user swipes, the app shrinks and the home screen shows behind it, and the user can still change their mind halfway. The system decides this as the swipe starts, so the navigation takes the back only while it has something to do with it: a screen to pop, a modal, a menu or a search to close, a tab to go to (`backBehavior`), or a [`BackHandler` listener](#back-handler) of the app.
@@ -249,7 +249,7 @@ iPhone Duo decides by itself too: folded, the outer display shows the app in por
249
249
 
250
250
  ## Backgrounds
251
251
 
252
- `backgroundColor` colors the screen, behind its React content. Without it, the screen has the background of the [theme](../architecture/theme-and-context).
252
+ `backgroundColor` colors the screen, behind its React content. Without it, the screen has the background of the [theme](../architecture/theme-and-context), or the system's when the theme has none: `systemBackground` on (iOS), `android:colorBackground` of the app's theme on (Android) (a screen in a sheet shows the sheet's surface; a native screen paints its own view). So a React screen is never see-through by accident; `'transparent'` makes it so on purpose, as overlays are.
253
253
 
254
254
  ```ts
255
255
  s.backgroundColor('#F3E9D2');
@@ -126,11 +126,13 @@ In an app as the React Native template makes it, the window shows **one React vi
126
126
 
127
127
  | File | What setup does | Why |
128
128
  | --- | --- | --- |
129
- | `MainApplication.kt` | Extends `NavigationApplication`. | It loads React Native itself and keeps one React host for all screens. Native screens made by `codegen` are registered there, before JS opens them. |
129
+ | `MainApplication.kt` | Extends `NavigationApplication`; keeps the template's `loadReactNative(this)` in `onCreate`, or adds it. | It keeps one React host for all screens, made after `loadReactNative(this)` loaded React Native. Native screens made by `codegen` are registered there, before JS opens them. |
130
130
  | `MainActivity.kt` | Extends `NavigationActivity`. | The activity holds the containers of the navigation instead of one React view. It gets the back press, edge-to-edge and configuration changes first. |
131
131
  | `AndroidManifest.xml` | Adds `android:enableOnBackInvokedCallback="true"` to `<application>`. | On the first screen the system plays its own animation back to the home screen as the user swipes back (predictive back). Android 16 turns it on for apps that target API 36; an app that targets less opts in here. A value the app set itself stays. See [Back to the home screen](../screen-ui/back-and-gestures#system-animation). |
132
132
  | `styles.xml` | `AppTheme` inherits `Theme.ApexNavigation`. | The bars, tabs and search are Material 3 views and take their colors from the app's theme. This one is Material 3 in neutral grays, light and dark; change any color in the same file. A theme of the app's own stays as it is. See [Android's colors](../architecture/theme-and-context#android). |
133
133
 
134
+ The main activity keeps the template's `android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"`. Without `orientation`, `screenSize`, `smallestScreenSize`, `screenLayout` or `uiMode` Android recreates the activity on a rotation, a resize, a fold or a light/dark switch, and every screen goes with it; `doctor` warns about it under Config changes.
135
+
134
136
  #### iOS files
135
137
 
136
138
  | File | What setup does | Why |
package/ios/CLAUDE.md CHANGED
@@ -27,7 +27,7 @@ Swift is not used here: apps reach the core through the public headers (`Public/
27
27
  - `Layouts/SplitView/`: The split view module, plus the core's `ANNSplitViewPresenter` and `ANNSplitViewCommands`.
28
28
  - `Layouts/Arrangement/`: The pair (iOS 27.1): `ANNArrangementViewController`, `ANNArrangementState`.
29
29
  - `TopBar/`: The bar of a screen: title, buttons, back button, large title, appearance, search, toolbar, edge effects.
30
- - `Options/`, `Options/Params`: One `ANN<Group>Options` class per group, the field table, the value kinds and parsers (`Bool`, `Color`, `Text`, `Image`, …).
30
+ - `Options/`, `Options/Params`: One `ANN<Group>Options` class per group, the field table, the value kinds and parsers (`Bool`, `Color`, `Text`, `Image`, …), the images' cache and loader (`ANNOptionImages`).
31
31
  - `Animations/`: The custom transition engine: plan, animator, wait, frame driver, rules; `Elements/` and `Interpolators/`.
32
32
  - `React/`, `React/Slots/`: React views of screens and components, the surface host, the safe area view, the slot host and slot screens.
33
33
  - `Public/ApexNavigation/`: The headers apps import: `ApexNavigation.h`, `ANNAppDelegate.h`, `ANNSceneDelegate.h`.
@@ -42,7 +42,7 @@ Top-level files: `Package.swift` (SwiftPM; `headerFolders` lists every folder wh
42
42
  - Launch, the app-facing API — `Public/ApexNavigation/`; `Core/`: `ApexNavigation.mm`, `ANNAppDelegate.mm`, `ANNSceneDelegate.mm`, `ApexNavigation+Internal.h`
43
43
  - Root, splash, reload — `Layouts/Root/`: `ANNRootCoordinator`, `ANNSplashViewController`; `Core/`: `ANNReloadCoordinator`
44
44
  - Screen: React root, slot, native screen — `Layouts/Screen/`: `ANNComponentViewController`; `React/`: `ANNReactView`, `ANNReactViewCreator`, `Slots/`; `Layouts/External/`
45
- - Screen: insets, background, keyboard focus — `Layouts/Screen/`: `ANNScreenInsets`, `ANNScreenSafeArea`, `ANNScreenBars`, `ANNComponentPresenter` (background, status bar blur), `ANNComponentViewController` (key commands)
45
+ - Screen: insets, background, keyboard focus — `Layouts/Screen/`: `ANNScreenInsets`, `ANNScreenSafeArea`, `ANNScreenBars`, `ANNScreenPlace` (the safe area of a parked screen), `ANNComponentPresenter` (background, status bar blur), `ANNComponentViewController` (key commands)
46
46
  - Safe area view — `React/`: `ANNSafeAreaViewComponentView`; `Layouts/Screen/`: `ANNScreenReadiness`; `cpp/`
47
47
  - Stack: queue, commands, back, gestures — `Layouts/Stack/`: `ANNStackQueue`, `ANNStackCommands`, `ANNStackController`, `InteractivePopGestureDelegate`, `ANNStackAnimatingDelegate`
48
48
  - Top bar: title, buttons, menus, large title — `TopBar/`: `ANNTopBarPresenter`, `ANNBarButtons`, `ANNBarAppearance`, `ANNLargeTitlePresenter`, `ANNBackButtonPresenter`, `ANNTitleView`, `ANNTitleGlassView`
@@ -60,24 +60,31 @@ Top-level files: `Package.swift` (SwiftPM; `headerFolders` lists every folder wh
60
60
  - System bars, orientation, direction — `Core/`: `ANNSystemUI`, `ANNSystemUIRules`, `ANNAppDirection`, `ANNNodePresenter` (the answers to UIKit)
61
61
  - Events and state — `Core/`: `ANNEventEmitter`, `ANNEventContract` (names and keys, made by `scripts/contract.mjs`), `ANNAppearanceDispatcher`, `ANNScreenAppearance`, `ANNNavigationState`; `TurboModules/`: `ANNTurboEventEmitter`
62
62
  - Options: parsing, merging, defaults — `Options/`: `ANNOptions`, `ANNOptionField`, `ANNOptionsReader`, `ANNDefaultOptions`, `ANNOptionsCache`; `Core/`: `ANNOptionsRepaint`
63
- - Error texts — `Contract/`: `ANNErrorTexts` (made from `src/interfaces/contract/errors.json` by `scripts/contract.mjs`); `Core/`: `ANNErrorHandler`
63
+ - Error texts and answers — `Contract/`: `ANNErrorTexts` and `ANNCommandContract` (made from `src/interfaces/contract/errors.json` and `commands.json` by `scripts/contract.mjs`); `Core/`: `ANNErrorHandler`
64
64
  - Links — `Core/`: `ANNDeepLinkQueue`; the `getLaunchLink` method of `ANNTurboModule`
65
65
  - Key commands — `Core/`: `ANNKeyCommands`; `Layouts/Screen/`: `ANNComponentViewController`
66
66
  - Hinge, regions — `Core/`: `ANNHinge`; `Layouts/Screen/`: `ANNScreenRegions`; `Layouts/Arrangement/`: `ANNArrangementState`
67
67
  - Native screens — `Layouts/External/`: `ANNExternalViewController`, `ANNNativeScreenCalls`; `Layouts/Screen/ANNNativeScreenContext.mm`
68
- - Slots — `React/Slots/`: `ANNSlotHost`, `ANNSlotContainers`, `ANNSlotScreenView`, `ANNSlotComponentView`; `cpp/`
68
+ - Slots — `React/Slots/`: `ANNSlotHost`, `ANNSlotContainers` (containers and slot views by slot id: a change goes
69
+ straight to the views of its id), `ANNSlotScreenView`, `ANNSlotComponentView`; `cpp/`
69
70
 
70
71
  ## Threads
71
72
 
72
73
  - **Module queue:** the layout check (`ANNLayoutReader`) and the sync getters. The check reads only stores safe from
73
74
  any thread (`ApexNavigation hasNativeScreenNamed:`, `ANNArrangementState canShowArrangements`).
74
75
  - **Main queue:** everything else. A command body starts with `ANNAssertMainQueue()`. The options of a layout are parsed
75
- here (`ANNLayoutTreeOptions`, images load while they are read). `ANNEventEmitter` moves an event from another queue
76
+ here (`ANNLayoutTreeOptions`; an image keeps only its source). `ANNEventEmitter` moves an event from another queue
76
77
  to the main queue.
78
+ - **The images' queue** (`ANNOptionImages`, `Options/Params`): every option image decodes there, through one cache.
79
+ An element that shows an image asks for it when it applies and waits for it (the first frame needs it); only a tab
80
+ item's new icon on the bar that shows comes later (`ANNBottomTabsPresenter`), the last build of the item winning.
77
81
  - **Sync getters** answer JS during a screen's first render: `getScreenInsets`, `getScreenRegions`,
78
82
  `getArrangementState`, `getHinge`, `getStartInfo`. They read `ANNScreenInsets`, `ANNScreenRegions`,
79
83
  `ANNArrangementState`, `ANNHinge lastValue`, written on the main queue and guarded by `@synchronized`.
80
84
  `ANNReloadCoordinator.generation` and `ANNEventEmitter.isCurrent` are readable from any thread.
85
+ - **After the calls of JavaScript:** `ANNJavaScriptCalls afterCallsMadeSoFar:` runs a step on the main queue after
86
+ every call JavaScript made by then, by the calls' own way (the JavaScript thread, the module's queue, the main
87
+ queue). A push's wait ends through it, so the `mergeOptions` of its screen's first commit come first.
81
88
 
82
89
  ## Checks
83
90
 
@@ -92,6 +99,11 @@ Top-level files: `Package.swift` (SwiftPM; `headerFolders` lists every folder wh
92
99
 
93
100
  - Native code never touches React Native's views (their subviews, their layout, their frames): Fabric owns and recycles
94
101
  them. Read them; what a transition writes (`alpha`, `transform`, a hidden flag) goes back on every path.
102
+ - A screen parked by a wait for its content (`ANNParking`) has no parent, and UIKit gives its view no safe area: it lays
103
+ out with the safe area of its place (`ANNScreenPlace`, measured on a navigation controller of the core's), or its
104
+ list would move as the push starts and UIKit would show the large title only after the transition. On iOS 26 its
105
+ large title also keeps a place of the core's for the push (`arrivesByPushOn:`). Check Showcase 1.4 (with a font
106
+ size), every section panel and 12.6 on push, pop and swipe after any change to waits, parking or the large title.
95
107
  - Every change of a stack's children goes through its `ANNStackQueue`. The core's own calls to UIKit's navigation
96
108
  come only from there; UIKit's own pops are followed (`uikitShows:`), not fought.
97
109
  - Every UIKit present and dismiss in the main window is a step in `ANNPresentationGate`. UIKit drops a transition
package/ios/CORE.md CHANGED
@@ -68,16 +68,20 @@ dispatcher.
68
68
  `+[ANNErrorTexts nowhereToShow]` when there is no window, then runs the body in `@try`. The body finds Home in
69
69
  `ANNNodeRegistry` and calls `+[ANNStackCommands push:componentId:what:answer:]`.
70
70
  4. **`ANNStackCommands`.** Finds the stack by walking `layoutParent`; checks `ANNLayoutValidator` (an id already shown,
71
- what a stack takes); reads the options of the whole tree with `ANNLayoutTreeOptions` (images load here); checks
72
- `ANNLayoutFactory canMakeTrees:`; a node with `preview.nativeID` goes to `ANNPreviewPush`; a `key` of an entry below
73
- goes back to that entry; else `+[ANNLayoutFactory nodeFromLayout:options:]` makes Profile (an
71
+ what a stack takes); reads the options of the whole tree with `ANNLayoutTreeOptions` (an image keeps its source,
72
+ loaded through `ANNOptionImages` when an element shows it); checks `ANNLayoutFactory canMakeTrees:`; a node with
73
+ `preview.nativeID` goes to `ANNPreviewPush`; a `key` of an entry below goes back to that entry; else
74
+ `+[ANNLayoutFactory nodeFromLayout:options:]` makes Profile (an
74
75
  `ANNComponentViewController` with an `ANNComponentPresenter`, content from `ANNReactViewCreator`) and registers it.
75
76
  No view is rendered yet.
76
77
  5. **`-[ANNStackQueue push:answer:…]`.** The node joins `entries` now (every check reads `entries`), the operation is
77
78
  queued, `runNext` starts it when the queue is at rest.
78
79
  6. **`startPush:` and `startPushTransition:`.** By `animations.push.waitForRender` the queue waits through
79
- `ANNRenderWait`, for the content and the bar components or for the bar components alone (a screen that waits for its
80
- content is parked unseen in the stack's view by `ANNParking`, `stackPark:`), then renders the node, drives the appearance of Home and Profile (`+[ANNAppearanceDispatcher driveAppearanceOf:]`), and calls
80
+ `ANNRenderWait`: unset, for the content at most 0.5 s, with `true` as long as it takes, then for the bar components
81
+ (their limits from the wait's start when unset, from the content's end with `true`; the screen is parked unseen in
82
+ the stack's view by `ANNParking`, `stackPark:`); the content's end, content there already too, waits for the calls
83
+ JavaScript made by then (`ANNJavaScriptCalls`: the JavaScript thread, the module's queue, the main queue), so the
84
+ options of the screen's first commit are in the bar; with `false` nothing. Then it renders the node, drives the appearance of Home and Profile (`+[ANNAppearanceDispatcher driveAppearanceOf:]`), and calls
81
85
  `+[ANNAppearanceDispatcher beginTransitionShowing:]`. Profile's `ComponentWillAppear` is sent now (held until its
82
86
  content mounted, 0.5 s at most). `stackChange:` pushes in UIKit; with an own animation `ANNTransitionPlan` and
83
87
  `ANNStackAnimatingDelegate` give UIKit the `ANNTransitionAnimator`.
@@ -85,7 +89,7 @@ dispatcher.
85
89
  `+[ANNAppearanceDispatcher endTransitionHiding:showing:events:answer:value:]`. In this order: `ComponentDidDisappear`
86
90
  of Home, `ComponentDidAppear` of Profile, `ANN.NavigationStateChanged` (`+[ANNNavigationState updateNow]`), the
87
91
  transition's `events`, then `-[ANNCommandAnswer resolve:]`: the state again if it changed
88
- (`reportBeforeAnswer`), the promise with Profile's id, then `ANN.CommandCompleted`. `finish:` frees the queue for
92
+ (`reportBeforeAnswerOf:`), the promise with Profile's id, then `ANN.CommandCompleted`. `finish:` frees the queue for
89
93
  the next operation.
90
94
 
91
95
  Events go out through `ANNEventEmitter` on the main queue and reach JS through `ANNTurboEventEmitter`.
@@ -116,13 +120,14 @@ Each of these owns one rule. Call it; do not copy it.
116
120
  - `ANNCommandAnswer`: The answer: state, then promise, then `ANN.CommandCompleted`; once; silent for a JavaScript that went. Called by: Every coordinator and queue.
117
121
  - `ANNNodeRegistry`: Every node by id, weakly. Filled by the factory, cleared at a reload. Called by: Commands, modules, `findViewController:`.
118
122
  - `ANNLayoutFactory`: The core code that names module classes (`ANNSplitViewController`, `ANNSideMenuViewController`, `ANNArrangementViewController`); `ANNSideMenuPresenter` is the one other file that does. One maker per layout type; registers each node. Called by: Every command that shows a layout.
119
- - `ANNOptionsCache`: The one invalidation point of resolved options. Called by: The setter of `ANNNodeHelper.options` and `ANNDefaultOptions` only.
120
- - `ANNNodeHelper resolveOptionsWithDefault`: What a node shows with: defaults, parents' own options, the node's `resolveOptions` (own, then the current child down). Cached, read-only. Called by: Presenters, coordinators.
123
+ - `ANNOptionsCache`: The one invalidation point of resolved options. Called by: The setters of `ANNNodeHelper.options` and of its user layer (`setUserChoice:forKey:`), and `ANNDefaultOptions`, only.
124
+ - `ANNNodeHelper resolveOptionsWithDefault`: What a node shows with: defaults, parents' own options (each with its user layer), the node's `resolveOptions` (own, its user layer, then the current child down). Cached, read-only. Called by: Presenters, coordinators.
121
125
  - `ANNAppearanceDispatcher`: The only sender of `ComponentWillAppear`, `ComponentDidAppear`, `ComponentDidDisappear`; the end-of-transition order; the events of a node that goes. Called by: Screens, queues, coordinators.
122
126
  - `ANNNavigationState`: The visible screen and the nearest tabs it is in (the last tabs met walking down); coalesced updates, holds, the state before an answer. Called by: The dispatcher, `ANNCommandAnswer`, `ANNRootCoordinator`.
123
127
  - `ANNScreenReadiness`: The signals of a screen's safe area view (has one, laid out, content rendered), sticky, by screen id. Called by: `ANNSafeAreaViewComponentView`, `ANNReactView`.
124
128
  - `ANNRenderWait`: The one wait for the render of what a command shows: content, bar components, limits, the unknown-content rule. Called by: Root, stack queue, modals, overlays, `showInColumn`.
125
129
  - `ANNParking`: Where a view waits for its content unseen: the window, a stack's view, a preview's host view. Called by: Root, stack, modals, overlays, `ANNPreviewPush`.
130
+ - `ANNScreenPlace`: The safe area a parked screen lays out with, measured where it will show on top of a stack. Called by: `ANNStackController` (`stackPark:`), `ANNSplitViewCommands` (`showInColumn`), through `placeWhileParked` of the screen.
126
131
  - `ANNPendingLayers`: Layers that wait to show (a pending root, a modal, an overlay); a node inside counts as off screen. Called by: The layer's owner; nodes ask.
127
132
  - `ANNShownScreens`: "What a node shows changed": tells the pending layer, the modal hold and the system UI, in that order. Called by: Every container that changes what it shows.
128
133
  - `ANNModalCoordinator`, `ANNModal`: One record per modal, its states and waiting commands. Called by: Commands, root, state, repaint, reload.
@@ -1,26 +1,12 @@
1
1
  #import "ANNCommandAnswer.h"
2
2
  #import "ANNAssert.h"
3
+ #import "ANNCommandContract.h"
3
4
  #import "ANNErrorHandler.h"
4
5
  #import "ANNErrorTexts.h"
5
6
  #import "ANNNavigationState.h"
6
7
  #import "ANNReloadCoordinator.h"
7
8
  #import "ApexNavigation+Internal.h"
8
9
 
9
- // The commands that send `ANN.CommandCompleted` once they resolved: every one that shows or dismisses, not
10
- // `setColumnShown`, `preparePush` or `getLaunchLink`.
11
- static NSSet<NSString *> *ANNCompletedCommandNames(void) {
12
- static NSSet<NSString *> *names;
13
- static dispatch_once_t once;
14
- dispatch_once(&once, ^{
15
- names = [NSSet setWithArray:@[
16
- @"setRoot", @"push", @"pop", @"popTo", @"popToRoot", @"setStackRoot", @"showInColumn",
17
- @"showModal", @"dismissModal", @"dismissAllModals", @"showOverlay", @"dismissOverlay",
18
- @"dismissAllOverlays"
19
- ]];
20
- });
21
- return names;
22
- }
23
-
24
10
  @implementation ANNCommandAnswer {
25
11
  RCTPromiseResolveBlock _resolve;
26
12
  RCTPromiseRejectBlock _reject;
@@ -72,14 +58,13 @@ static NSSet<NSString *> *ANNCompletedCommandNames(void) {
72
58
  RCTPromiseResolveBlock resolve = _resolve;
73
59
  _resolve = nil;
74
60
  _reject = nil;
75
- // A command that may change what shows (the 13 and `setColumnShown`): the state goes before the promise, so that
61
+ // A command that may change what shows (`ANNCommandContract`): the state goes before the promise, so that
76
62
  // `getState()` after `await` has the result.
77
- BOOL changesWhatShows = [ANNCompletedCommandNames() containsObject:_commandName] ||
78
- [_commandName isEqualToString:@"setColumnShown"];
63
+ BOOL changesWhatShows = ANNCommandReportsState(_commandName);
79
64
  if (changesWhatShows) {
80
65
  // The command did its work: a failure of the state step costs only the state, never the answer.
81
66
  @try {
82
- [ANNNavigationState reportBeforeAnswer];
67
+ [ANNNavigationState reportBeforeAnswerOf:_commandName];
83
68
  } @catch (NSException *exception) {
84
69
  NSString *reason = exception.reason.length > 0 ? exception.reason : exception.name;
85
70
  [ANNErrorHandler logFailureOf:_commandName
@@ -88,7 +73,7 @@ static NSSet<NSString *> *ANNCompletedCommandNames(void) {
88
73
  }
89
74
  }
90
75
  [ANNErrorHandler resolve:resolve of:_commandName value:value];
91
- if (_commandId && [ANNCompletedCommandNames() containsObject:_commandName]) {
76
+ if (_commandId && ANNCommandSendsCompleted(_commandName)) {
92
77
  double completionTime = floor(NSDate.date.timeIntervalSince1970 * 1000);
93
78
  [ApexNavigation.eventEmitter sendCommandCompleted:_commandName
94
79
  commandId:_commandId
@@ -0,0 +1,15 @@
1
+ // Made by scripts/contract.mjs from src/interfaces/contract/commands.json: change that file and run the script again.
2
+
3
+ #import <Foundation/Foundation.h>
4
+
5
+ NS_ASSUME_NONNULL_BEGIN
6
+
7
+ /// Whether the navigation state goes before the answer of the command `name`: every command that may change what
8
+ /// shows. Any thread.
9
+ FOUNDATION_EXPORT BOOL ANNCommandReportsState(NSString *name);
10
+
11
+ /// Whether `ANN.CommandCompleted` follows the answer of the command `name`: every command that shows or dismisses.
12
+ /// Any thread.
13
+ FOUNDATION_EXPORT BOOL ANNCommandSendsCompleted(NSString *name);
14
+
15
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,78 @@
1
+ // Made by scripts/contract.mjs from src/interfaces/contract/commands.json: change that file and run the script again.
2
+
3
+ #import "ANNCommandContract.h"
4
+
5
+ // What each command resolves with:
6
+ // - `setRoot`: the id of the root, once the root and the layout's modals and overlays show.
7
+ // - `push`: the id of the screen that shows; of a pair, the pair's id.
8
+ // - `preparePush`: the frame `{ width, height, padding }` of the prepared screen, or null; always
9
+ // null on iOS.
10
+ // - `pop`: the id of the screen that went.
11
+ // - `popTo`: the id of the screen it went back to.
12
+ // - `popToRoot`: the id of the first screen of the stack.
13
+ // - `setStackRoot`: the id of the new top screen.
14
+ // - `showModal`: the id of the modal's root.
15
+ // - `dismissModal`: the id of the modal's root.
16
+ // - `dismissAllModals`: an empty string.
17
+ // - `showOverlay`: the id of the overlay's root.
18
+ // - `dismissOverlay`: the id of the overlay's root.
19
+ // - `dismissAllOverlays`: an empty string.
20
+ // - `showInColumn`: the id of the screen that shows.
21
+ // - `setColumnShown`: nothing once the columns stopped moving (`undefined` on iOS, `null` on
22
+ // Android).
23
+ // - `getLaunchLink`: the link that launched the app, or null.
24
+
25
+ static NSSet<NSString *> *ANNStateCommandNames(void) {
26
+ static NSSet<NSString *> *names;
27
+ static dispatch_once_t once;
28
+ dispatch_once(&once, ^{
29
+ names = [NSSet setWithArray:@[
30
+ @"setRoot",
31
+ @"push",
32
+ @"pop",
33
+ @"popTo",
34
+ @"popToRoot",
35
+ @"setStackRoot",
36
+ @"showModal",
37
+ @"dismissModal",
38
+ @"dismissAllModals",
39
+ @"showOverlay",
40
+ @"dismissOverlay",
41
+ @"dismissAllOverlays",
42
+ @"showInColumn",
43
+ @"setColumnShown",
44
+ ]];
45
+ });
46
+ return names;
47
+ }
48
+
49
+ static NSSet<NSString *> *ANNCompletedCommandNames(void) {
50
+ static NSSet<NSString *> *names;
51
+ static dispatch_once_t once;
52
+ dispatch_once(&once, ^{
53
+ names = [NSSet setWithArray:@[
54
+ @"setRoot",
55
+ @"push",
56
+ @"pop",
57
+ @"popTo",
58
+ @"popToRoot",
59
+ @"setStackRoot",
60
+ @"showModal",
61
+ @"dismissModal",
62
+ @"dismissAllModals",
63
+ @"showOverlay",
64
+ @"dismissOverlay",
65
+ @"dismissAllOverlays",
66
+ @"showInColumn",
67
+ ]];
68
+ });
69
+ return names;
70
+ }
71
+
72
+ BOOL ANNCommandReportsState(NSString *name) {
73
+ return [ANNStateCommandNames() containsObject:name];
74
+ }
75
+
76
+ BOOL ANNCommandSendsCompleted(NSString *name) {
77
+ return [ANNCompletedCommandNames() containsObject:name];
78
+ }
@@ -305,7 +305,7 @@ static BOOL ANNIsInsideNode(UIViewController *screen, UIViewController *node) {
305
305
  for (UIViewController *node in shown) {
306
306
  [self nodeDidAppear:node];
307
307
  }
308
- [ANNNavigationState updateNow];
308
+ [ANNNavigationState updateNowFor:answer.commandName];
309
309
  if (events) {
310
310
  events();
311
311
  }