react-native-screens 5.0.0-alpha.3 → 5.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 (337) hide show
  1. package/README.md +0 -4
  2. package/android/build.gradle +1 -0
  3. package/android/consumer-rules.pro +3 -0
  4. package/android/src/fabric/java/com/swmansion/rnscreens/legacy/FabricEnabledHeaderConfigViewGroup.kt +2 -0
  5. package/android/src/fabric/java/com/swmansion/rnscreens/legacy/FabricEnabledHeaderSubviewViewGroup.kt +2 -0
  6. package/android/src/fabric/java/com/swmansion/rnscreens/legacy/FabricEnabledViewGroup.kt +18 -3
  7. package/android/src/main/java/com/swmansion/rnscreens/RNScreensPackage.kt +2 -2
  8. package/android/src/main/java/com/swmansion/rnscreens/ScreensModule.kt +7 -3
  9. package/android/src/main/java/com/swmansion/rnscreens/common/colorscheme/ColorSchemeCoordinator.kt +11 -1
  10. package/android/src/main/java/com/swmansion/rnscreens/common/container/Container.kt +9 -0
  11. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/native → core/modals/formsheet}/coordinator/FormSheetAppearanceCoordinator.kt +2 -2
  12. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/native → core/modals/formsheet}/coordinator/FormSheetBehaviorController.kt +2 -2
  13. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/native → core/modals/formsheet}/coordinator/FormSheetDimensionsCoordinator.kt +6 -6
  14. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/native → core/modals/formsheet}/coordinator/FormSheetKeyboardCoordinator.kt +1 -1
  15. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/native → core/modals/formsheet}/coordinator/FormSheetNativeDismissCoordinator.kt +2 -2
  16. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/native/core → core/modals/formsheet/dialog}/FormSheetAvailableHeightProvider.kt +1 -1
  17. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/native/core → core/modals/formsheet/dialog}/FormSheetContainer.kt +1 -1
  18. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/native/core → core/modals/formsheet/dialog}/FormSheetDialog.kt +1 -1
  19. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/native/core → core/modals/formsheet/dialog}/FormSheetDialogManager.kt +26 -8
  20. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/native → core/modals/formsheet}/interfaces/FormSheetContentSizeChangeDelegate.kt +1 -1
  21. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/native → core/modals/formsheet}/interfaces/FormSheetContentSizeChangeProvider.kt +1 -1
  22. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/native → core/modals/formsheet}/interfaces/FormSheetDialogEventEmitter.kt +1 -1
  23. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/native → core/modals/formsheet}/model/FormSheetConfig.kt +1 -1
  24. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/native → core/modals/formsheet}/model/FormSheetDetents.kt +1 -1
  25. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/native → core/modals/formsheet}/presentation/FormSheetAnimatorFactory.kt +1 -1
  26. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/native → core/modals/formsheet}/presentation/FormSheetDimmingManager.kt +6 -18
  27. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/native → core/modals/formsheet}/presentation/FormSheetDismissalOrigin.kt +1 -1
  28. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/native → core/modals/formsheet}/presentation/FormSheetPresentation.kt +10 -10
  29. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/native → core/modals/formsheet}/presentation/FormSheetPresentationManager.kt +1 -1
  30. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/native → core/modals/formsheet}/presentation/FormSheetPresentationState.kt +1 -1
  31. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/native → core/modals/formsheet}/presentation/FormSheetStackRegistry.kt +1 -1
  32. package/android/src/main/java/com/swmansion/rnscreens/ext/FragmentManagerExt.kt +6 -0
  33. package/android/src/main/java/com/swmansion/rnscreens/helpers/FragmentManagerHelper.kt +0 -3
  34. package/android/src/main/java/com/swmansion/rnscreens/helpers/NoTintDrawable.kt +31 -0
  35. package/android/src/main/java/com/swmansion/rnscreens/helpers/ViewFinder.kt +0 -17
  36. package/android/src/main/java/com/swmansion/rnscreens/legacy/Screen.kt +29 -2
  37. package/android/src/main/java/com/swmansion/rnscreens/legacy/ScreenContainer.kt +35 -3
  38. package/android/src/main/java/com/swmansion/rnscreens/legacy/ScreenStack.kt +13 -0
  39. package/android/src/main/java/com/swmansion/rnscreens/legacy/ScreenStackHeaderConfig.kt +7 -1
  40. package/android/src/main/java/com/swmansion/rnscreens/legacy/ScreenStackHeaderSubviewManager.kt +7 -0
  41. package/android/src/main/java/com/swmansion/rnscreens/legacy/ScreenStackViewManager.kt +6 -0
  42. package/android/src/main/java/com/swmansion/rnscreens/legacy/bottomsheet/DimmingView.kt +2 -0
  43. package/android/src/main/java/com/swmansion/rnscreens/legacy/stack/views/ScreensCoordinatorLayout.kt +2 -0
  44. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/react → react/modals/formsheet}/contentwrapper/FormSheetContentWrapper.kt +3 -3
  45. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/react → react/modals/formsheet}/contentwrapper/FormSheetContentWrapperViewManager.kt +1 -1
  46. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/react → react/modals/formsheet}/event/FormSheetDetentChangedEvent.kt +1 -1
  47. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/react → react/modals/formsheet}/event/FormSheetDidAppearEvent.kt +1 -1
  48. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/react → react/modals/formsheet}/event/FormSheetDidDisappearEvent.kt +1 -1
  49. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/react → react/modals/formsheet}/event/FormSheetDismissEvent.kt +1 -1
  50. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/react → react/modals/formsheet}/event/FormSheetNativeDismissEvent.kt +1 -1
  51. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/react → react/modals/formsheet}/event/FormSheetNativeDismissPreventedEvent.kt +1 -1
  52. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/react → react/modals/formsheet}/event/FormSheetSyncFlushEvent.kt +1 -1
  53. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/react → react/modals/formsheet}/event/FormSheetWillAppearEvent.kt +1 -1
  54. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/react → react/modals/formsheet}/event/FormSheetWillDisappearEvent.kt +1 -1
  55. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/react → react/modals/formsheet}/host/FormSheetContentView.kt +3 -3
  56. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/react → react/modals/formsheet}/host/FormSheetHost.kt +5 -3
  57. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/react → react/modals/formsheet}/host/FormSheetHostEventEmitter.kt +11 -11
  58. package/android/src/main/java/com/swmansion/rnscreens/{modals/formsheet/react → react/modals/formsheet}/host/FormSheetHostViewManager.kt +10 -10
  59. package/android/src/main/java/com/swmansion/rnscreens/safearea/OnInterfaceInsetsChangeListener.kt +15 -0
  60. package/android/src/main/java/com/swmansion/rnscreens/safearea/SafeAreaProvider.kt +8 -8
  61. package/android/src/main/java/com/swmansion/rnscreens/safearea/SafeAreaView.kt +3 -2
  62. package/android/src/main/java/com/swmansion/rnscreens/stack/header/StackHeaderApplicator.kt +0 -52
  63. package/android/src/main/java/com/swmansion/rnscreens/stack/header/StackHeaderCoordinatorLayout.kt +112 -59
  64. package/android/src/main/java/com/swmansion/rnscreens/stack/header/appbar/StackHeaderAppBarLayout.kt +1 -1
  65. package/android/src/main/java/com/swmansion/rnscreens/stack/header/appbar/StackHeaderToolbar.kt +74 -0
  66. package/android/src/main/java/com/swmansion/rnscreens/stack/header/config/StackHeaderConfig.kt +4 -73
  67. package/android/src/main/java/com/swmansion/rnscreens/stack/header/config/StackHeaderConfigurationObserver.kt +5 -2
  68. package/android/src/main/java/com/swmansion/rnscreens/stack/header/config/StackHeaderConfigurationProviding.kt +0 -6
  69. package/android/src/main/java/com/swmansion/rnscreens/stack/header/config/StackHeaderInvalidationFlags.kt +3 -7
  70. package/android/src/main/java/com/swmansion/rnscreens/stack/host/FragmentOperation.kt +1 -0
  71. package/android/src/main/java/com/swmansion/rnscreens/stack/host/FragmentOperationExecutor.kt +2 -1
  72. package/android/src/main/java/com/swmansion/rnscreens/stack/host/StackContainer.kt +57 -19
  73. package/android/src/main/java/com/swmansion/rnscreens/stack/host/StackContainerDelegate.kt +7 -0
  74. package/android/src/main/java/com/swmansion/rnscreens/stack/host/StackContainerUpdateCoordinator.kt +5 -3
  75. package/android/src/main/java/com/swmansion/rnscreens/stack/host/StackHost.kt +14 -5
  76. package/android/src/main/java/com/swmansion/rnscreens/stack/host/StackUpdateBatchStateProviding.kt +10 -0
  77. package/android/src/main/java/com/swmansion/rnscreens/stack/screen/PreventNativeDismissCallback.kt +2 -2
  78. package/android/src/main/java/com/swmansion/rnscreens/stack/screen/StackScreenFragment.kt +33 -12
  79. package/android/src/main/java/com/swmansion/rnscreens/tabs/appearance/TabsAppearanceApplicator.kt +106 -10
  80. package/android/src/main/java/com/swmansion/rnscreens/tabs/appearance/TabsAppearanceCoordinator.kt +44 -3
  81. package/android/src/main/java/com/swmansion/rnscreens/tabs/appearance/TabsAppearanceModel.kt +3 -0
  82. package/android/src/main/java/com/swmansion/rnscreens/tabs/container/CustomBottomNavigationView.kt +21 -1
  83. package/android/src/main/java/com/swmansion/rnscreens/tabs/container/TabsContainer.kt +28 -13
  84. package/android/src/main/java/com/swmansion/rnscreens/tabs/container/TabsContainerDelegate.kt +11 -0
  85. package/android/src/main/java/com/swmansion/rnscreens/tabs/host/TabsHost.kt +12 -2
  86. package/android/src/main/java/com/swmansion/rnscreens/tabs/screen/TabsScreen.kt +41 -17
  87. package/android/src/main/java/com/swmansion/rnscreens/tabs/screen/TabsScreenDelegate.kt +6 -0
  88. package/android/src/main/java/com/swmansion/rnscreens/tabs/screen/TabsScreenIcon.kt +82 -0
  89. package/android/src/main/java/com/swmansion/rnscreens/tabs/screen/TabsScreenIconTinting.kt +17 -0
  90. package/android/src/main/java/com/swmansion/rnscreens/tabs/screen/TabsScreenViewManager.kt +26 -19
  91. package/android/src/main/java/com/swmansion/rnscreens/utils/DimensionUtils.kt +14 -7
  92. package/android/src/main/java/com/swmansion/rnscreens/utils/RNSLog.kt +22 -6
  93. package/ios/RNSEnums.h +14 -1
  94. package/ios/helpers/image/RNSImageLoadingHelper.h +7 -0
  95. package/ios/helpers/image/RNSImageLoadingHelper.mm +9 -0
  96. package/ios/legacy/RNSBarButtonItem.mm +15 -0
  97. package/ios/legacy/RNSConvert.h +6 -0
  98. package/ios/legacy/RNSConvert.mm +17 -0
  99. package/ios/legacy/RNSNavigationBar.h +31 -0
  100. package/ios/legacy/RNSNavigationBar.mm +15 -0
  101. package/ios/legacy/RNSScreen.mm +122 -12
  102. package/ios/legacy/RNSScreenContentWrapper.mm +4 -1
  103. package/ios/legacy/RNSScreenStack.mm +202 -15
  104. package/ios/legacy/RNSScreenStackHeaderConfig.h +30 -0
  105. package/ios/legacy/RNSScreenStackHeaderConfig.mm +70 -17
  106. package/ios/legacy/RNSScreenStackHeaderSubview.mm +33 -0
  107. package/ios/safe-area/RNSSafeAreaViewComponentView.mm +1 -12
  108. package/ios/stack/conversion/RNSConversions-Stack.h +30 -0
  109. package/ios/stack/conversion/RNSConversions-Stack.mm +79 -0
  110. package/ios/stack/header/RNSHeaderItemVisibilityPriority.h +9 -0
  111. package/ios/stack/header/RNSStackHeaderConfigComponentView.h +4 -0
  112. package/ios/stack/header/RNSStackHeaderConfigComponentView.mm +40 -6
  113. package/ios/stack/header/RNSStackHeaderConfigDataProviding.h +5 -0
  114. package/ios/stack/header/RNSStackHeaderContentFactory.mm +3 -0
  115. package/ios/stack/header/RNSStackHeaderItemComponentView.h +9 -2
  116. package/ios/stack/header/RNSStackHeaderItemComponentView.mm +57 -3
  117. package/ios/stack/header/RNSStackHeaderItemDataProviding.h +5 -0
  118. package/ios/stack/header/RNSStackHeaderMenuCoordinator.h +13 -0
  119. package/ios/stack/header/RNSStackHeaderMenuCoordinator.mm +24 -0
  120. package/ios/stack/header/RNSStackHeaderMenuFinder.h +8 -1
  121. package/ios/stack/header/RNSStackHeaderMenuFinder.mm +26 -9
  122. package/ios/stack/screen/RNSStackScreenComponentView.h +2 -1
  123. package/ios/stack/screen/RNSStackScreenComponentView.mm +23 -0
  124. package/ios/stack/screen/RNSStackScreenHeaderCoordinator.mm +81 -8
  125. package/ios/tabs/RNSTabBarAppearanceCoordinator.mm +79 -16
  126. package/ios/tabs/RNSTabBarItemCoordinator.h +16 -0
  127. package/ios/tabs/RNSTabBarItemCoordinator.mm +143 -0
  128. package/ios/tabs/bottom-accessory/RNSTabsBottomAccessoryWrapperView.h +24 -0
  129. package/ios/tabs/bottom-accessory/RNSTabsBottomAccessoryWrapperView.mm +22 -0
  130. package/ios/tabs/conversion/RNSConversions-Tabs.h +22 -0
  131. package/ios/tabs/conversion/RNSConversions-Tabs.mm +97 -3
  132. package/ios/tabs/host/RNSTabBarController.h +16 -0
  133. package/ios/tabs/host/RNSTabBarController.mm +516 -107
  134. package/ios/tabs/host/RNSTabsHostComponentView.h +2 -0
  135. package/ios/tabs/host/RNSTabsHostComponentView.mm +32 -2
  136. package/ios/tabs/screen/RNSTabsScreenComponentView.h +21 -0
  137. package/ios/tabs/screen/RNSTabsScreenComponentView.mm +54 -69
  138. package/ios/tabs/screen/RNSTabsScreenViewController.mm +1 -0
  139. package/ios/utils/RNSDefines.h +12 -7
  140. package/lib/commonjs/components/stack/header/StackHeaderConfig.android.js +3 -2
  141. package/lib/commonjs/components/stack/header/StackHeaderConfig.android.js.map +1 -1
  142. package/lib/commonjs/components/stack/header/StackHeaderConfig.ios.js +8 -3
  143. package/lib/commonjs/components/stack/header/StackHeaderConfig.ios.js.map +1 -1
  144. package/lib/commonjs/components/stack/header/StackHeaderConfig.js +17 -0
  145. package/lib/commonjs/components/stack/header/StackHeaderConfig.js.map +1 -0
  146. package/lib/commonjs/components/stack/header/ios/StackHeaderItem.ios.js +4 -1
  147. package/lib/commonjs/components/stack/header/ios/StackHeaderItem.ios.js.map +1 -1
  148. package/lib/commonjs/components/stack/host/StackHost.js +8 -1
  149. package/lib/commonjs/components/stack/host/StackHost.js.map +1 -1
  150. package/lib/commonjs/components/stack/host/StackHost.web.js +0 -2
  151. package/lib/commonjs/components/stack/host/StackHost.web.js.map +1 -1
  152. package/lib/commonjs/components/stack/index.js.map +1 -1
  153. package/lib/commonjs/components/tabs/host/TabsHost.ios.js +2 -0
  154. package/lib/commonjs/components/tabs/host/TabsHost.ios.js.map +1 -1
  155. package/lib/commonjs/components/tabs/host/{TabsHost.web.js → TabsHost.js} +1 -1
  156. package/lib/commonjs/components/tabs/host/TabsHost.js.map +1 -0
  157. package/lib/commonjs/components/tabs/index.js.map +1 -1
  158. package/lib/commonjs/components/tabs/screen/TabsScreen.android.js +3 -1
  159. package/lib/commonjs/components/tabs/screen/TabsScreen.android.js.map +1 -1
  160. package/lib/commonjs/components/tabs/screen/TabsScreen.ios.js +10 -1
  161. package/lib/commonjs/components/tabs/screen/TabsScreen.ios.js.map +1 -1
  162. package/lib/commonjs/components/tabs/screen/{TabsScreen.web.js → TabsScreen.js} +1 -1
  163. package/lib/commonjs/components/tabs/screen/TabsScreen.js.map +1 -0
  164. package/lib/commonjs/components/{stack/header/StackHeaderConfig.d.js → tabs/screen/TabsScreenIcon.types.js} +1 -1
  165. package/lib/commonjs/components/{stack/header/StackHeaderConfig.d.js.map → tabs/screen/TabsScreenIcon.types.js.map} +1 -1
  166. package/lib/commonjs/fabric/legacy/ScreenStackHeaderSubviewNativeComponent.js.map +1 -1
  167. package/lib/commonjs/fabric/legacy/ScreenStackNativeComponent.js.map +1 -1
  168. package/lib/commonjs/fabric/stack/StackHeaderConfigIOSNativeComponent.js.map +1 -1
  169. package/lib/commonjs/fabric/stack/StackHeaderItemIOSNativeComponent.js.map +1 -1
  170. package/lib/commonjs/fabric/tabs/TabsHostIOSNativeComponent.js.map +1 -1
  171. package/lib/commonjs/fabric/tabs/TabsScreenAndroidNativeComponent.js.map +1 -1
  172. package/lib/commonjs/fabric/tabs/TabsScreenIOSNativeComponent.js +1 -1
  173. package/lib/commonjs/fabric/tabs/TabsScreenIOSNativeComponent.js.map +1 -1
  174. package/lib/commonjs/legacy/components/ScreenStackItem.js +31 -11
  175. package/lib/commonjs/legacy/components/ScreenStackItem.js.map +1 -1
  176. package/lib/module/components/stack/header/StackHeaderConfig.android.js +4 -3
  177. package/lib/module/components/stack/header/StackHeaderConfig.android.js.map +1 -1
  178. package/lib/module/components/stack/header/StackHeaderConfig.ios.js +9 -4
  179. package/lib/module/components/stack/header/StackHeaderConfig.ios.js.map +1 -1
  180. package/lib/module/components/stack/header/StackHeaderConfig.js +10 -0
  181. package/lib/module/components/stack/header/StackHeaderConfig.js.map +1 -0
  182. package/lib/module/components/stack/header/ios/StackHeaderItem.ios.js +4 -1
  183. package/lib/module/components/stack/header/ios/StackHeaderItem.ios.js.map +1 -1
  184. package/lib/module/components/stack/host/StackHost.js +9 -2
  185. package/lib/module/components/stack/host/StackHost.js.map +1 -1
  186. package/lib/module/components/stack/host/StackHost.web.js +0 -3
  187. package/lib/module/components/stack/host/StackHost.web.js.map +1 -1
  188. package/lib/module/components/stack/index.js.map +1 -1
  189. package/lib/module/components/tabs/host/TabsHost.ios.js +2 -0
  190. package/lib/module/components/tabs/host/TabsHost.ios.js.map +1 -1
  191. package/{src/components/tabs/host/TabsHost.web.tsx → lib/module/components/tabs/host/TabsHost.js} +1 -2
  192. package/lib/module/components/tabs/host/TabsHost.js.map +1 -0
  193. package/lib/module/components/tabs/index.js.map +1 -1
  194. package/lib/module/components/tabs/screen/TabsScreen.android.js +3 -1
  195. package/lib/module/components/tabs/screen/TabsScreen.android.js.map +1 -1
  196. package/lib/module/components/tabs/screen/TabsScreen.ios.js +10 -1
  197. package/lib/module/components/tabs/screen/TabsScreen.ios.js.map +1 -1
  198. package/{src/components/tabs/screen/TabsScreen.web.tsx → lib/module/components/tabs/screen/TabsScreen.js} +1 -2
  199. package/lib/module/components/tabs/screen/TabsScreen.js.map +1 -0
  200. package/lib/module/components/tabs/screen/TabsScreenIcon.types.js +2 -0
  201. package/lib/module/components/{stack/header/StackHeaderConfig.d.js.map → tabs/screen/TabsScreenIcon.types.js.map} +1 -1
  202. package/lib/module/fabric/legacy/ScreenStackHeaderSubviewNativeComponent.js.map +1 -1
  203. package/lib/module/fabric/legacy/ScreenStackNativeComponent.js.map +1 -1
  204. package/lib/module/fabric/stack/StackHeaderConfigIOSNativeComponent.js.map +1 -1
  205. package/lib/module/fabric/stack/StackHeaderItemIOSNativeComponent.js.map +1 -1
  206. package/lib/module/fabric/tabs/TabsHostIOSNativeComponent.js.map +1 -1
  207. package/lib/module/fabric/tabs/TabsScreenAndroidNativeComponent.js.map +1 -1
  208. package/lib/module/fabric/tabs/TabsScreenIOSNativeComponent.js +1 -1
  209. package/lib/module/fabric/tabs/TabsScreenIOSNativeComponent.js.map +1 -1
  210. package/lib/module/legacy/components/ScreenStackItem.js +31 -11
  211. package/lib/module/legacy/components/ScreenStackItem.js.map +1 -1
  212. package/lib/typescript/components/stack/header/StackHeaderConfig.android.d.ts +6 -3
  213. package/lib/typescript/components/stack/header/StackHeaderConfig.android.d.ts.map +1 -1
  214. package/lib/typescript/components/stack/header/StackHeaderConfig.android.types.d.ts +4 -3
  215. package/lib/typescript/components/stack/header/StackHeaderConfig.android.types.d.ts.map +1 -1
  216. package/lib/typescript/components/stack/header/StackHeaderConfig.d.ts +8 -0
  217. package/lib/typescript/components/stack/header/StackHeaderConfig.d.ts.map +1 -0
  218. package/lib/typescript/components/stack/header/StackHeaderConfig.ios.d.ts +6 -3
  219. package/lib/typescript/components/stack/header/StackHeaderConfig.ios.d.ts.map +1 -1
  220. package/lib/typescript/components/stack/header/StackHeaderConfig.ios.types.d.ts +137 -2
  221. package/lib/typescript/components/stack/header/StackHeaderConfig.ios.types.d.ts.map +1 -1
  222. package/lib/typescript/components/stack/header/StackHeaderConfig.types.d.ts +2 -0
  223. package/lib/typescript/components/stack/header/StackHeaderConfig.types.d.ts.map +1 -1
  224. package/lib/typescript/components/stack/header/ios/StackHeaderItem.ios.d.ts.map +1 -1
  225. package/lib/typescript/components/stack/header/ios/StackHeaderItem.ios.types.d.ts +5 -0
  226. package/lib/typescript/components/stack/header/ios/StackHeaderItem.ios.types.d.ts.map +1 -1
  227. package/lib/typescript/components/stack/host/StackHost.d.ts.map +1 -1
  228. package/lib/typescript/components/stack/host/StackHost.types.d.ts +24 -1
  229. package/lib/typescript/components/stack/host/StackHost.types.d.ts.map +1 -1
  230. package/lib/typescript/components/stack/host/StackHost.web.d.ts +1 -3
  231. package/lib/typescript/components/stack/host/StackHost.web.d.ts.map +1 -1
  232. package/lib/typescript/components/stack/index.d.ts +4 -3
  233. package/lib/typescript/components/stack/index.d.ts.map +1 -1
  234. package/lib/typescript/components/tabs/host/TabsHost.d.ts +5 -0
  235. package/lib/typescript/components/tabs/host/TabsHost.d.ts.map +1 -0
  236. package/lib/typescript/components/tabs/host/TabsHost.ios.d.ts.map +1 -1
  237. package/lib/typescript/components/tabs/host/TabsHost.ios.types.d.ts +56 -0
  238. package/lib/typescript/components/tabs/host/TabsHost.ios.types.d.ts.map +1 -1
  239. package/lib/typescript/components/tabs/index.d.ts +4 -6
  240. package/lib/typescript/components/tabs/index.d.ts.map +1 -1
  241. package/lib/typescript/components/tabs/screen/TabsScreen.android.d.ts.map +1 -1
  242. package/lib/typescript/components/tabs/screen/TabsScreen.android.types.d.ts +42 -7
  243. package/lib/typescript/components/tabs/screen/TabsScreen.android.types.d.ts.map +1 -1
  244. package/lib/typescript/components/tabs/screen/TabsScreen.d.ts +5 -0
  245. package/lib/typescript/components/tabs/screen/TabsScreen.d.ts.map +1 -0
  246. package/lib/typescript/components/tabs/screen/TabsScreen.ios.d.ts.map +1 -1
  247. package/lib/typescript/components/tabs/screen/TabsScreen.ios.types.d.ts +78 -13
  248. package/lib/typescript/components/tabs/screen/TabsScreen.ios.types.d.ts.map +1 -1
  249. package/lib/typescript/components/tabs/screen/TabsScreenIcon.types.d.ts +91 -0
  250. package/lib/typescript/components/tabs/screen/TabsScreenIcon.types.d.ts.map +1 -0
  251. package/lib/typescript/components/tabs/screen/index.d.ts +1 -0
  252. package/lib/typescript/components/tabs/screen/index.d.ts.map +1 -1
  253. package/lib/typescript/fabric/legacy/ScreenStackHeaderSubviewNativeComponent.d.ts +2 -0
  254. package/lib/typescript/fabric/legacy/ScreenStackHeaderSubviewNativeComponent.d.ts.map +1 -1
  255. package/lib/typescript/fabric/legacy/ScreenStackNativeComponent.d.ts +1 -0
  256. package/lib/typescript/fabric/legacy/ScreenStackNativeComponent.d.ts.map +1 -1
  257. package/lib/typescript/fabric/stack/StackHeaderConfigIOSNativeComponent.d.ts +4 -0
  258. package/lib/typescript/fabric/stack/StackHeaderConfigIOSNativeComponent.d.ts.map +1 -1
  259. package/lib/typescript/fabric/stack/StackHeaderItemIOSNativeComponent.d.ts +6 -0
  260. package/lib/typescript/fabric/stack/StackHeaderItemIOSNativeComponent.d.ts.map +1 -1
  261. package/lib/typescript/fabric/tabs/TabsHostIOSNativeComponent.d.ts +3 -0
  262. package/lib/typescript/fabric/tabs/TabsHostIOSNativeComponent.d.ts.map +1 -1
  263. package/lib/typescript/fabric/tabs/TabsScreenAndroidNativeComponent.d.ts +6 -0
  264. package/lib/typescript/fabric/tabs/TabsScreenAndroidNativeComponent.d.ts.map +1 -1
  265. package/lib/typescript/fabric/tabs/TabsScreenIOSNativeComponent.d.ts +10 -1
  266. package/lib/typescript/fabric/tabs/TabsScreenIOSNativeComponent.d.ts.map +1 -1
  267. package/lib/typescript/legacy/components/ScreenStackItem.d.ts +25 -0
  268. package/lib/typescript/legacy/components/ScreenStackItem.d.ts.map +1 -1
  269. package/lib/typescript/legacy/components/helpers/prepareHeaderBarButtonItems.d.ts +4 -0
  270. package/lib/typescript/legacy/components/helpers/prepareHeaderBarButtonItems.d.ts.map +1 -1
  271. package/lib/typescript/legacy/types.d.ts +30 -0
  272. package/lib/typescript/legacy/types.d.ts.map +1 -1
  273. package/package.json +4 -2
  274. package/src/components/stack/header/StackHeaderConfig.android.tsx +6 -20
  275. package/src/components/stack/header/StackHeaderConfig.android.types.ts +4 -3
  276. package/src/components/stack/header/StackHeaderConfig.ios.tsx +10 -16
  277. package/src/components/stack/header/StackHeaderConfig.ios.types.ts +148 -2
  278. package/src/components/stack/header/StackHeaderConfig.tsx +12 -0
  279. package/src/components/stack/header/StackHeaderConfig.types.ts +2 -0
  280. package/src/components/stack/header/ios/StackHeaderItem.ios.tsx +9 -1
  281. package/src/components/stack/header/ios/StackHeaderItem.ios.types.ts +5 -0
  282. package/src/components/stack/host/StackHost.tsx +10 -3
  283. package/src/components/stack/host/StackHost.types.ts +25 -1
  284. package/src/components/stack/host/StackHost.web.tsx +1 -4
  285. package/src/components/stack/index.ts +9 -1
  286. package/src/components/tabs/host/TabsHost.ios.tsx +2 -0
  287. package/src/components/tabs/host/TabsHost.ios.types.ts +60 -0
  288. package/src/components/tabs/host/TabsHost.tsx +7 -0
  289. package/src/components/tabs/index.ts +9 -0
  290. package/src/components/tabs/screen/TabsScreen.android.tsx +8 -3
  291. package/src/components/tabs/screen/TabsScreen.android.types.ts +42 -7
  292. package/src/components/tabs/screen/TabsScreen.ios.tsx +22 -5
  293. package/src/components/tabs/screen/TabsScreen.ios.types.ts +79 -17
  294. package/src/components/tabs/screen/TabsScreen.tsx +7 -0
  295. package/src/components/tabs/screen/TabsScreenIcon.types.ts +117 -0
  296. package/src/components/tabs/screen/index.ts +1 -0
  297. package/src/fabric/legacy/ScreenStackHeaderSubviewNativeComponent.ts +6 -0
  298. package/src/fabric/legacy/ScreenStackNativeComponent.ts +2 -0
  299. package/src/fabric/stack/StackHeaderConfigIOSNativeComponent.ts +11 -0
  300. package/src/fabric/stack/StackHeaderItemIOSNativeComponent.ts +11 -0
  301. package/src/fabric/tabs/TabsHostIOSNativeComponent.ts +7 -0
  302. package/src/fabric/tabs/TabsScreenAndroidNativeComponent.ts +9 -0
  303. package/src/fabric/tabs/TabsScreenIOSNativeComponent.ts +20 -2
  304. package/src/legacy/components/ScreenStackItem.tsx +59 -12
  305. package/src/legacy/types.ts +31 -0
  306. package/android/src/main/java/com/swmansion/rnscreens/legacy/ScreenModalFragment.kt +0 -281
  307. package/android/src/main/java/com/swmansion/rnscreens/legacy/bottomsheet/BottomSheetDialogRootView.kt +0 -104
  308. package/android/src/main/java/com/swmansion/rnscreens/legacy/bottomsheet/BottomSheetDialogScreen.kt +0 -26
  309. package/lib/commonjs/components/stack/header/StackHeaderConfig.web.js +0 -10
  310. package/lib/commonjs/components/stack/header/StackHeaderConfig.web.js.map +0 -1
  311. package/lib/commonjs/components/tabs/host/TabsHost.d.js +0 -2
  312. package/lib/commonjs/components/tabs/host/TabsHost.d.js.map +0 -1
  313. package/lib/commonjs/components/tabs/host/TabsHost.web.js.map +0 -1
  314. package/lib/commonjs/components/tabs/screen/TabsScreen.d.js +0 -2
  315. package/lib/commonjs/components/tabs/screen/TabsScreen.d.js.map +0 -1
  316. package/lib/commonjs/components/tabs/screen/TabsScreen.web.js.map +0 -1
  317. package/lib/module/components/stack/header/StackHeaderConfig.d.js +0 -2
  318. package/lib/module/components/stack/header/StackHeaderConfig.web.js +0 -4
  319. package/lib/module/components/stack/header/StackHeaderConfig.web.js.map +0 -1
  320. package/lib/module/components/tabs/host/TabsHost.d.js +0 -2
  321. package/lib/module/components/tabs/host/TabsHost.d.js.map +0 -1
  322. package/lib/module/components/tabs/host/TabsHost.web.js +0 -4
  323. package/lib/module/components/tabs/host/TabsHost.web.js.map +0 -1
  324. package/lib/module/components/tabs/screen/TabsScreen.d.js +0 -2
  325. package/lib/module/components/tabs/screen/TabsScreen.d.js.map +0 -1
  326. package/lib/module/components/tabs/screen/TabsScreen.web.js +0 -4
  327. package/lib/module/components/tabs/screen/TabsScreen.web.js.map +0 -1
  328. package/lib/typescript/components/stack/header/StackHeaderConfig.web.d.ts +0 -7
  329. package/lib/typescript/components/stack/header/StackHeaderConfig.web.d.ts.map +0 -1
  330. package/lib/typescript/components/tabs/host/TabsHost.web.d.ts +0 -7
  331. package/lib/typescript/components/tabs/host/TabsHost.web.d.ts.map +0 -1
  332. package/lib/typescript/components/tabs/screen/TabsScreen.web.d.ts +0 -7
  333. package/lib/typescript/components/tabs/screen/TabsScreen.web.d.ts.map +0 -1
  334. package/src/components/stack/header/StackHeaderConfig.d.ts +0 -22
  335. package/src/components/stack/header/StackHeaderConfig.web.tsx +0 -5
  336. package/src/components/tabs/host/TabsHost.d.ts +0 -12
  337. package/src/components/tabs/screen/TabsScreen.d.ts +0 -12
@@ -5,6 +5,7 @@
5
5
  #import <objc/runtime.h>
6
6
  #import <limits>
7
7
  #import "NSString+RNSUtility.h"
8
+ #import "RNSDefines.h"
8
9
  #import "RNSLog.h"
9
10
  #import "RNSParentContainerItemRegistry.h"
10
11
  #import "RNSScreenWindowTraits.h"
@@ -70,6 +71,11 @@ static void rns_pushViewController(__unsafe_unretained id self,
70
71
  @implementation RNSTabBarController {
71
72
  NSArray<RNSTabsScreenViewController *> *_Nullable _tabScreenControllers;
72
73
 
74
+ /// Controllers currently installed in UIKit (see `installScreenControllers:animated:`).
75
+ /// On the UITab path `UITabBarController.viewControllers` is empty once `tabs` is set,
76
+ /// so the installed set is tracked here; on the legacy path UIKit itself is the source of truth.
77
+ NSArray<RNSTabsScreenViewController *> *_Nullable _installedScreenControllers;
78
+
73
79
  /// This property is nullable until first container update. Later it MUST NOT be nil.
74
80
  RNSTabsNavigationState *_Nullable _navigationState;
75
81
 
@@ -86,20 +92,46 @@ static void rns_pushViewController(__unsafe_unretained id self,
86
92
  /// delegate handling). Setter overrides skip reconciliation while this flag is set.
87
93
  BOOL _isHandlingExplicitSelectionUpdate;
88
94
 
95
+ /// UITab path only. YES while this controller itself writes `tabs` / `selectedTab` /
96
+ /// `selectedViewController`. UIKit sends `didSelectTab:` synchronously from inside those writes -
97
+ /// this flag filters them out, so that only UIKit-driven (user) selections reach the delegate
98
+ /// logic. Marking user selections instead is not viable: for `UISearchTab`, a mount transaction
99
+ /// can land between `shouldSelectTab:` and `didSelectTab:`.
100
+ BOOL _isApplyingProgrammaticTabSelection;
101
+
102
+ /// UITab path only. Set in `tabBar:didSelectItem:` when the user selects the More tab while
103
+ /// another tab is active. Tapping More does not necessarily show the More list - the stack may
104
+ /// re-display a hosted screen retained from a previous visit - so the decision between emitting
105
+ /// `onMoreTabSelected` and progressing state to the re-displayed screen is deferred to
106
+ /// `willShowViewController:`, which reports what actually shows.
107
+ BOOL _pendingMoreTabSelectedEmit;
108
+
89
109
  RNSTabsNavigationStateObserverRegistry *_observerRegistry;
90
110
 
91
111
  RNSParentContainerItemRegistry *_Nonnull _parentContainerRegistry;
112
+
113
+ /// Screen key of the prominent tab (iOS 27+); nil when not set, letting UIKit decide. Kept as the screen key,
114
+ /// because `UISearchTab` gets a system-assigned identifier & the tabs are rebuilt on children updates.
115
+ NSString *_Nullable _prominentScreenKey;
116
+
117
+ BOOL _needsUpdateOfProminentIdentifier;
92
118
  }
93
119
 
94
120
  - (instancetype)init
95
121
  {
96
122
  if (self = [super init]) {
97
123
  _tabScreenControllers = nil;
124
+ _installedScreenControllers = nil;
98
125
  _tabBarAppearanceCoordinator = [RNSTabBarAppearanceCoordinator new];
126
+ _tabBarItemCoordinator = [RNSTabBarItemCoordinator new];
99
127
  _tabsHostComponentView = nil;
100
128
  _navigationState = nil;
101
129
  _pendingStateUpdate = nil;
102
130
  _shouldProgressStateOnMoreNavigationControllerPush = NO;
131
+ _isApplyingProgrammaticTabSelection = NO;
132
+ _pendingMoreTabSelectedEmit = NO;
133
+ _prominentScreenKey = nil;
134
+ _needsUpdateOfProminentIdentifier = NO;
103
135
  _observerRegistry = [RNSTabsNavigationStateObserverRegistry new];
104
136
  _parentContainerRegistry = [RNSParentContainerItemRegistry new];
105
137
 
@@ -156,9 +188,9 @@ static void rns_pushViewController(__unsafe_unretained id self,
156
188
 
157
189
  - (nullable UIScrollView *)resolveCurrentContentScrollView
158
190
  {
159
- // `selectedViewController` may be the `moreNavigationController` (a `UINavigationController`) -
191
+ // The effective selection may be the `moreNavigationController` (a `UINavigationController`) -
160
192
  // we only resolve for our own tab screens.
161
- UIViewController *selectedController = self.selectedViewController;
193
+ UIViewController *selectedController = [self effectiveSelectedViewController];
162
194
  if (![selectedController isKindOfClass:RNSTabsScreenViewController.class]) {
163
195
  return nil;
164
196
  }
@@ -191,7 +223,32 @@ static void rns_pushViewController(__unsafe_unretained id self,
191
223
 
192
224
  - (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
193
225
  {
194
- RNSLog(@"TabBar: %@ didSelectItem: %@", tabBar, item);
226
+ #if RNS_MORE_NAVIGATION_CONTROLLER_AVAILABLE && RNS_UITAB_API_SDK_AVAILABLE
227
+ if (RNS_UITAB_API_ENABLED) {
228
+ // The only direct "user tapped More" signal on the UITab path - no UITab delegate covers More.
229
+ // Mirrors the More branch of the legacy `shouldSelectViewController:`: enforce selection
230
+ // prevention on the More stack top before UIKit displays it.
231
+ if ([self isMoreNavigationControllerPresentInTabBar] && item == self.moreNavigationController.tabBarItem) {
232
+ [self prepareForMoreNavigationControllerHandlingIfNeeded];
233
+ [self disableNavigationBarInMoreNavigationController];
234
+ UIViewController *_Nullable poppedViewController =
235
+ [self popToRootInMoreNavigationControllerRespectSelectionPrevention:YES animated:NO];
236
+ if (poppedViewController != nil) {
237
+ [self
238
+ onDidPreventUserFromSelectingViewControllerWithKey:[self screenKeyForViewController:poppedViewController]];
239
+ }
240
+
241
+ // At this point, the `view.selectedViewController` is not yet updated and points
242
+ // to previous tab's viewController, which allows us to tell if whether
243
+ // we've just navigated to More tab or we've been there earlier
244
+ // Verified on both iOS 26 and 27. There's no other simple way to check that
245
+ // since `didSelectTab:` doesn't fire for More tab
246
+ if (![self isSelectedViewControllerTheMoreNavigationController]) {
247
+ _pendingMoreTabSelectedEmit = YES;
248
+ }
249
+ }
250
+ }
251
+ #endif // RNS_MORE_NAVIGATION_CONTROLLER_AVAILABLE && RNS_UITAB_API_SDK_AVAILABLE
195
252
  }
196
253
 
197
254
  - (void)setSelectedIndex:(NSUInteger)selectedIndex
@@ -210,16 +267,27 @@ static void rns_pushViewController(__unsafe_unretained id self,
210
267
  }
211
268
  }
212
269
 
270
+ #if RNS_UITAB_API_SDK_AVAILABLE
271
+ - (void)setSelectedTab:(UITab *)selectedTab API_AVAILABLE(ios(18.0))
272
+ {
273
+ [super setSelectedTab:selectedTab];
274
+ if (!_isHandlingExplicitSelectionUpdate) {
275
+ [self reconcileNavigationStateWithUIKitState];
276
+ }
277
+ }
278
+ #endif // RNS_UITAB_API_SDK_AVAILABLE
279
+
213
280
  - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
214
281
  {
215
282
  [super traitCollectionDidChange:previousTraitCollection];
216
283
 
217
- if (previousTraitCollection == nil || self.selectedViewController == nil) {
284
+ UIViewController *_Nullable selectedController = [self effectiveSelectedViewController];
285
+ if (previousTraitCollection == nil || selectedController == nil) {
218
286
  return;
219
287
  }
220
288
 
221
289
  if (self.traitCollection.horizontalSizeClass != previousTraitCollection.horizontalSizeClass &&
222
- [self isViewControllerHostedByMoreNavigationController:self.selectedViewController]) {
290
+ [self isViewControllerHostedByMoreNavigationController:selectedController]) {
223
291
  [self disableNavigationBarInMoreNavigationController];
224
292
  }
225
293
  }
@@ -275,12 +343,16 @@ static void rns_pushViewController(__unsafe_unretained id self,
275
343
  - (void)performContainerUpdate
276
344
  {
277
345
  _isHandlingExplicitSelectionUpdate = YES;
346
+ [self createTabBarItemsIfNeeded];
278
347
  [self updateChildViewControllersIfNeeded];
348
+ [self updateSearchTabsIfNeeded];
279
349
  [self updateSelectedViewControllerIfNeeded];
280
350
  _isHandlingExplicitSelectionUpdate = NO;
281
351
 
352
+ [self updateTabBarItemsIfNeeded];
282
353
  [self updateTabBarAppearanceIfNeeded];
283
354
  [self updateTabBarA11yIfNeeded];
355
+ [self updateProminentIdentifierIfNeeded];
284
356
  [self updateOrientationIfNeeded];
285
357
  }
286
358
 
@@ -302,18 +374,15 @@ static void rns_pushViewController(__unsafe_unretained id self,
302
374
  return NO;
303
375
  }
304
376
 
305
- UIViewController *currSelectedViewController = self.selectedViewController;
306
-
307
377
  RCTAssert(![NSString rnscreens_isBlankOrNull:screenKey],
308
378
  @"[RNScreens] The screenKey MUST NOT be null if the view controller is not null");
309
379
 
310
380
  [self progressNavigationState:screenKey withOrigin:actionOrigin];
311
381
 
312
- if (currSelectedViewController == nextSelectedViewController) {
313
- return YES;
382
+ if (![self isScreenControllerCurrentlySelected:nextSelectedViewController]) {
383
+ [self applySelectedScreenController:nextSelectedViewController];
314
384
  }
315
385
 
316
- [self setSelectedViewController:nextSelectedViewController];
317
386
  return YES;
318
387
  }
319
388
 
@@ -325,30 +394,22 @@ static void rns_pushViewController(__unsafe_unretained id self,
325
394
  */
326
395
  - (void)updateNavigationStateOnModelUpdate
327
396
  {
328
- [self progressNavigationState:[self screenKeyForSelectedViewController] withOrigin:RNSTabsActionOriginUser];
397
+ UIViewController *viewController = [self effectiveSelectedViewController];
398
+ [self progressNavigationState:[self screenKeyForViewController:viewController] withOrigin:RNSTabsActionOriginUser];
329
399
  }
330
400
 
331
- - (void)userDidRepeatViewControllerSelection:(nonnull UIViewController *)viewController
401
+ - (void)userDidRepeatSelectionOfScreenController:(nonnull RNSTabsScreenViewController *)screenController
332
402
  {
333
- RCTAssert(self.selectedViewController == viewController,
334
- @"[RNScreens] Expected UIKit to update selectedViewController");
335
-
336
- if ([self isSelectedViewControllerTheMoreNavigationController]) {
337
- // We don't want to run neither state update nor side effects.
338
- return;
339
- }
403
+ RCTAssert([self isScreenControllerCurrentlySelected:screenController],
404
+ @"[RNScreens] Expected the repeated screen controller to be the current selection");
340
405
 
341
- [self updateNavigationStateOnModelUpdate];
406
+ [self progressNavigationState:[self screenKeyForViewController:screenController] withOrigin:RNSTabsActionOriginUser];
342
407
 
343
408
  // After state progression we trigger the special effect.
344
- BOOL repeatedSelectionHandledBySpecialEffect = [[self selectedScreenViewController] tabScreenSelectedRepeatedly];
345
-
346
- auto *updateContext =
347
- [[RNSTabsNavigationStateUpdateContext alloc] initWithNavState:_navigationState
348
- isRepeated:YES
349
- hasTriggeredSpecialEffect:repeatedSelectionHandledBySpecialEffect
350
- actionOrigin:RNSTabsActionOriginUser];
351
- [_observerRegistry emitDidUpdateStateTo:_navigationState withContext:updateContext sender:self];
409
+ BOOL repeatedSelectionHandledBySpecialEffect = [screenController tabScreenSelectedRepeatedly];
410
+ [self emitSelectionUpdateWithOrigin:RNSTabsActionOriginUser
411
+ repeated:YES
412
+ hasTriggeredSpecialEffect:repeatedSelectionHandledBySpecialEffect];
352
413
  }
353
414
 
354
415
  - (void)userDidSelectViewController:(nonnull UIViewController *)viewController
@@ -357,7 +418,7 @@ static void rns_pushViewController(__unsafe_unretained id self,
357
418
  RCTAssert(self.selectedViewController == viewController,
358
419
  @"[RNScreens] Expected UIKit to update selectedViewController");
359
420
 
360
- if ([self isSelectedViewControllerTheMoreNavigationController]) {
421
+ if (![self usesUITabAPI] && [self isSelectedViewControllerTheMoreNavigationController]) {
361
422
  [self disableNavigationBarInMoreNavigationController];
362
423
  [self prepareForMoreNavigationControllerHandlingIfNeeded];
363
424
 
@@ -366,14 +427,21 @@ static void rns_pushViewController(__unsafe_unretained id self,
366
427
  [_observerRegistry emitDidSelectMoreTabWithCurrentState:_navigationState sender:self];
367
428
  } else {
368
429
  [self updateNavigationStateOnModelUpdate];
369
- auto *updateContext = [[RNSTabsNavigationStateUpdateContext alloc] initWithNavState:_navigationState
370
- isRepeated:NO
371
- hasTriggeredSpecialEffect:NO
372
- actionOrigin:RNSTabsActionOriginUser];
373
- [_observerRegistry emitDidUpdateStateTo:_navigationState withContext:updateContext sender:self];
430
+ [self emitSelectionUpdateWithOrigin:RNSTabsActionOriginUser repeated:NO hasTriggeredSpecialEffect:NO];
374
431
  }
375
432
  }
376
433
 
434
+ - (void)emitSelectionUpdateWithOrigin:(RNSTabsActionOrigin)actionOrigin
435
+ repeated:(BOOL)repeated
436
+ hasTriggeredSpecialEffect:(BOOL)hasTriggeredSpecialEffect
437
+ {
438
+ auto *updateContext = [[RNSTabsNavigationStateUpdateContext alloc] initWithNavState:_navigationState
439
+ isRepeated:repeated
440
+ hasTriggeredSpecialEffect:hasTriggeredSpecialEffect
441
+ actionOrigin:actionOrigin];
442
+ [_observerRegistry emitDidUpdateStateTo:_navigationState withContext:updateContext sender:self];
443
+ }
444
+
377
445
  - (void)onDidPreventUserFromSelectingViewControllerWithKey:(nonnull NSString *)screenKey
378
446
  {
379
447
  [_observerRegistry emitPreventedSelectionOf:screenKey currentState:_navigationState sender:self];
@@ -390,10 +458,38 @@ static void rns_pushViewController(__unsafe_unretained id self,
390
458
  return screenViewController.isPreventNativeSelectionEnabled;
391
459
  }
392
460
 
393
- #pragma mark - UITabBarControllerDelegate
461
+ /// Shared handling for the `shouldSelect` delegate callbacks - the legacy
462
+ /// `tabBarController:shouldSelectViewController:` and the `UITab` `tabBarController:shouldSelectTab:`.
463
+ - (BOOL)interceptUserSelectionOfViewController:(nonnull UIViewController *)viewController
464
+ {
465
+ BOOL repeatedSelection = viewController == self.selectedViewController;
466
+ #if RNS_UITAB_API_SDK_AVAILABLE
467
+ if (RNS_UITAB_API_ENABLED) {
468
+ repeatedSelection = viewController.tab == self.selectedTab && ![self isMoreNavigationControllerTabBarItemSelected];
469
+ }
470
+ #endif // RNS_UITAB_API_SDK_AVAILABLE
471
+
472
+ if (repeatedSelection) {
473
+ // On repeated selection we block the native *pop to root* effect (works from iOS 26) that
474
+ // interferes with our implementation (necessary for controlled tabs). The did-select callback
475
+ // won't fire on a blocked selection, so we trigger the state update here.
476
+ if (![self isViewControllerTheMoreNavigationController:viewController]) {
477
+ [self userDidRepeatSelectionOfScreenController:static_cast<RNSTabsScreenViewController *>(viewController)];
478
+ }
479
+ return YES;
480
+ }
481
+
482
+ if ([self shouldPreventNativeTabSelection:viewController]) {
483
+ // Ideally we'd call this AFTER we prevent, but there is no appropriate callback. As long as we
484
+ // emit the event asynchronously this is rather fine.
485
+ [self onDidPreventUserFromSelectingViewControllerWithKey:[self screenKeyForViewController:viewController]];
486
+ return YES;
487
+ }
394
488
 
395
- // These methods are not called on programatic selection!
396
- // They are called only when a user taps on tab bar.
489
+ return NO;
490
+ }
491
+
492
+ #pragma mark - UITabBarControllerDelegate
397
493
 
398
494
  - (BOOL)tabBarController:(UITabBarController *)tabBarController
399
495
  shouldSelectViewController:(UIViewController *)viewController
@@ -408,25 +504,8 @@ static void rns_pushViewController(__unsafe_unretained id self,
408
504
 
409
505
  // TODO: handle enforcing orientation with natively-driven tabs
410
506
 
411
- // Detect repeated selection and inform tabScreenController
412
- BOOL repeatedSelection = self.selectedViewController == viewController;
413
-
414
- if (repeatedSelection) {
415
- // On repeated selection we return false to prevent native *pop to root* effect that works only starting from iOS 26
416
- // and interferes with our implementation (which is necessary for controlled tabs).
417
-
418
- // We trigger the state update from here, because `tabBarController:didSelectViewController:` won't be called.
419
- [self userDidRepeatViewControllerSelection:viewController];
420
-
421
- return NO;
422
- }
423
-
424
- BOOL shouldPreventTabSelection = [self shouldPreventNativeTabSelection:viewController];
425
-
426
- if (shouldPreventTabSelection) {
427
- // Ideally we'd call this AFTER we prevent, but there is no appropriate callback.
428
- // As long as we emit the event asynchronously this is rather fine.
429
- [self onDidPreventUserFromSelectingViewControllerWithKey:[self screenKeyForViewController:viewController]];
507
+ BOOL selectionWasIntercepted = [self interceptUserSelectionOfViewController:viewController];
508
+ if (selectionWasIntercepted) {
430
509
  return NO;
431
510
  }
432
511
 
@@ -435,7 +514,8 @@ static void rns_pushViewController(__unsafe_unretained id self,
435
514
  // In such case, we want to pop to root.
436
515
  // We do it here, because in `tabBarController:didSelectViewController:` we won't receive
437
516
  // `moreNavigationController` in case there is already a tab pushed on the stack.
438
- if ([self isViewControllerTheMoreNavigationController:viewController]) {
517
+ // Legacy path only - on the UITab path More never arrives here; `tabBar:didSelectItem:` covers it.
518
+ if (![self usesUITabAPI] && [self isViewControllerTheMoreNavigationController:viewController]) {
439
519
  auto *poppedViewController = [self popToRootInMoreNavigationControllerRespectSelectionPrevention:YES animated:NO];
440
520
  if (poppedViewController != nil) {
441
521
  // We actually popped something -> let's notify JS realm of this fact.
@@ -462,7 +542,223 @@ static void rns_pushViewController(__unsafe_unretained id self,
462
542
  _isHandlingExplicitSelectionUpdate = NO;
463
543
  }
464
544
 
465
- #pragma mark - UINavigationControllerDelegate
545
+ #if RNS_UITAB_API_SDK_AVAILABLE
546
+
547
+ #pragma mark UITabBarControllerDelegate (UITab API)
548
+
549
+ - (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectTab:(UITab *)tab API_AVAILABLE(ios(18.0))
550
+ {
551
+ return [self tabBarController:tabBarController shouldSelectViewController:tab.viewController];
552
+ }
553
+
554
+ - (void)tabBarController:(UITabBarController *)tabBarController
555
+ didSelectTab:(UITab *)selectedTab
556
+ previousTab:(nullable UITab *)previousTab API_AVAILABLE(ios(18.0))
557
+ {
558
+ if (_isApplyingProgrammaticTabSelection) {
559
+ return;
560
+ }
561
+
562
+ RCTAssert(self.selectedTab == selectedTab, @"[RNScreens] Expected UIKit to update selectedTab");
563
+ [self tabBarController:tabBarController didSelectViewController:selectedTab.viewController];
564
+ }
565
+
566
+ #endif // RNS_UITAB_API_SDK_AVAILABLE
567
+
568
+ #pragma mark - UIKit configuration boundary
569
+
570
+ // Every UIKit read/write related to child installation & selection goes through the methods below.
571
+ // The legacy `viewControllers`-based API and the `UITab`-based API (iOS 26.1+) are mutually
572
+ // exclusive - funnelling the accesses through a single boundary is what lets the rest of the
573
+ // controller stay path-agnostic.
574
+
575
+ - (void)installScreenControllers:(nonnull NSArray<RNSTabsScreenViewController *> *)screenControllers
576
+ animated:(BOOL)animated
577
+ {
578
+ _installedScreenControllers = screenControllers;
579
+
580
+ #if RNS_UITAB_API_SDK_AVAILABLE
581
+ if (RNS_UITAB_API_ENABLED) {
582
+ UITab *_Nullable previouslySelectedTab = self.selectedTab;
583
+
584
+ // Restoring the stale `selectedTab` while More is active would yank the selection away.
585
+ BOOL shouldRestoreSelectedTab =
586
+ previouslySelectedTab != nil && ![self isMoreNavigationControllerTabBarItemSelected];
587
+
588
+ NSMutableArray<__kindof UITab *> *tabs = [NSMutableArray arrayWithCapacity:screenControllers.count];
589
+ for (RNSTabsScreenViewController *screenController in screenControllers) {
590
+ [tabs addObject:[self tabForTabScreenController:screenController]];
591
+ }
592
+ _isApplyingProgrammaticTabSelection = YES;
593
+ [self setTabs:tabs animated:animated];
594
+ [self assertAtMostOneSearchTabInstalled];
595
+
596
+ if (shouldRestoreSelectedTab && [tabs containsObject:previouslySelectedTab]) {
597
+ self.selectedTab = previouslySelectedTab;
598
+ }
599
+ _isApplyingProgrammaticTabSelection = NO;
600
+
601
+ // The prominent screen key might point to a tab created in this update - re-resolve it.
602
+ _needsUpdateOfProminentIdentifier = YES;
603
+ return;
604
+ }
605
+ #endif // RNS_UITAB_API_SDK_AVAILABLE
606
+
607
+ [self setViewControllers:screenControllers animated:animated];
608
+ }
609
+
610
+ #if RNS_UITAB_API_SDK_AVAILABLE
611
+ /// Debug-build sanity check run after the tabs are installed: UIKit gives only the system search
612
+ /// role special placement & behavior, so more than one `role: 'search'` screen is a config error.
613
+ - (void)assertAtMostOneSearchTabInstalled API_AVAILABLE(ios(18.0))
614
+ {
615
+ #if RCT_DEBUG
616
+ NSUInteger searchTabCount = 0;
617
+ for (UITab *tab in self.tabs) {
618
+ if ([tab isKindOfClass:UISearchTab.class]) {
619
+ searchTabCount += 1;
620
+ }
621
+ }
622
+ RCTAssert(searchTabCount <= 1,
623
+ @"[RNScreens] At most one tab screen can have `role: 'search'`, got %lu",
624
+ (unsigned long)searchTabCount);
625
+ #endif // RCT_DEBUG
626
+ }
627
+ #endif // RNS_UITAB_API_SDK_AVAILABLE
628
+
629
+ // Controllers currently installed in UIKit, not including more controller.
630
+ - (nonnull NSArray<RNSTabsScreenViewController *> *)installedScreenControllers
631
+ {
632
+ #if RNS_UITAB_API_SDK_AVAILABLE
633
+ if (RNS_UITAB_API_ENABLED) {
634
+ return _installedScreenControllers ?: @[];
635
+ }
636
+ #endif // RNS_UITAB_API_SDK_AVAILABLE
637
+ return self.viewControllers ?: @[];
638
+ }
639
+
640
+ - (void)applySelectedScreenController:(nonnull UIViewController *)screenController
641
+ {
642
+ #if RNS_UITAB_API_SDK_AVAILABLE
643
+ if (RNS_UITAB_API_ENABLED) {
644
+ // Both writes below are our own. UIKit sends `didSelectTab:` synchronously from inside them, and
645
+ // `didSelectTab:` must not treat that as a user selection - otherwise the state is progressed
646
+ // twice and a second, `user`-origin event is emitted.
647
+ _isApplyingProgrammaticTabSelection = YES;
648
+ if (![self isMoreNavigationControllerTabBarItemSelected]) {
649
+ RCTAssert(screenController.tab != nil,
650
+ @"[RNScreens] No installed UITab for screenKey: %@",
651
+ [self screenKeyForViewController:screenController]);
652
+ self.selectedTab = screenController.tab;
653
+ } else {
654
+ // While More is active `selectedTab` lags behind UIKit's selection, so the write goes through
655
+ // the legacy setter (our own override, so that reconciliation of implicit UIKit-driven updates
656
+ // keeps working).
657
+ [self setSelectedViewController:screenController];
658
+ }
659
+ _isApplyingProgrammaticTabSelection = NO;
660
+ return;
661
+ }
662
+ #endif // RNS_UITAB_API_SDK_AVAILABLE
663
+
664
+ // Goes through our own setter override, so that reconciliation of implicit UIKit-driven
665
+ // updates keeps working.
666
+ [self setSelectedViewController:screenController];
667
+ }
668
+
669
+ /// Whether `screenController` is already the effective current selection.
670
+ - (BOOL)isScreenControllerCurrentlySelected:(nonnull UIViewController *)screenController
671
+ {
672
+ if ([self isMoreNavigationControllerTabBarItemSelected]) {
673
+ return screenController == [self resolveMoreNavigationController].topViewController;
674
+ }
675
+
676
+ #if RNS_UITAB_API_SDK_AVAILABLE
677
+ if (RNS_UITAB_API_ENABLED) {
678
+ return screenController == self.selectedTab.viewController;
679
+ }
680
+ #endif // RNS_UITAB_API_SDK_AVAILABLE
681
+ return screenController == self.selectedViewController;
682
+ }
683
+
684
+ /// Effective current selection for reads, path-agnostic. On the UITab path `selectedViewController`
685
+ /// is not the source of truth and `selectedTab` lags behind while More is active, so More is
686
+ /// resolved explicitly: the hosted screen when one is displayed, the More navigation controller
687
+ /// itself otherwise.
688
+ - (nullable UIViewController *)effectiveSelectedViewController
689
+ {
690
+ if ([self isMoreNavigationControllerTabBarItemSelected]) {
691
+ UINavigationController *moreNavigationController = [self resolveMoreNavigationController];
692
+ UIViewController *_Nullable topViewController = moreNavigationController.topViewController;
693
+ return [topViewController isKindOfClass:RNSTabsScreenViewController.class] ? topViewController
694
+ : moreNavigationController;
695
+ }
696
+
697
+ #if RNS_UITAB_API_SDK_AVAILABLE
698
+ if (RNS_UITAB_API_ENABLED) {
699
+ return self.selectedTab.viewController;
700
+ }
701
+ #endif // RNS_UITAB_API_SDK_AVAILABLE
702
+ return self.selectedViewController;
703
+ }
704
+
705
+ #if RNS_UITAB_API_SDK_AVAILABLE
706
+
707
+ - (UITab *)tabForTabScreenController:(RNSTabsScreenViewController *)screenController API_AVAILABLE(ios(18.0))
708
+ {
709
+ // Return existing tab if present. Note that this "latches"
710
+ // the type of the tab: you cannot swap regular UITab with UISearchTab;
711
+ // and we want it this way because tabs hold view controller references
712
+ // and UIKit asserts they are unique even for the detached, not-deallocated tabs
713
+ if (screenController.tab) {
714
+ return screenController.tab;
715
+ }
716
+
717
+ UITab *tab = [self makeTabForTabScreenController:screenController];
718
+ [self disableUserCustomizationOfTab:tab];
719
+ return tab;
720
+ }
721
+
722
+ /// Customizable tab would force the Edit button into the iPad sidebar,
723
+ /// and we don't support tab editing as of now.
724
+ - (void)disableUserCustomizationOfTab:(UITab *)tab API_AVAILABLE(ios(18.0))
725
+ {
726
+ tab.preferredPlacement = UITabPlacementFixed;
727
+ }
728
+
729
+ /// Always builds a FRESH instance, seeded with current content - the bar reads a `UITab` only
730
+ /// when its instance enters the tabs array.
731
+ - (UITab *)makeTabForTabScreenController:(RNSTabsScreenViewController *)screenController API_AVAILABLE(ios(18.0))
732
+ {
733
+ __weak RNSTabsScreenViewController *weakScreenController = screenController;
734
+
735
+ if (screenController.tabScreenComponentView.tabRole == RNSTabsScreenTabRoleSearch) {
736
+ // The designated initializer of `UISearchTab` takes no identifier - UIKit assigns a system one.
737
+ UISearchTab *searchTab = [[UISearchTab alloc] initWithViewControllerProvider:^UIViewController *(UITab *) {
738
+ return weakScreenController;
739
+ }];
740
+ #if RNS_UITAB_API_SDK_AVAILABLE && RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) && !TARGET_OS_TV && !TARGET_OS_VISION
741
+ if (@available(iOS 26.0, *)) {
742
+ if (RNS_UITAB_API_ENABLED) {
743
+ searchTab.automaticallyActivatesSearch = screenController.tabScreenComponentView.automaticallyActivatesSearch;
744
+ }
745
+ }
746
+ #endif // RNS_UITAB_API_SDK_AVAILABLE && RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) && !TARGET_OS_TV && !TARGET_OS_VISION
747
+
748
+ return searchTab;
749
+ }
750
+
751
+ return [[UITab alloc] initWithTitle:[screenController.tabScreenComponentView title] ?: @""
752
+ image:nil
753
+ identifier:[screenController.tabScreenComponentView screenKey]
754
+ viewControllerProvider:^UIViewController *(UITab *) {
755
+ return weakScreenController;
756
+ }];
757
+ }
758
+
759
+ #endif // RNS_UITAB_API_SDK_AVAILABLE
760
+
761
+ #pragma mark - UINavigationControllerDelegate - for More controller
466
762
 
467
763
  - (void)navigationController:(UINavigationController *)navigationController
468
764
  willShowViewController:(UIViewController *)viewController
@@ -478,10 +774,55 @@ static void rns_pushViewController(__unsafe_unretained id self,
478
774
  [self shouldProgressStateOnMoreNavigationControllerPush]) {
479
775
  [self userDidSelectViewController:viewController];
480
776
  [self setShouldProgressStateOnMoreNavigationControllerPush:NO];
777
+ return;
778
+ }
779
+
780
+ if ([self usesUITabAPI]) {
781
+ [self handleMoreNavigationControllerTransitionToViewController:viewController];
481
782
  }
482
783
  #endif // RNS_MORE_NAVIGATION_CONTROLLER_AVAILABLE
483
784
  }
484
785
 
786
+ - (void)handleMoreNavigationControllerTransitionToViewController:(nonnull UIViewController *)viewController
787
+ {
788
+ if ([viewController isKindOfClass:RNSTabsScreenViewController.class]) {
789
+ // A screen retained on the More stack is being re-displayed - not a More list entry.
790
+ _pendingMoreTabSelectedEmit = NO;
791
+ [self progressStateOnMoreHostedScreenRedisplayIfNeeded:viewController];
792
+ return;
793
+ }
794
+
795
+ // Transition to the More list root.
796
+ if (_pendingMoreTabSelectedEmit) {
797
+ _pendingMoreTabSelectedEmit = NO;
798
+ [_observerRegistry emitDidSelectMoreTabWithCurrentState:_navigationState sender:self];
799
+ }
800
+ }
801
+
802
+ - (void)progressStateOnMoreHostedScreenRedisplayIfNeeded:(nonnull UIViewController *)viewController
803
+ {
804
+ if (_navigationState == nil || _isHandlingExplicitSelectionUpdate ||
805
+ ![viewController isKindOfClass:RNSTabsScreenViewController.class]) {
806
+ return;
807
+ }
808
+
809
+ // Also skips iOS 27's stale willShow fired for the OUTGOING hosted screen when the selection
810
+ // moves away from More - the bar item flips synchronously before that willShow arrives.
811
+ if (![self isMoreNavigationControllerTabBarItemSelected]) {
812
+ return;
813
+ }
814
+
815
+ NSString *screenKey = [self screenKeyForViewController:viewController];
816
+ if ([_navigationState.selectedScreenKey isEqualToString:screenKey]) {
817
+ // Already reflected in the state, e.g. a programmatic selection progressed it before the
818
+ // transition fired this callback.
819
+ return;
820
+ }
821
+
822
+ [self progressNavigationState:screenKey withOrigin:RNSTabsActionOriginUser];
823
+ [self emitSelectionUpdateWithOrigin:RNSTabsActionOriginUser repeated:NO hasTriggeredSpecialEffect:NO];
824
+ }
825
+
485
826
  #pragma mark - Signals related
486
827
 
487
828
  - (void)updateChildViewControllersIfNeeded
@@ -501,7 +842,7 @@ static void rns_pushViewController(__unsafe_unretained id self,
501
842
  return;
502
843
  }
503
844
 
504
- [self setViewControllers:_tabScreenControllers animated:[[self viewControllers] count] != 0];
845
+ [self installScreenControllers:_tabScreenControllers animated:[self installedScreenControllers].count != 0];
505
846
  }
506
847
 
507
848
  - (void)updateSelectedViewControllerIfNeeded
@@ -513,7 +854,7 @@ static void rns_pushViewController(__unsafe_unretained id self,
513
854
 
514
855
  - (void)updateSelectedViewController
515
856
  {
516
- if (_pendingStateUpdate == nil || self.viewControllers.count == 0) {
857
+ if (_pendingStateUpdate == nil || [self installedScreenControllers].count == 0) {
517
858
  return;
518
859
  }
519
860
 
@@ -531,10 +872,9 @@ static void rns_pushViewController(__unsafe_unretained id self,
531
872
  {
532
873
  RCTAssert(_pendingStateUpdate != nil, @"[RNScreens] Pending update MUST NOT be nil");
533
874
 
534
- UIViewController *_Nonnull currSelectedViewController = self.selectedViewController;
535
-
536
875
  NSString *_Nonnull nextSelectedViewControllerKey = _pendingStateUpdate.selectedScreenKey;
537
- UIViewController *nextSelectedViewController = [self findChildViewControllerForKey:nextSelectedViewControllerKey];
876
+ RNSTabsScreenViewController *nextSelectedViewController =
877
+ [self findChildViewControllerForKey:nextSelectedViewControllerKey];
538
878
 
539
879
  RCTAssert(nextSelectedViewController != nil,
540
880
  @"[RNScreens] Failed to determine next selected view controller for key: %@",
@@ -553,9 +893,10 @@ static void rns_pushViewController(__unsafe_unretained id self,
553
893
  return;
554
894
  }
555
895
 
556
- if (currSelectedViewController == nextSelectedViewController && _navigationState != nil) {
557
- // Nothing to do, we don't allow for programmatic repeat selection, unless
558
- // we're during first render.
896
+ BOOL isRepeatedSelection = [self isScreenControllerCurrentlySelected:nextSelectedViewController];
897
+
898
+ // Programmatic repeat selection is rejected, unless we're during first render.
899
+ if (isRepeatedSelection && _navigationState != nil) {
559
900
  [_observerRegistry emitRejectedStateUpdate:_pendingStateUpdate
560
901
  currentState:_navigationState
561
902
  withReason:RNSTabsNavigationStateRejectionReasonRepeated
@@ -563,16 +904,14 @@ static void rns_pushViewController(__unsafe_unretained id self,
563
904
  return;
564
905
  }
565
906
 
566
- // TODO: This code MUST be moved to some callback.
567
- // Should this be called only on JS updates?
568
- auto *screenViewController = static_cast<RNSTabsScreenViewController *>(nextSelectedViewController);
907
+ // TODO: move the user interface style update to a callback; should it run only on JS updates?
569
908
  if (@available(iOS 26.0, *)) {
570
909
  // On iOS 26, we need to set user interface style 2 parent views above the tab bar
571
910
  // for this prop to take effect.
572
911
  self.tabBar.superview.superview.overrideUserInterfaceStyle =
573
- screenViewController.tabScreenComponentView.userInterfaceStyle;
912
+ nextSelectedViewController.tabScreenComponentView.userInterfaceStyle;
574
913
  } else {
575
- self.tabBar.overrideUserInterfaceStyle = screenViewController.tabScreenComponentView.userInterfaceStyle;
914
+ self.tabBar.overrideUserInterfaceStyle = nextSelectedViewController.tabScreenComponentView.userInterfaceStyle;
576
915
  }
577
916
 
578
917
  RNSLog(@"Change selected view controller to: %@", nextSelectedViewControllerKey);
@@ -585,15 +924,20 @@ static void rns_pushViewController(__unsafe_unretained id self,
585
924
  }
586
925
 
587
926
  if (hasStateProgressed) {
588
- RNSTabsNavigationStateUpdateContext *context =
589
- [[RNSTabsNavigationStateUpdateContext alloc] initWithNavState:_navigationState
590
- isRepeated:NO
591
- hasTriggeredSpecialEffect:NO
592
- actionOrigin:_pendingStateUpdate.actionOrigin];
593
- [_observerRegistry emitDidUpdateStateTo:_navigationState withContext:context sender:self];
927
+ [self emitSelectionUpdateWithOrigin:_pendingStateUpdate.actionOrigin repeated:NO hasTriggeredSpecialEffect:NO];
594
928
  }
595
929
  }
596
930
 
931
+ - (void)createTabBarItemsIfNeeded
932
+ {
933
+ [_tabBarItemCoordinator createTabBarItemsForTabScreenControllers:_tabScreenControllers];
934
+ }
935
+
936
+ - (void)updateTabBarItemsIfNeeded
937
+ {
938
+ [_tabBarItemCoordinator updateTabBarItemsForTabScreenControllers:_tabScreenControllers];
939
+ }
940
+
597
941
  - (void)updateTabBarAppearanceIfNeeded
598
942
  {
599
943
  if (_needsUpdateOfTabBarAppearance) {
@@ -614,8 +958,8 @@ static void rns_pushViewController(__unsafe_unretained id self,
614
958
 
615
959
  - (void)updateTabBarA11yIfNeeded
616
960
  {
617
- for (UIViewController *tabViewController in self.viewControllers) {
618
- auto screenView = static_cast<RNSTabsScreenViewController *>(tabViewController).tabScreenComponentView;
961
+ for (RNSTabsScreenViewController *tabViewController in [self installedScreenControllers]) {
962
+ auto screenView = tabViewController.tabScreenComponentView;
619
963
  if (!screenView.tabBarItemNeedsA11yUpdate) {
620
964
  continue;
621
965
  }
@@ -626,6 +970,77 @@ static void rns_pushViewController(__unsafe_unretained id self,
626
970
  }
627
971
  }
628
972
 
973
+ /**
974
+ * Perform SearchTab-related updates. This should be called before tab selection update has run,
975
+ * otherwise the search activation may not trigger.
976
+ */
977
+ - (void)updateSearchTabsIfNeeded
978
+ {
979
+ #if RNS_UITAB_API_SDK_AVAILABLE && RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) && !TARGET_OS_TV && !TARGET_OS_VISION
980
+ if (@available(iOS 26.0, *)) {
981
+ if (RNS_UITAB_API_ENABLED) {
982
+ for (UITab *tab in self.tabs) {
983
+ if (![tab isKindOfClass:UISearchTab.class]) {
984
+ continue;
985
+ }
986
+ auto *searchTab = static_cast<UISearchTab *>(tab);
987
+ auto *screenController = static_cast<RNSTabsScreenViewController *>(tab.viewController);
988
+ auto *screenView = screenController.tabScreenComponentView;
989
+
990
+ if (searchTab.automaticallyActivatesSearch != screenView.automaticallyActivatesSearch) {
991
+ searchTab.automaticallyActivatesSearch = screenView.automaticallyActivatesSearch;
992
+ }
993
+ }
994
+ }
995
+ }
996
+ #endif // RNS_UITAB_API_SDK_AVAILABLE && RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) && !TARGET_OS_TV && !TARGET_OS_VISION
997
+ }
998
+
999
+ #pragma mark - Prominent tab
1000
+
1001
+ - (void)setProminentScreenKey:(nullable NSString *)screenKey
1002
+ {
1003
+ NSString *_Nullable newScreenKey = screenKey.length > 0 ? [screenKey copy] : nil;
1004
+ if (newScreenKey == _prominentScreenKey || [newScreenKey isEqualToString:_prominentScreenKey]) {
1005
+ return;
1006
+ }
1007
+ _prominentScreenKey = newScreenKey;
1008
+ _needsUpdateOfProminentIdentifier = YES;
1009
+ }
1010
+
1011
+ /// Resolves `_prominentScreenKey` against the installed tabs & pushes the result to UIKit.
1012
+ - (void)updateProminentIdentifierIfNeeded
1013
+ {
1014
+ if (!_needsUpdateOfProminentIdentifier) {
1015
+ return;
1016
+ }
1017
+ _needsUpdateOfProminentIdentifier = NO;
1018
+
1019
+ #if RNS_IPHONE_OS_VERSION_AVAILABLE(27_0) && !TARGET_OS_TV && !TARGET_OS_VISION
1020
+ if (@available(iOS 27.0, *)) {
1021
+ NSString *_Nullable tabIdentifier = nil;
1022
+
1023
+ if (_prominentScreenKey != nil) {
1024
+ // The lookup goes through the view controller, because `UISearchTab` carries
1025
+ // a system-assigned identifier.
1026
+ UITab *_Nullable tab = [self findChildViewControllerForKey:_prominentScreenKey].tab;
1027
+ if (tab != nil) {
1028
+ tabIdentifier = tab.identifier;
1029
+ } else {
1030
+ RCTLogWarn(@"[RNScreens] prominentScreenKey '%@' does not match the screenKey of any tab screen",
1031
+ _prominentScreenKey);
1032
+ }
1033
+ }
1034
+
1035
+ if (tabIdentifier == self.prominentTabIdentifier || [tabIdentifier isEqualToString:self.prominentTabIdentifier]) {
1036
+ return;
1037
+ }
1038
+ // Animate only runtime changes, not the initial setup before the tab bar is on screen.
1039
+ [self setProminentTabIdentifier:tabIdentifier animated:self.viewIfLoaded.window != nil];
1040
+ }
1041
+ #endif // RNS_IPHONE_OS_VERSION_AVAILABLE(27_0) && !TARGET_OS_TV && !TARGET_OS_VISION
1042
+ }
1043
+
629
1044
  #pragma mark - Utility
630
1045
 
631
1046
  - (nullable RNSTabsScreenViewController *)findChildViewControllerForKey:(nullable NSString *)screenKey
@@ -633,11 +1048,7 @@ static void rns_pushViewController(__unsafe_unretained id self,
633
1048
  if (screenKey == nil) {
634
1049
  return nil;
635
1050
  }
636
- for (UIViewController *viewController in self.viewControllers) {
637
- RCTAssert([viewController isKindOfClass:RNSTabsScreenViewController.class],
638
- @"[RNScreens] Unexpected type of controller: %@",
639
- viewController.class);
640
- auto *screenViewController = static_cast<RNSTabsScreenViewController *>(viewController);
1051
+ for (RNSTabsScreenViewController *screenViewController in [self installedScreenControllers]) {
641
1052
  if ([screenViewController.getScreenKeyOrNull isEqualToString:screenKey]) {
642
1053
  return screenViewController;
643
1054
  }
@@ -662,16 +1073,14 @@ static void rns_pushViewController(__unsafe_unretained id self,
662
1073
  }
663
1074
  }
664
1075
 
665
- /**
666
- * Be sure to call this method IF AND ONLY IF you know that the `self.selectedViewController`
667
- * is not the `moreNavigationController`.
668
- */
669
- - (RNSTabsScreenViewController *)selectedScreenViewController
1076
+ - (BOOL)usesUITabAPI
670
1077
  {
671
- RCTAssert([self.selectedViewController isKindOfClass:RNSTabsScreenViewController.class],
672
- @"[RNScreens] Unexpected type of selectedViewController: %@",
673
- self.selectedViewController.class);
674
- return static_cast<RNSTabsScreenViewController *>(self.selectedViewController);
1078
+ #if RNS_UITAB_API_SDK_AVAILABLE
1079
+ if (RNS_UITAB_API_ENABLED) {
1080
+ return YES;
1081
+ }
1082
+ #endif // RNS_UITAB_API_SDK_AVAILABLE
1083
+ return NO;
675
1084
  }
676
1085
 
677
1086
  - (nonnull NSString *)screenKeyForViewController:(nonnull UIViewController *)viewController
@@ -717,14 +1126,17 @@ static void rns_pushViewController(__unsafe_unretained id self,
717
1126
  return;
718
1127
  }
719
1128
 
720
- if (![self.selectedViewController isKindOfClass:RNSTabsScreenViewController.class]) {
721
- RCTAssert(NO,
722
- @"[RNScreens] Unexpected controller type during state reconciliation: %@",
723
- self.selectedViewController.class);
1129
+ UIViewController *_Nullable selectedController = [self effectiveSelectedViewController];
1130
+ if (selectedController == nil) {
1131
+ return;
1132
+ }
1133
+
1134
+ if (![selectedController isKindOfClass:RNSTabsScreenViewController.class]) {
1135
+ RCTAssert(NO, @"[RNScreens] Unexpected controller type during state reconciliation: %@", selectedController.class);
724
1136
  return;
725
1137
  }
726
1138
 
727
- NSString *selectedScreenKey = [self screenKeyForSelectedViewController];
1139
+ NSString *selectedScreenKey = [self screenKeyForViewController:selectedController];
728
1140
  if ([_navigationState.selectedScreenKey isEqualToString:selectedScreenKey]) {
729
1141
  return;
730
1142
  }
@@ -738,11 +1150,7 @@ static void rns_pushViewController(__unsafe_unretained id self,
738
1150
  [self disableNavigationBarInMoreNavigationController];
739
1151
  }
740
1152
 
741
- auto *context = [[RNSTabsNavigationStateUpdateContext alloc] initWithNavState:_navigationState
742
- isRepeated:NO
743
- hasTriggeredSpecialEffect:NO
744
- actionOrigin:RNSTabsActionOriginImplicit];
745
- [_observerRegistry emitDidUpdateStateTo:_navigationState withContext:context sender:self];
1153
+ [self emitSelectionUpdateWithOrigin:RNSTabsActionOriginImplicit repeated:NO hasTriggeredSpecialEffect:NO];
746
1154
  }
747
1155
 
748
1156
  /**
@@ -769,7 +1177,7 @@ static void rns_pushViewController(__unsafe_unretained id self,
769
1177
  // https://developer.apple.com/documentation/uikit/uitabbarcontroller?language=objc#The-More-navigation-controller
770
1178
  // The count is documented. Size class check is empirical, to tighten the condition and have less
771
1179
  // false positives. If we ever find it not correct, we can safely remove it.
772
- return self.viewControllers.count >= kMinCountOfVCsForMoreVCPresence &&
1180
+ return [self installedScreenControllers].count >= kMinCountOfVCsForMoreVCPresence &&
773
1181
  self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact;
774
1182
  #else
775
1183
  return NO;
@@ -783,7 +1191,7 @@ static void rns_pushViewController(__unsafe_unretained id self,
783
1191
  }
784
1192
 
785
1193
  // Guard: VC must be one we manage (excludes arbitrary external VCs).
786
- if ([self.viewControllers indexOfObject:viewController] == NSNotFound) {
1194
+ if (![[self installedScreenControllers] containsObject:static_cast<RNSTabsScreenViewController *>(viewController)]) {
787
1195
  return NO;
788
1196
  }
789
1197
 
@@ -1054,8 +1462,9 @@ static void rns_pushViewController(__unsafe_unretained id self,
1054
1462
 
1055
1463
  - (RNSOrientation)evaluateOrientation
1056
1464
  {
1057
- if ([self.selectedViewController respondsToSelector:@selector(evaluateOrientation)]) {
1058
- id<RNSOrientationProviding> selected = static_cast<id<RNSOrientationProviding>>(self.selectedViewController);
1465
+ UIViewController *_Nullable selectedController = [self effectiveSelectedViewController];
1466
+ if ([selectedController respondsToSelector:@selector(evaluateOrientation)]) {
1467
+ id<RNSOrientationProviding> selected = static_cast<id<RNSOrientationProviding>>(selectedController);
1059
1468
  return [selected evaluateOrientation];
1060
1469
  }
1061
1470