@apexnative/navigation 1.0.0-alpha.5 → 1.0.0-alpha.7

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 (358) hide show
  1. package/ApexNavigation.podspec +2 -1
  2. package/CHANGELOG.md +32 -0
  3. package/README.md +1 -1
  4. package/android/CLAUDE.md +31 -20
  5. package/android/CORE.md +12 -8
  6. package/android/consumer-rules.pro +6 -0
  7. package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +36 -5
  8. package/android/src/main/java/dev/apexnative/navigation/options/BottomTabOptions.kt +1 -1
  9. package/android/src/main/java/dev/apexnative/navigation/options/BottomTabsOptions.kt +13 -8
  10. package/android/src/main/java/dev/apexnative/navigation/options/ButtonOptions.kt +1 -1
  11. package/android/src/main/java/dev/apexnative/navigation/options/LargeTitleOptions.kt +1 -1
  12. package/android/src/main/java/dev/apexnative/navigation/options/LayoutAnimation.kt +1 -1
  13. package/android/src/main/java/dev/apexnative/navigation/options/OptionKey.kt +18 -0
  14. package/android/src/main/java/dev/apexnative/navigation/options/OptionPaths.kt +91 -0
  15. package/android/src/main/java/dev/apexnative/navigation/options/SheetOptions.kt +7 -4
  16. package/android/src/main/java/dev/apexnative/navigation/options/TitleOptions.kt +2 -2
  17. package/android/src/main/java/dev/apexnative/navigation/options/TopBarAppearanceOptions.kt +1 -1
  18. package/android/src/main/java/dev/apexnative/navigation/options/TopBarOptions.kt +16 -7
  19. package/android/src/main/java/dev/apexnative/navigation/react/ContentHeights.kt +95 -0
  20. package/android/src/main/java/dev/apexnative/navigation/react/ContentMounts.kt +66 -0
  21. package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +7 -3
  22. package/android/src/main/java/dev/apexnative/navigation/react/OptionsCommands.kt +41 -3
  23. package/android/src/main/java/dev/apexnative/navigation/react/ReactView.kt +16 -1
  24. package/android/src/main/java/dev/apexnative/navigation/react/ReloadCoordinator.kt +0 -2
  25. package/android/src/main/java/dev/apexnative/navigation/react/ScreenEnvironment.kt +177 -0
  26. package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +12 -49
  27. package/android/src/main/java/dev/apexnative/navigation/react/ScrollEvents.kt +49 -128
  28. package/android/src/main/java/dev/apexnative/navigation/safearea/SafeAreaView.kt +6 -6
  29. package/android/src/main/java/dev/apexnative/navigation/slots/SlotScreenView.kt +27 -7
  30. package/android/src/main/java/dev/apexnative/navigation/slots/SlotView.kt +12 -1
  31. package/android/src/main/java/dev/apexnative/navigation/utils/MaterialTheme.kt +5 -6
  32. package/android/src/main/java/dev/apexnative/navigation/utils/SystemAnimations.kt +36 -0
  33. package/android/src/main/java/dev/apexnative/navigation/utils/ViewExtensions.kt +0 -13
  34. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/ArrangementController.kt +9 -7
  35. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsCollapseBehavior.kt +5 -3
  36. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +13 -10
  37. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/CustomTabRowGeometry.kt +22 -0
  38. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/TabsRoom.kt +0 -5
  39. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentViewController.kt +7 -8
  40. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenSafeArea.kt +29 -11
  41. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/externalcomponent/ExternalComponentViewController.kt +2 -6
  42. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +20 -2
  43. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +443 -58
  44. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +33 -5
  45. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetBack.kt +42 -0
  46. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetStops.kt +60 -2
  47. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +4 -0
  48. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ParentController.kt +3 -6
  49. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuMotion.kt +8 -2
  50. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/BackPreview.kt +32 -4
  51. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/PreparedFrame.kt +3 -3
  52. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackCommands.kt +2 -2
  53. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +50 -7
  54. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +15 -3
  55. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +10 -4
  56. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabMotion.kt +142 -0
  57. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt +215 -43
  58. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +25 -26
  59. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/BarRowTransition.kt +3 -2
  60. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/ButtonPlaces.kt +11 -5
  61. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +36 -38
  62. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ScrollEventListener.kt +6 -9
  63. package/android/src/main/java/dev/apexnative/navigation/views/animations/AnimationLayer.kt +73 -0
  64. package/android/src/main/java/dev/apexnative/navigation/views/animations/ValueInterpolation.kt +14 -0
  65. package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabsLayout.kt +61 -2
  66. package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/CustomTabRow.kt +72 -10
  67. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +129 -179
  68. package/android/src/main/java/dev/apexnative/navigation/views/element/TargetsWait.kt +8 -1
  69. package/android/src/main/java/dev/apexnative/navigation/views/element/TransitionLayer.kt +37 -112
  70. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +67 -3
  71. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt +346 -36
  72. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMorph.kt +199 -0
  73. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +55 -42
  74. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/TextMorph.kt +176 -0
  75. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewMorph.kt +108 -0
  76. package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabCopy.kt +85 -0
  77. package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabMenu.kt +13 -0
  78. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TitleBar.kt +5 -24
  79. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TopBar.kt +51 -34
  80. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonComponentCell.kt +6 -6
  81. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonView.kt +113 -0
  82. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarMenu.kt +219 -0
  83. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonBar.kt +121 -175
  84. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonMenus.kt +34 -64
  85. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonRow.kt +216 -0
  86. package/android/src/main/jni/ANNContentHeights.cpp +82 -0
  87. package/android/src/main/res/drawable/apex_navigation_ic_arrow_right.xml +14 -0
  88. package/android/src/main/res/layout/apex_navigation_menu_header.xml +19 -0
  89. package/android/src/main/res/layout/apex_navigation_menu_item.xml +68 -0
  90. package/android/src/main/res/values/styles.xml +1 -1
  91. package/android/src/main/res/values-night/styles.xml +1 -1
  92. package/cli/doctor/checks/app.js +16 -1
  93. package/cli/project.js +4 -0
  94. package/cli/setup/index.js +5 -0
  95. package/cli/setup/steps/metro.js +46 -0
  96. package/docs-md/architecture/native-screens.md +4 -0
  97. package/docs-md/architecture/theme-and-context.md +131 -12
  98. package/docs-md/architecture/transitions.md +46 -5
  99. package/docs-md/containers/modals.md +2 -0
  100. package/docs-md/containers/overlays.md +1 -1
  101. package/docs-md/containers/side-menu.md +1 -1
  102. package/docs-md/containers/tabs.md +2 -0
  103. package/docs-md/flows/bottom-sheets.md +4 -2
  104. package/docs-md/fundamentals/screens-and-props.md +9 -1
  105. package/docs-md/fundamentals/starting-the-app.md +1 -1
  106. package/docs-md/guides/cli.md +3 -1
  107. package/docs-md/help/glossary.md +2 -2
  108. package/docs-md/help/troubleshooting.md +22 -1
  109. package/docs-md/index.json +41 -1
  110. package/docs-md/migration/from-expo-router.md +1 -1
  111. package/docs-md/migration/from-react-native-navigation.md +1 -1
  112. package/docs-md/migration/from-react-navigation.md +2 -2
  113. package/docs-md/screen-ui/back-and-gestures.md +17 -1
  114. package/docs-md/screen-ui/bar-buttons.md +5 -5
  115. package/docs-md/screen-ui/search.md +1 -1
  116. package/docs-md/screen-ui/system-ui.md +16 -11
  117. package/docs-md/screen-ui/toolbar-and-fab.md +4 -2
  118. package/docs-md/screen-ui/top-bar.md +8 -4
  119. package/docs-md/start/installation.md +16 -1
  120. package/ios/Animations/ANNFrameDriver.h +55 -15
  121. package/ios/Animations/ANNFrameDriver.mm +216 -60
  122. package/ios/Animations/ANNTransitionAnimator.h +26 -5
  123. package/ios/Animations/ANNTransitionAnimator.mm +49 -3
  124. package/ios/Animations/ANNTransitionPlan.h +2 -2
  125. package/ios/Animations/ANNTransitionPlan.mm +10 -0
  126. package/ios/Animations/Elements/ANNElementFinder.h +2 -2
  127. package/ios/Animations/Elements/ANNElementFinder.mm +2 -2
  128. package/ios/Animations/Elements/ANNElementTransition.h +3 -3
  129. package/ios/Animations/Elements/ANNElementTransition.mm +2 -2
  130. package/ios/Animations/Elements/ANNSharedElementFlight.h +3 -3
  131. package/ios/Animations/Elements/ANNSharedElementFlight.mm +249 -45
  132. package/ios/Animations/Interpolators/ANNInterpolate.h +2 -30
  133. package/ios/Animations/Interpolators/ANNTransformParts.h +48 -0
  134. package/ios/Animations/Interpolators/ANNTransformParts.mm +507 -0
  135. package/ios/Animations/Interpolators/ANNViewTracks.h +9 -6
  136. package/ios/Animations/Interpolators/ANNViewTracks.mm +100 -30
  137. package/ios/CLAUDE.md +32 -22
  138. package/ios/CORE.md +25 -21
  139. package/ios/Contract/ANNCommandRunner.h +8 -7
  140. package/ios/Contract/ANNCommandRunner.mm +21 -27
  141. package/ios/Contract/ANNLayoutNode.h +3 -3
  142. package/ios/Contract/ANNLayoutReader.h +3 -3
  143. package/ios/Core/ANNAppDelegate.mm +0 -2
  144. package/ios/Core/ANNDeepLinkQueue.h +6 -5
  145. package/ios/Core/ANNDeepLinkQueue.mm +22 -10
  146. package/ios/Core/ANNJavaScriptCalls.h +11 -12
  147. package/ios/Core/ANNJavaScriptCalls.mm +7 -16
  148. package/ios/Core/ANNReloadCoordinator.mm +4 -8
  149. package/ios/Core/ANNSceneDelegate.mm +2 -2
  150. package/ios/Core/ApexNavigation+Internal.h +1 -1
  151. package/ios/Core/ApexNavigation.mm +6 -2
  152. package/ios/Layouts/Arrangement/ANNArrangementState.h +2 -17
  153. package/ios/Layouts/Arrangement/ANNArrangementState.mm +0 -38
  154. package/ios/Layouts/Arrangement/ANNArrangementViewController.mm +4 -4
  155. package/ios/Layouts/Root/ANNRootCoordinator.mm +45 -15
  156. package/ios/Layouts/Screen/ANNComponentViewController.h +2 -2
  157. package/ios/Layouts/Screen/ANNComponentViewController.mm +13 -23
  158. package/ios/Layouts/Screen/ANNPreviewInteraction.h +7 -4
  159. package/ios/Layouts/Screen/ANNPreviewInteraction.mm +72 -10
  160. package/ios/Layouts/Screen/ANNScreenEnvironment.h +63 -0
  161. package/ios/Layouts/Screen/ANNScreenEnvironment.mm +181 -0
  162. package/ios/Layouts/Screen/ANNScreenRegions.h +3 -18
  163. package/ios/Layouts/Screen/ANNScreenRegions.mm +0 -38
  164. package/ios/Layouts/Screen/ANNScreenSafeArea.h +4 -9
  165. package/ios/Layouts/Screen/ANNScreenSafeArea.mm +14 -16
  166. package/ios/Layouts/Stack/ANNPreviewPush.h +7 -6
  167. package/ios/Layouts/Stack/ANNPreviewPush.mm +57 -5
  168. package/ios/Layouts/Stack/ANNStackAnimatingDelegate.h +11 -2
  169. package/ios/Layouts/Stack/ANNStackAnimatingDelegate.mm +14 -7
  170. package/ios/Layouts/Stack/ANNStackController.h +8 -0
  171. package/ios/Layouts/Stack/ANNStackController.mm +153 -11
  172. package/ios/Layouts/Stack/ANNSwipePopInteraction.h +26 -0
  173. package/ios/Layouts/Stack/ANNSwipePopInteraction.mm +185 -0
  174. package/ios/Layouts/Stack/InteractivePopGestureDelegate.mm +28 -4
  175. package/ios/Layouts/Tabs/ANNBottomTabsAccessoryView.h +12 -1
  176. package/ios/Layouts/Tabs/ANNBottomTabsAccessoryView.mm +23 -0
  177. package/ios/Layouts/Tabs/ANNBottomTabsController.mm +2 -1
  178. package/ios/Layouts/Tabs/ANNBottomTabsCustomRow.mm +8 -3
  179. package/ios/Layouts/Tabs/ANNBottomTabsPresenter.h +4 -0
  180. package/ios/Layouts/Tabs/ANNBottomTabsPresenter.mm +123 -36
  181. package/ios/Layouts/Tabs/ANNTabsDesign.h +6 -5
  182. package/ios/Layouts/Tabs/ANNTabsDesign.mm +9 -1
  183. package/ios/Layouts/Tabs/UITabBar+utils.h +5 -5
  184. package/ios/Layouts/Tabs/UITabBar+utils.mm +32 -13
  185. package/ios/Options/ANNAnimationOptions.h +2 -1
  186. package/ios/Options/ANNAnimationOptions.mm +1 -1
  187. package/ios/Options/ANNBottomTabsOptions.h +2 -0
  188. package/ios/Options/ANNBottomTabsOptions.mm +1 -0
  189. package/ios/Options/ANNOptions.h +25 -0
  190. package/ios/Options/ANNOptions.mm +157 -1
  191. package/ios/Options/ANNOptionsReader.mm +108 -0
  192. package/ios/Options/ANNStackPopAnimationOptions.h +13 -0
  193. package/ios/Options/ANNStackPopAnimationOptions.mm +11 -0
  194. package/ios/Options/Params/ANNParam.h +15 -0
  195. package/ios/Options/Params/ANNParam.mm +61 -0
  196. package/ios/Options/Params/Color.h +3 -0
  197. package/ios/Options/Params/Color.mm +21 -0
  198. package/ios/Options/Params/ColorParser.h +4 -3
  199. package/ios/Options/Params/ColorParser.mm +22 -4
  200. package/ios/Options/Params/Image.h +4 -0
  201. package/ios/Options/Params/Image.mm +20 -0
  202. package/ios/React/ANNReactView.mm +6 -25
  203. package/ios/React/ANNSafeAreaViewComponentView.mm +26 -15
  204. package/ios/React/ANNSurfaceHostingView.h +14 -0
  205. package/ios/React/ANNSurfaceHostingView.mm +144 -1
  206. package/ios/React/Slots/ANNSlotScreenView.mm +8 -2
  207. package/ios/TopBar/ANNBackButtonPresenter.mm +6 -5
  208. package/ios/TopBar/ANNBarAppearance.h +2 -1
  209. package/ios/TopBar/ANNBarAppearance.mm +111 -30
  210. package/ios/TopBar/ANNBarBackgroundComponent.mm +16 -0
  211. package/ios/TopBar/ANNBarButtonComponentView.h +4 -4
  212. package/ios/TopBar/ANNBarButtonComponentView.mm +1 -1
  213. package/ios/TopBar/ANNBarButtons.h +5 -3
  214. package/ios/TopBar/ANNBarButtons.mm +186 -17
  215. package/ios/TopBar/ANNLargeTitlePresenter.mm +24 -4
  216. package/ios/TopBar/ANNMainList.h +3 -0
  217. package/ios/TopBar/ANNMainList.mm +4 -0
  218. package/ios/TopBar/ANNScrollEdgeEffects.h +22 -6
  219. package/ios/TopBar/ANNScrollEdgeEffects.mm +123 -16
  220. package/ios/TopBar/ANNSearchPresenter.mm +2 -2
  221. package/ios/TopBar/ANNSearchResultsViewController.mm +5 -5
  222. package/ios/TopBar/ANNTopBarPresenter.h +3 -2
  223. package/ios/TopBar/ANNTopBarPresenter.mm +95 -22
  224. package/ios/TurboModules/ANNTurboModule.mm +66 -16
  225. package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.cpp +20 -0
  226. package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.h +5 -14
  227. package/lib/module/ARCHITECTURE.md +17 -7
  228. package/lib/module/adapters/NativeANNTurboModule.js.map +1 -1
  229. package/lib/module/api/app/AppRegistry.js +14 -2
  230. package/lib/module/api/app/AppRegistry.js.map +1 -1
  231. package/lib/module/api/app/createNavigation.js +188 -66
  232. package/lib/module/api/app/createNavigation.js.map +1 -1
  233. package/lib/module/api/events/navigationEvents.js +6 -2
  234. package/lib/module/api/events/navigationEvents.js.map +1 -1
  235. package/lib/module/api/hooks/useSafeAreaInsets.js +4 -3
  236. package/lib/module/api/hooks/useSafeAreaInsets.js.map +1 -1
  237. package/lib/module/api/linking/linking.js +11 -0
  238. package/lib/module/api/linking/linking.js.map +1 -1
  239. package/lib/module/api/options/OptionsChain.js +2 -2
  240. package/lib/module/api/screens/defineScreen.js +45 -3
  241. package/lib/module/api/screens/defineScreen.js.map +1 -1
  242. package/lib/module/api/screens/nativeScreens.js +31 -6
  243. package/lib/module/api/screens/nativeScreens.js.map +1 -1
  244. package/lib/module/api/screens/registerScreens.js +172 -35
  245. package/lib/module/api/screens/registerScreens.js.map +1 -1
  246. package/lib/module/api/theme/theme.js +5 -0
  247. package/lib/module/api/theme/theme.js.map +1 -1
  248. package/lib/module/engine/commands.js +42 -2
  249. package/lib/module/engine/commands.js.map +1 -1
  250. package/lib/module/engine/layout.js +7 -2
  251. package/lib/module/engine/layout.js.map +1 -1
  252. package/lib/module/engine/nativeScreens.js +9 -0
  253. package/lib/module/engine/nativeScreens.js.map +1 -1
  254. package/lib/module/engine/options.js +5 -4
  255. package/lib/module/engine/options.js.map +1 -1
  256. package/lib/module/engine/optionsChange.js +102 -0
  257. package/lib/module/engine/optionsChange.js.map +1 -0
  258. package/lib/module/engine/refresh.js +85 -0
  259. package/lib/module/engine/refresh.js.map +1 -0
  260. package/lib/module/engine/rememberedOptions.js +60 -0
  261. package/lib/module/engine/rememberedOptions.js.map +1 -1
  262. package/lib/module/engine/screens.js +55 -4
  263. package/lib/module/engine/screens.js.map +1 -1
  264. package/lib/typescript/adapters/NativeANNTurboModule.d.ts +7 -0
  265. package/lib/typescript/adapters/NativeANNTurboModule.d.ts.map +1 -1
  266. package/lib/typescript/api/app/AppRegistry.d.ts.map +1 -1
  267. package/lib/typescript/api/app/createNavigation.d.ts +8 -3
  268. package/lib/typescript/api/app/createNavigation.d.ts.map +1 -1
  269. package/lib/typescript/api/events/navigationEvents.d.ts +2 -2
  270. package/lib/typescript/api/events/navigationEvents.d.ts.map +1 -1
  271. package/lib/typescript/api/hooks/useSafeAreaInsets.d.ts +4 -3
  272. package/lib/typescript/api/hooks/useSafeAreaInsets.d.ts.map +1 -1
  273. package/lib/typescript/api/index.d.ts +1 -1
  274. package/lib/typescript/api/index.d.ts.map +1 -1
  275. package/lib/typescript/api/linking/linking.d.ts +8 -0
  276. package/lib/typescript/api/linking/linking.d.ts.map +1 -1
  277. package/lib/typescript/api/options/OptionsChain.d.ts +2 -2
  278. package/lib/typescript/api/screens/defineScreen.d.ts +23 -4
  279. package/lib/typescript/api/screens/defineScreen.d.ts.map +1 -1
  280. package/lib/typescript/api/screens/nativeScreens.d.ts +8 -1
  281. package/lib/typescript/api/screens/nativeScreens.d.ts.map +1 -1
  282. package/lib/typescript/api/screens/registerScreens.d.ts.map +1 -1
  283. package/lib/typescript/api/theme/theme.d.ts +2 -0
  284. package/lib/typescript/api/theme/theme.d.ts.map +1 -1
  285. package/lib/typescript/engine/commands.d.ts +5 -0
  286. package/lib/typescript/engine/commands.d.ts.map +1 -1
  287. package/lib/typescript/engine/layout.d.ts.map +1 -1
  288. package/lib/typescript/engine/nativeScreens.d.ts +5 -0
  289. package/lib/typescript/engine/nativeScreens.d.ts.map +1 -1
  290. package/lib/typescript/engine/optionsChange.d.ts +28 -0
  291. package/lib/typescript/engine/optionsChange.d.ts.map +1 -0
  292. package/lib/typescript/engine/refresh.d.ts +35 -0
  293. package/lib/typescript/engine/refresh.d.ts.map +1 -0
  294. package/lib/typescript/engine/rememberedOptions.d.ts +13 -0
  295. package/lib/typescript/engine/rememberedOptions.d.ts.map +1 -1
  296. package/lib/typescript/engine/screens.d.ts +27 -6
  297. package/lib/typescript/engine/screens.d.ts.map +1 -1
  298. package/lib/typescript/interfaces/ComponentEvents.d.ts +2 -2
  299. package/lib/typescript/interfaces/options/animations.d.ts +44 -14
  300. package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
  301. package/lib/typescript/interfaces/options/bottomTabs.d.ts +19 -6
  302. package/lib/typescript/interfaces/options/bottomTabs.d.ts.map +1 -1
  303. package/lib/typescript/interfaces/options/buttons.d.ts +10 -10
  304. package/lib/typescript/interfaces/options/modals.d.ts +6 -7
  305. package/lib/typescript/interfaces/options/modals.d.ts.map +1 -1
  306. package/lib/typescript/interfaces/options/stack.d.ts +8 -3
  307. package/lib/typescript/interfaces/options/stack.d.ts.map +1 -1
  308. package/lib/typescript/interfaces/options/system.d.ts +8 -5
  309. package/lib/typescript/interfaces/options/system.d.ts.map +1 -1
  310. package/lib/typescript/interfaces/options/toolbar.d.ts +2 -2
  311. package/lib/typescript/interfaces/options/topBar.d.ts +46 -29
  312. package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
  313. package/metro/acceptPlugin.js +57 -0
  314. package/metro/babelTransformer.js +45 -0
  315. package/metro/index.js +36 -0
  316. package/package.json +3 -1
  317. package/src/ARCHITECTURE.md +17 -7
  318. package/src/adapters/NativeANNTurboModule.ts +7 -0
  319. package/src/api/app/AppRegistry.ts +16 -2
  320. package/src/api/app/createNavigation.ts +201 -69
  321. package/src/api/events/navigationEvents.ts +9 -3
  322. package/src/api/hooks/useSafeAreaInsets.ts +4 -3
  323. package/src/api/index.ts +1 -0
  324. package/src/api/linking/linking.ts +22 -1
  325. package/src/api/options/OptionsChain.ts +2 -2
  326. package/src/api/screens/defineScreen.ts +49 -5
  327. package/src/api/screens/nativeScreens.ts +40 -4
  328. package/src/api/screens/registerScreens.tsx +170 -42
  329. package/src/api/theme/theme.ts +5 -0
  330. package/src/engine/commands.ts +51 -6
  331. package/src/engine/layout.ts +11 -2
  332. package/src/engine/nativeScreens.ts +10 -1
  333. package/src/engine/options.ts +6 -8
  334. package/src/engine/optionsChange.ts +107 -0
  335. package/src/engine/refresh.ts +97 -0
  336. package/src/engine/rememberedOptions.ts +70 -0
  337. package/src/engine/screens.tsx +53 -8
  338. package/src/interfaces/ComponentEvents.ts +2 -2
  339. package/src/interfaces/options/animations.ts +44 -14
  340. package/src/interfaces/options/bottomTabs.ts +19 -6
  341. package/src/interfaces/options/buttons.ts +10 -10
  342. package/src/interfaces/options/modals.ts +6 -7
  343. package/src/interfaces/options/stack.ts +8 -3
  344. package/src/interfaces/options/system.ts +8 -5
  345. package/src/interfaces/options/toolbar.ts +2 -2
  346. package/src/interfaces/options/topBar.ts +46 -29
  347. package/android/src/main/java/dev/apexnative/navigation/react/ArrangementStates.kt +0 -42
  348. package/android/src/main/java/dev/apexnative/navigation/react/ScreenInsets.kt +0 -70
  349. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/CustomTabRowPlacer.kt +0 -223
  350. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementStub.kt +0 -31
  351. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +0 -136
  352. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +0 -86
  353. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PropertyAnimatorCreator.kt +0 -33
  354. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +0 -64
  355. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/TextChangeAnimator.kt +0 -177
  356. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +0 -137
  357. package/ios/Layouts/Screen/ANNScreenInsets.h +0 -30
  358. package/ios/Layouts/Screen/ANNScreenInsets.mm +0 -70
@@ -0,0 +1,185 @@
1
+ #import "ANNSwipePopInteraction.h"
2
+ #import "ANNAssert.h"
3
+ #import "ANNTransitionAnimator.h"
4
+
5
+ // A flick faster than this, in points a second, ends the swipe by its direction alone.
6
+ static const CGFloat ANNSwipeFlickSpeed = 500;
7
+
8
+ // The rest of the pop plays at most this many times as fast as its own animation.
9
+ static const CGFloat ANNSwipeMostSpeedUp = 3;
10
+
11
+ @implementation ANNSwipePopInteraction {
12
+ ANNTransitionAnimator *_strongAnimator;
13
+ __weak UIPanGestureRecognizer *_recognizer;
14
+ __weak UIView *_view;
15
+ id<UIViewControllerContextTransitioning> _context;
16
+ // The interaction keeps itself while it follows the gesture: UIKit may let it go before the finger does.
17
+ ANNSwipePopInteraction *_keep;
18
+ // The gesture ended before UIKit started the transition: the end is chosen at the start.
19
+ BOOL _endedEarly;
20
+ BOOL _gaveUpEarly;
21
+ CGFloat _completionSpeed;
22
+ }
23
+
24
+ - (instancetype)initWithAnimator:(ANNTransitionAnimator *)animator
25
+ recognizer:(UIPanGestureRecognizer *)recognizer
26
+ view:(UIView *)view {
27
+ if (self = [super init]) {
28
+ _animator = animator;
29
+ _strongAnimator = animator;
30
+ _recognizer = recognizer;
31
+ _view = view;
32
+ _completionSpeed = 1;
33
+ _keep = self;
34
+ [recognizer addTarget:self action:@selector(swipe:)];
35
+ }
36
+ return self;
37
+ }
38
+
39
+ #pragma mark - UIViewControllerInteractiveTransitioning
40
+
41
+ - (void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
42
+ ANNAssertMainQueue();
43
+ ANNTransitionAnimator *animator = _strongAnimator;
44
+ if (!animator || _context) {
45
+ return;
46
+ }
47
+ _context = transitionContext;
48
+ [animator animateFollowingGesture:transitionContext];
49
+ UIGestureRecognizerState state = _recognizer.state;
50
+ BOOL moving = state == UIGestureRecognizerStateBegan || state == UIGestureRecognizerStateChanged;
51
+ if (_gaveUpEarly) {
52
+ [self endFinishing:NO speed:1];
53
+ return;
54
+ }
55
+ if (_endedEarly || !moving) {
56
+ [self letGo];
57
+ return;
58
+ }
59
+ [self follow];
60
+ }
61
+
62
+ - (CGFloat)completionSpeed {
63
+ return _completionSpeed;
64
+ }
65
+
66
+ // The frames play at an even pace to their end, so UIKit's part (the bar) does too.
67
+ - (UIViewAnimationCurve)completionCurve {
68
+ return UIViewAnimationCurveLinear;
69
+ }
70
+
71
+ - (BOOL)wantsInteractiveStart {
72
+ return YES;
73
+ }
74
+
75
+ #pragma mark - The gesture
76
+
77
+ - (void)swipe:(UIPanGestureRecognizer *)recognizer {
78
+ switch (recognizer.state) {
79
+ case UIGestureRecognizerStateBegan:
80
+ case UIGestureRecognizerStateChanged:
81
+ [self follow];
82
+ break;
83
+ case UIGestureRecognizerStateEnded:
84
+ [self letGo];
85
+ break;
86
+ case UIGestureRecognizerStateCancelled:
87
+ case UIGestureRecognizerStateFailed:
88
+ [self giveUp];
89
+ break;
90
+ default:
91
+ break;
92
+ }
93
+ }
94
+
95
+ - (void)follow {
96
+ if (!_context) {
97
+ return;
98
+ }
99
+ if (_strongAnimator.isCompleted) {
100
+ [self stop];
101
+ return;
102
+ }
103
+ CGFloat progress = [self progress];
104
+ [_strongAnimator setProgress:progress];
105
+ [_context updateInteractiveTransition:progress];
106
+ }
107
+
108
+ // The finger left: a flick decides by its direction, a slow release by how far the swipe went.
109
+ - (void)letGo {
110
+ if (!_context) {
111
+ [self endedEarlyGivingUp:NO];
112
+ return;
113
+ }
114
+ CGFloat velocity = [self alongBack:[_recognizer velocityInView:_view].x];
115
+ BOOL finish = velocity > ANNSwipeFlickSpeed ||
116
+ (velocity >= -ANNSwipeFlickSpeed && [self progress] > 0.5);
117
+ // The rest takes the finger's pace when that is quicker than the animation's: the animation would cover the width
118
+ // in its length, the finger in width / velocity.
119
+ CGFloat width = MAX(_view.bounds.size.width, 1);
120
+ BOOL withFinger = finish ? velocity > 0 : velocity < 0;
121
+ CGFloat speed = withFinger ? fabs(velocity) * _strongAnimator.length / width : 1;
122
+ [self endFinishing:finish speed:MIN(MAX(speed, 1.0), ANNSwipeMostSpeedUp)];
123
+ }
124
+
125
+ // The system took the touch (a call, a second gesture): the screen goes back.
126
+ - (void)giveUp {
127
+ if (!_context) {
128
+ [self endedEarlyGivingUp:YES];
129
+ return;
130
+ }
131
+ [self endFinishing:NO speed:1];
132
+ }
133
+
134
+ // UIKit holds an interaction it starts; one it never starts goes once the gesture ended.
135
+ - (void)endedEarlyGivingUp:(BOOL)gaveUp {
136
+ _endedEarly = YES;
137
+ _gaveUpEarly = gaveUp;
138
+ [_recognizer removeTarget:self action:@selector(swipe:)];
139
+ _keep = nil;
140
+ }
141
+
142
+ - (void)endFinishing:(BOOL)finish speed:(CGFloat)speed {
143
+ id<UIViewControllerContextTransitioning> context = _context;
144
+ ANNTransitionAnimator *animator = _strongAnimator;
145
+ [self stop];
146
+ if (!context || !animator || animator.isCompleted) {
147
+ return;
148
+ }
149
+ // UIKit reads the speed as it ends its own part of the transition.
150
+ _completionSpeed = speed;
151
+ if (finish) {
152
+ [context finishInteractiveTransition];
153
+ [animator finishWithSpeed:speed];
154
+ } else {
155
+ [context cancelInteractiveTransition];
156
+ [animator rewindWithSpeed:speed];
157
+ }
158
+ }
159
+
160
+ // The part of the width the finger went in the direction of the swipe back.
161
+ - (CGFloat)progress {
162
+ UIView *view = _view;
163
+ CGFloat width = view.bounds.size.width;
164
+ if (width <= 0) {
165
+ return 0;
166
+ }
167
+ CGFloat along = [self alongBack:[_recognizer translationInView:view].x];
168
+ return MIN(MAX(along / width, 0.0), 1.0);
169
+ }
170
+
171
+ // A move across the view, positive in the direction of the swipe back: rightward, leftward in a right-to-left layout.
172
+ - (CGFloat)alongBack:(CGFloat)x {
173
+ BOOL rightToLeft =
174
+ _view.effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft;
175
+ return rightToLeft ? -x : x;
176
+ }
177
+
178
+ - (void)stop {
179
+ [_recognizer removeTarget:self action:@selector(swipe:)];
180
+ _context = nil;
181
+ _strongAnimator = nil;
182
+ _keep = nil;
183
+ }
184
+
185
+ @end
@@ -86,10 +86,11 @@ static BOOL ANNIsOwnQuestionOfUIKit(SEL selector) {
86
86
 
87
87
  #pragma mark - UIGestureRecognizerDelegate
88
88
 
89
- // The order: `popGesture` first (with `false` there is neither a swipe nor the hold's event), then one screen or a
90
- // stack not at rest, then the hold (its event instead of the swipe), then the edge swipe under a hidden bar begins
91
- // without UIKit's delegate, and otherwise UIKit's delegate decides (its direction, a vertical scroll of the content). A
92
- // top that isn't the stack's own (a column UIKit's split view moved in) is UIKit's.
89
+ // The order: which of the stack's swipes plays first (`swipeBackTakes:`: the stack's own while the top's pop follows
90
+ // the finger, UIKit's otherwise), then `popGesture` (with `false` there is neither a swipe nor the hold's event), then
91
+ // one screen or a stack not at rest, then the hold (its event instead of the swipe), then the edge swipe under a hidden
92
+ // bar begins without UIKit's delegate, and otherwise UIKit's delegate decides (its direction, a vertical scroll of the
93
+ // content). A top that isn't the stack's own (a column UIKit's split view moved in) is UIKit's.
93
94
  //
94
95
  // Every pan in the content is offered to the content swipe, so it is first sorted: a pan that isn't a swipe back
95
96
  // (vertical, the other way) is no attempt, and neither is one on a list that scrolls sideways, which takes the pan
@@ -97,6 +98,10 @@ static BOOL ANNIsOwnQuestionOfUIKit(SEL selector) {
97
98
  // hidden bar it refuses them all, and the swipe sorts them itself, as the edge swipe begins there without it.
98
99
  - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
99
100
  ANNStackController *stack = _stack;
101
+ // One swipe plays the pop: the stack's own while the top's pop follows the finger, UIKit's otherwise.
102
+ if (stack && ![stack swipeBackTakes:gestureRecognizer]) {
103
+ return NO;
104
+ }
100
105
  if (stack && ![stack swipeBackIsUIKits:gestureRecognizer]) {
101
106
  if (!_enabled || ![stack swipeBackMayBegin]) {
102
107
  return NO;
@@ -191,6 +196,25 @@ static BOOL ANNIsOwnQuestionOfUIKit(SEL selector) {
191
196
  return YES;
192
197
  }
193
198
 
199
+ // A list under the stack's own swipe waits for it, as for UIKit's edge swipe: a swipe from the edge goes back, also on
200
+ // a list that scrolls sideways. UIKit's swipes keep UIKit's answer.
201
+ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
202
+ shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
203
+ ANNStackController *stack = _stack;
204
+ if (stack && [stack swipeBackIsStacks:gestureRecognizer]) {
205
+ UIView *view = otherGestureRecognizer.view;
206
+ return [view isKindOfClass:UIScrollView.class] &&
207
+ otherGestureRecognizer == ((UIScrollView *)view).panGestureRecognizer;
208
+ }
209
+ id<UIGestureRecognizerDelegate> systemDelegate = _systemDelegate;
210
+ SEL question = @selector(gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:);
211
+ if ([systemDelegate respondsToSelector:question]) {
212
+ return [systemDelegate gestureRecognizer:gestureRecognizer
213
+ shouldBeRequiredToFailByGestureRecognizer:otherGestureRecognizer];
214
+ }
215
+ return NO;
216
+ }
217
+
194
218
  #pragma mark - The rest goes to UIKit's delegate
195
219
 
196
220
  - (void)setBarHidden:(BOOL)barHidden {
@@ -2,6 +2,8 @@
2
2
  #import "ANNReactView.h"
3
3
  #import <UIKit/UIKit.h>
4
4
 
5
+ NS_ASSUME_NONNULL_BEGIN
6
+
5
7
  /**
6
8
  * The content of the tab bar's bottom accessory (iOS 26): the React component fills it, and the
7
9
  * component gets `bottomAccessoryPlacementChanged` when UIKit moves the accessory between the
@@ -10,8 +12,17 @@
10
12
  @interface ANNBottomTabsAccessoryView : UIView
11
13
 
12
14
  - (instancetype)initWithReactView:(ANNReactView *)reactView
13
- eventEmitter:(ANNEventEmitter *)eventEmitter;
15
+ eventEmitter:(nullable ANNEventEmitter *)eventEmitter;
14
16
 
15
17
  @property(nonatomic, strong, readonly) ANNReactView *reactView;
16
18
 
19
+ /**
20
+ * `handler` runs once when the view is out of a window: at once when it is already, else when it
21
+ * leaves it, so a replaced accessory shows to the end of whatever UIKit draws of it. nil cancels a
22
+ * handler not run yet.
23
+ */
24
+ - (void)whenOutOfWindow:(nullable dispatch_block_t)handler;
25
+
17
26
  @end
27
+
28
+ NS_ASSUME_NONNULL_END
@@ -3,6 +3,7 @@
3
3
  @implementation ANNBottomTabsAccessoryView {
4
4
  ANNEventEmitter *_eventEmitter;
5
5
  NSString *_placement;
6
+ dispatch_block_t _outOfWindowHandler;
6
7
  }
7
8
 
8
9
  - (instancetype)initWithReactView:(ANNReactView *)reactView
@@ -32,6 +33,28 @@
32
33
  }];
33
34
  }
34
35
 
36
+ - (void)whenOutOfWindow:(dispatch_block_t)handler {
37
+ _outOfWindowHandler = nil;
38
+ if (!handler) {
39
+ return;
40
+ }
41
+ if (!self.window) {
42
+ handler();
43
+ return;
44
+ }
45
+ _outOfWindowHandler = [handler copy];
46
+ }
47
+
48
+ - (void)didMoveToWindow {
49
+ [super didMoveToWindow];
50
+ dispatch_block_t handler = _outOfWindowHandler;
51
+ if (self.window || !handler) {
52
+ return;
53
+ }
54
+ _outOfWindowHandler = nil;
55
+ handler();
56
+ }
57
+
35
58
  // UIKit sets the environment when the accessory is shown and changes it as the tab bar minimizes
36
59
  // and expands.
37
60
  - (void)tabAccessoryEnvironmentDidChange API_AVAILABLE(ios(26.0)) {
@@ -598,10 +598,11 @@ static void ANNStacksOfNode(UIViewController *node, NSMutableArray<ANNStackContr
598
598
  [[self tabsPresenter] tabsDidLayOut];
599
599
  }
600
600
 
601
- // `afterInitialTab` goes on at the tabs' first appearance at the latest.
601
+ // `afterInitialTab` goes on at the tabs' first appearance at the latest; a dot that waited for the window shows.
602
602
  - (void)viewDidAppear:(BOOL)animated {
603
603
  [super viewDidAppear:animated];
604
604
  [self scheduleChain];
605
+ [[self tabsPresenter] tabsDidAppear];
605
606
  }
606
607
 
607
608
  #pragma mark - Rendering and the preload window
@@ -126,8 +126,13 @@ static const CGFloat ANNWideRowMargin = 16;
126
126
  if (self.superview != host) {
127
127
  [host addSubview:self];
128
128
  }
129
- [host bringSubviewToFront:self];
130
- // The system bar keeps its place and shows nothing; UIKit makes its subviews anew on a size change.
129
+ if (host.subviews.lastObject != self) {
130
+ [host bringSubviewToFront:self];
131
+ }
132
+ // The system bar keeps its place and shows nothing; UIKit makes its subviews anew on a size change. Not the bar's
133
+ // `alpha`: UIKit puts it back to 1 when the bar shows, on a pop with `hidesBottomBarWhenPushed`, and on iOS 27 on
134
+ // a rotation and a theme change, after the tabs' layout (a check here misses it); alpha 0 also drops the system's
135
+ // soft fade at the bottom.
131
136
  for (UIView *subview in bar.subviews) {
132
137
  if (!subview.hidden) {
133
138
  subview.hidden = YES;
@@ -193,6 +198,7 @@ static const CGFloat ANNWideRowMargin = 16;
193
198
  extra = MAX(0, CGRectGetMinY(barFrame) - CGRectGetMinY(frame));
194
199
  }
195
200
  BOOL hidden = wide ? _hiddenByOptions : (tabs.barHidden || bar.hidden);
201
+ // A new size lays the cells out again by itself; the same frame changes nothing.
196
202
  if (!CGRectEqualToRect(self.frame, frame)) {
197
203
  self.frame = frame;
198
204
  }
@@ -203,7 +209,6 @@ static const CGFloat ANNWideRowMargin = 16;
203
209
  _background.layer.cornerRadius = radius;
204
210
  }
205
211
  [self setExtraSafeArea:hidden ? 0 : extra];
206
- [self setNeedsLayout];
207
212
  }
208
213
 
209
214
  - (void)setHiddenByOptions:(BOOL)hiddenByOptions {
@@ -55,6 +55,10 @@ NS_ASSUME_NONNULL_BEGIN
55
55
  /// The tabs laid out (and before they appear): the dots, the buttons' `testID`s, the custom row.
56
56
  - (void)tabsDidLayOut;
57
57
 
58
+ /// The tabs appeared, in a window: a dot set while they were out of it (under a modal over the whole screen) is
59
+ /// placed now, as no layout may follow.
60
+ - (void)tabsDidAppear;
61
+
58
62
  /// The bar laid out in a window for the first time: the titles are set again, so that the bar measures them at their
59
63
  /// real place.
60
64
  - (void)barDidLayOutInWindow;
@@ -12,9 +12,6 @@
12
12
 
13
13
  // The dot of a tab before iOS 26: its default size and color.
14
14
  static const CGFloat ANNDotDefaultSize = 6;
15
- // A button not there yet gets its `testID` again this often, at most this many times (`bottomTab.testID`).
16
- static const NSTimeInterval ANNTestIDRetryInterval = 0.15;
17
- static const NSUInteger ANNTestIDRetries = 5;
18
15
 
19
16
  // A picture of `color` (both of its styles when it is dynamic): the bar's 1×1 background and the border's line.
20
17
  static UIImage *ANNImageOfColor(UIColor *color, CGSize size) {
@@ -63,13 +60,23 @@ static id ANNValue(NSArray *list, NSUInteger index) {
63
60
  NSMutableArray<NSNumber *> *_roleItems;
64
61
  // The identifiers the buttons had before the core gave them a `testID`, to give back.
65
62
  NSMapTable<UIView *, NSString *> *_buttonIds;
63
+ // What the `testID`s and the dots were last applied to: the bar's items, the button of each tab and of More, the
64
+ // bar's size and its traits. UIKit makes the buttons anew (rotation, text size, items moving between the bar and
65
+ // More), and a layout that finds other buttons applies them again.
66
+ NSPointerArray *_appliedButtons;
67
+ CGSize _appliedBarSize;
68
+ UITraitCollection *_appliedBarTraits;
66
69
  BOOL _tabsAreSet;
67
70
  BOOL _titlesHidden;
71
+ // A dot that shows was placed while its button had no window (the tabs under a full-screen modal): it hides then,
72
+ // and the next layout in the window places it, whatever the check of the buttons says.
73
+ BOOL _dotsWaitForWindow;
68
74
  // The bar's look: its options, and the background and border every item's look starts from.
69
75
  ANNBottomTabsOptions *_barOptions;
70
76
  UITabBarAppearance *_base;
71
77
  ANNBottomTabsCustomRow *_row;
72
- // The accessory shown (its id), and the content view of each accessory made, kept until the tabs go.
78
+ // The accessory shown (its id), and the content view of each accessory made: kept while hidden, until another
79
+ // accessory shows or the tabs go.
73
80
  NSString *_accessoryId;
74
81
  NSMutableDictionary<NSString *, ANNBottomTabsAccessoryView *> *_accessoryViews;
75
82
  // The pictures of the bar's colors by color and size.
@@ -123,6 +130,7 @@ static id ANNValue(NSArray *list, NSUInteger index) {
123
130
  _dotViews = [NSMutableArray new];
124
131
  _roleItems = [NSMutableArray new];
125
132
  _buttonIds = [NSMapTable weakToStrongObjectsMapTable];
133
+ _appliedButtons = [NSPointerArray weakObjectsPointerArray];
126
134
  _accessoryViews = [NSMutableDictionary new];
127
135
  _colorImages = [NSMutableDictionary new];
128
136
  _iconLoads = [NSMutableDictionary new];
@@ -251,7 +259,7 @@ static id ANNValue(NSArray *list, NSUInteger index) {
251
259
  [_row applyOptions:resolvedOptions.bottomTabs.customRow];
252
260
  }
253
261
  [self applyAccessoryOf:resolvedOptions animated:NO];
254
- [self applyButtonTestIDsWithRetries:ANNTestIDRetries];
262
+ [self applyButtonTestIDs];
255
263
  }
256
264
 
257
265
  #pragma mark - ANNNodePresenter
@@ -315,7 +323,7 @@ static id ANNValue(NSArray *list, NSUInteger index) {
315
323
  }
316
324
  [self buildItemAtIndex:index bottomTab:bottomTab updatesBadgeAndDot:badgeAndDot];
317
325
  [self updateCells];
318
- [self applyButtonTestIDsWithRetries:mergeOptions.testID.hasValue ? ANNTestIDRetries : 0];
326
+ [self applyButtonTestIDs];
319
327
  }
320
328
 
321
329
  // The whole item from `bottomTab`; the badge and the dot from it only with `updatesBadgeAndDot`.
@@ -589,6 +597,7 @@ static ANNBottomTabOptions *ANNWithIcons(ANNBottomTabOptions *bottomTab, NSArray
589
597
  if (anyDot) {
590
598
  [bar layoutIfNeeded];
591
599
  }
600
+ BOOL waitsForWindow = NO;
592
601
  for (NSUInteger index = 0; index < tabs.layoutTabs.count; index++) {
593
602
  ANNTabDotView *view = ANNValue(_dotViews, index);
594
603
  if (!view) {
@@ -602,49 +611,36 @@ static ANNBottomTabOptions *ANNWithIcons(ANNBottomTabOptions *bottomTab, NSArray
602
611
  }
603
612
  UITabBarItem *item = tabs.layoutTabs[index].tabBarItem;
604
613
  BOOL inBar = [bar.items indexOfObjectIdenticalTo:item] != NSNotFound;
605
- [view placeOverButton:inBar ? [bar viewOfItem:item] : nil inBar:bar];
614
+ UIView *button = inBar ? [bar viewOfItem:item] : nil;
615
+ waitsForWindow = waitsForWindow || (inBar && !button.window);
616
+ [view placeOverButton:button inBar:bar];
606
617
  }
618
+ _dotsWaitForWindow = waitsForWindow;
607
619
  }
608
620
 
609
621
  #pragma mark - testID (`bottomTab.testID`, `bottomTabs.moreTestID`)
610
622
 
611
- // Each tab's button carries its `testID`; a button not there yet gets it again after 0.15 s, at most 5 times; a
612
- // removed `testID` gives the button its identifier back.
613
- - (void)applyButtonTestIDsWithRetries:(NSUInteger)retries {
623
+ // Each tab's button carries its `testID`; a removed `testID` gives the button its identifier back. A button not
624
+ // there yet gets it at the layout that finds it (`tabsDidLayOut`).
625
+ - (void)applyButtonTestIDs {
614
626
  ANNBottomTabsController *tabs = self.tabs;
615
627
  UITabBar *bar = tabs.tabBar;
616
628
  if (!tabs || !bar) {
617
629
  return;
618
630
  }
619
- BOOL missing = NO;
620
631
  for (NSUInteger index = 0; index < tabs.layoutTabs.count; index++) {
621
632
  UITabBarItem *item = tabs.layoutTabs[index].tabBarItem;
622
- NSString *testID = item.accessibilityIdentifier;
623
633
  UIView *button = [bar viewOfItem:item];
624
- if (!button) {
625
- missing = missing || testID.length > 0;
626
- continue;
634
+ if (button) {
635
+ [self setTestID:item.accessibilityIdentifier ofButton:button];
627
636
  }
628
- [self setTestID:testID ofButton:button];
629
637
  }
630
638
  if (tabs.layoutTabs.count > 5) {
631
- NSString *moreTestID = [_barOptions.moreTestID withDefault:nil];
632
639
  UIView *button = [bar viewOfItem:tabs.moreNavigationController.tabBarItem];
633
640
  if (button) {
634
- [self setTestID:moreTestID ofButton:button];
635
- } else {
636
- missing = missing || moreTestID.length > 0;
641
+ [self setTestID:[_barOptions.moreTestID withDefault:nil] ofButton:button];
637
642
  }
638
643
  }
639
- if (!missing || retries == 0) {
640
- return;
641
- }
642
- __weak ANNBottomTabsPresenter *weakSelf = self;
643
- dispatch_after(
644
- dispatch_time(DISPATCH_TIME_NOW, (int64_t)(ANNTestIDRetryInterval * NSEC_PER_SEC)),
645
- dispatch_get_main_queue(), ^{
646
- [weakSelf applyButtonTestIDsWithRetries:retries - 1];
647
- });
648
644
  }
649
645
 
650
646
  - (void)setTestID:(NSString *)testID ofButton:(UIView *)button {
@@ -728,8 +724,9 @@ static ANNBottomTabOptions *ANNWithIcons(ANNBottomTabOptions *bottomTab, NSArray
728
724
  warned = YES;
729
725
  RCTLogWarn(
730
726
  @"[ApexNavigation] bottomTabs.backgroundColor is ignored with Liquid "
731
- @"Glass (iOS 26). It applies with UIDesignRequiresCompatibility and on "
732
- @"earlier iOS versions.");
727
+ @"Glass (iOS 26). It applies before iOS 26 and where UIKit keeps the "
728
+ @"earlier design for UIDesignRequiresCompatibility (iOS 26; iOS 27 only "
729
+ @"for an app built with an SDK before iOS 27).");
733
730
  }
734
731
  }
735
732
  }
@@ -969,14 +966,15 @@ static ANNBottomTabOptions *ANNWithIcons(ANNBottomTabOptions *bottomTab, NSArray
969
966
  [tabs updateRootLayoutOfShownScreens];
970
967
  }
971
968
  if (mergeOptions.testID.hasValue || mergeOptions.moreTestID.hasValue) {
972
- [self applyButtonTestIDsWithRetries:ANNTestIDRetries];
969
+ [self applyButtonTestIDs];
973
970
  }
974
971
  }
975
972
 
976
973
  #pragma mark - The accessory (iOS 26)
977
974
 
978
- // No name or `visible: false`: no accessory. The same id shown: nothing; another: a new one. A root of an id stays
979
- // with the tabs once made, so one hidden and shown again keeps its state.
975
+ // No name or `visible: false`: no accessory. The same id shown: nothing; another: a new one. A hidden accessory keeps
976
+ // its root, so one hidden and shown again keeps its state; the roots of the other ids stop once another accessory
977
+ // shows and theirs is out of the window.
980
978
  - (void)applyAccessoryOf:(ANNNavigationOptions *)options animated:(BOOL)animated {
981
979
  if (@available(iOS 26.0, *)) {
982
980
  ANNBottomTabsController *tabs = self.tabs;
@@ -1012,8 +1010,37 @@ static ANNBottomTabOptions *ANNWithIcons(ANNBottomTabOptions *bottomTab, NSArray
1012
1010
  return;
1013
1011
  }
1014
1012
  _accessoryId = componentId;
1013
+ [view whenOutOfWindow:nil];
1015
1014
  UITabAccessory *tabAccessory = [[UITabAccessory alloc] initWithContentView:view];
1016
1015
  [tabs setBottomAccessory:tabAccessory animated:animated && inWindow];
1016
+ [self stopAccessoriesOtherThan:componentId];
1017
+ }
1018
+ }
1019
+
1020
+ // The roots of the accessories not shown stop, each once its view is out of the window, unless it shows again first.
1021
+ - (void)stopAccessoriesOtherThan:(NSString *)shownId {
1022
+ ANNBottomTabsController *tabs = self.tabs;
1023
+ __weak ANNBottomTabsPresenter *weakSelf = self;
1024
+ __weak ANNReactComponentRegistry *weakComponents = tabs.components;
1025
+ for (NSString *componentId in _accessoryViews.allKeys) {
1026
+ if ([componentId isEqualToString:shownId]) {
1027
+ continue;
1028
+ }
1029
+ ANNBottomTabsAccessoryView *view = _accessoryViews[componentId];
1030
+ __weak ANNBottomTabsAccessoryView *weakView = view;
1031
+ [view whenOutOfWindow:^{
1032
+ ANNBottomTabsPresenter *presenter = weakSelf;
1033
+ ANNBottomTabsAccessoryView *gone = weakView;
1034
+ if (!presenter || !gone || presenter->_accessoryViews[componentId] != gone ||
1035
+ [presenter->_accessoryId isEqualToString:componentId]) {
1036
+ return;
1037
+ }
1038
+ [presenter->_accessoryViews removeObjectForKey:componentId];
1039
+ ANNReactComponentRegistry *components = weakComponents;
1040
+ if ([components existingComponent:componentId] == gone.reactView) {
1041
+ [components removeChildComponent:componentId];
1042
+ }
1043
+ }];
1017
1044
  }
1018
1045
  }
1019
1046
 
@@ -1062,18 +1089,78 @@ static ANNBottomTabOptions *ANNWithIcons(ANNBottomTabOptions *bottomTab, NSArray
1062
1089
  [self updateCells];
1063
1090
  }
1064
1091
 
1092
+ // The `testID`s and the dots go on the buttons again only when the bar has other buttons than they were applied to,
1093
+ // another size or other traits; a merge and a rebuilt item apply them at once. A dot placed out of the window waits
1094
+ // for the next layout.
1065
1095
  - (void)tabsDidLayOut {
1066
1096
  if (!_tabsAreSet) {
1067
1097
  return;
1068
1098
  }
1069
1099
  [self updateHiddenTitles];
1070
- if (!ANNTabsDesign.isGlass) {
1100
+ BOOL changed = [self barChangedSinceApplied];
1101
+ if ((changed || _dotsWaitForWindow) && !ANNTabsDesign.isGlass) {
1071
1102
  [self placeDots];
1072
1103
  }
1073
- [self applyButtonTestIDsWithRetries:0];
1104
+ if (changed) {
1105
+ [self applyButtonTestIDs];
1106
+ }
1074
1107
  [_row layOutInTabs];
1075
1108
  }
1076
1109
 
1110
+ - (void)tabsDidAppear {
1111
+ if (_tabsAreSet && _dotsWaitForWindow && !ANNTabsDesign.isGlass) {
1112
+ [self placeDots];
1113
+ }
1114
+ }
1115
+
1116
+ // The bar's items and the button of each tab and of More, by pointer, against those last seen; no walk through the
1117
+ // buttons. An item moving between the bar and More changes the items. The tabs lay out before the bar does: a tab in
1118
+ // the bar without its button yet makes the bar lay out first.
1119
+ - (BOOL)barChangedSinceApplied {
1120
+ ANNBottomTabsController *tabs = self.tabs;
1121
+ UITabBar *bar = tabs.tabBar;
1122
+ if (!tabs || !bar) {
1123
+ return NO;
1124
+ }
1125
+ NSArray<UIViewController *> *layoutTabs = tabs.layoutTabs;
1126
+ NSArray<UITabBarItem *> *barItems = bar.items;
1127
+ NSUInteger tabCount = layoutTabs.count + (layoutTabs.count > 5 ? 1 : 0);
1128
+ NSMutableArray *seen = [NSMutableArray arrayWithArray:barItems];
1129
+ BOOL laidOut = NO;
1130
+ for (NSUInteger index = 0; index < tabCount; index++) {
1131
+ UITabBarItem *item = index < layoutTabs.count ? layoutTabs[index].tabBarItem
1132
+ : tabs.moreNavigationController.tabBarItem;
1133
+ UIView *button = [bar viewOfItem:item];
1134
+ if (!button && !laidOut && bar.window &&
1135
+ [barItems indexOfObjectIdenticalTo:item] != NSNotFound) {
1136
+ laidOut = YES;
1137
+ [bar layoutIfNeeded];
1138
+ button = [bar viewOfItem:item];
1139
+ }
1140
+ [seen addObject:button ?: NSNull.null];
1141
+ }
1142
+ NSUInteger count = seen.count;
1143
+ BOOL changed = _appliedButtons.count != count;
1144
+ for (NSUInteger index = 0; index < count && !changed; index++) {
1145
+ id object = seen[index] == NSNull.null ? nil : seen[index];
1146
+ changed = (__bridge void *)object != [_appliedButtons pointerAtIndex:index];
1147
+ }
1148
+ CGSize size = bar.bounds.size;
1149
+ UITraitCollection *traits = bar.traitCollection;
1150
+ changed = changed || !CGSizeEqualToSize(size, _appliedBarSize) ||
1151
+ ![traits isEqual:_appliedBarTraits];
1152
+ if (!changed) {
1153
+ return NO;
1154
+ }
1155
+ _appliedButtons.count = 0;
1156
+ for (id object in seen) {
1157
+ [_appliedButtons addPointer:object == NSNull.null ? NULL : (__bridge void *)object];
1158
+ }
1159
+ _appliedBarSize = size;
1160
+ _appliedBarTraits = traits;
1161
+ return YES;
1162
+ }
1163
+
1077
1164
  // The glass bar measures a title when it is set and keeps that size: a title set before the bar was ever in a window
1078
1165
  // was measured in a button of its first layout there, when the bar's floating platter had no size yet, and stays cut
1079
1166
  // short ("Sec…") and lower than a title measured later. Neither a layout of the bar nor of its buttons measures it
@@ -2,16 +2,17 @@
2
2
 
3
3
  NS_ASSUME_NONNULL_BEGIN
4
4
 
5
- /// Which design UIKit draws the bars in: the Liquid Glass of iOS 26, or the design before it, which iOS 26 keeps when
6
- /// the app's Info.plist has `UIDesignRequiresCompatibility = YES` (the compatibility mode). The key is read once per
7
- /// process. Every place of the core that depends on the design asks here (the module `UITabBar+utils` keeps its own
8
- /// copy).
5
+ /// Which design UIKit draws the bars in: the Liquid Glass of iOS 26, or the design before it, which UIKit keeps when
6
+ /// the app's Info.plist has `UIDesignRequiresCompatibility = YES` (the compatibility mode): on iOS 26, and on iOS 27
7
+ /// only for an app built with an SDK before iOS 27. The key is read once per process. Every place of the core that
8
+ /// depends on the design asks here.
9
9
  @interface ANNTabsDesign : NSObject
10
10
 
11
11
  /// iOS 26 or later, and not the compatibility mode.
12
12
  @property(class, nonatomic, readonly) BOOL isGlass;
13
13
 
14
- /// iOS 26 or later with `UIDesignRequiresCompatibility = YES`: the bars look as before iOS 26.
14
+ /// iOS 26 or later with `UIDesignRequiresCompatibility = YES`, where UIKit honors it (not on iOS 27 and later for an
15
+ /// app built with the iOS 27 SDK): the bars look as before iOS 26.
15
16
  @property(class, nonatomic, readonly) BOOL isCompatibilityMode;
16
17
 
17
18
  /// iOS 18 and later on an iPad or a Mac in a regular width: the system's tab bar is on top (or in the sidebar), and