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

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 (235) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +1 -1
  3. package/android/CLAUDE.md +23 -14
  4. package/android/CORE.md +32 -19
  5. package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +60 -13
  6. package/android/src/main/java/dev/apexnative/navigation/options/AnimationsOptions.kt +2 -2
  7. package/android/src/main/java/dev/apexnative/navigation/options/BottomTabsOptions.kt +13 -8
  8. package/android/src/main/java/dev/apexnative/navigation/options/HardwareBackButtonOptions.kt +9 -1
  9. package/android/src/main/java/dev/apexnative/navigation/options/StackAnimationOptions.kt +40 -2
  10. package/android/src/main/java/dev/apexnative/navigation/react/CommandAnswer.kt +28 -13
  11. package/android/src/main/java/dev/apexnative/navigation/react/Hinge.kt +7 -4
  12. package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +3 -3
  13. package/android/src/main/java/dev/apexnative/navigation/react/ReactView.kt +39 -20
  14. package/android/src/main/java/dev/apexnative/navigation/react/ReloadCoordinator.kt +7 -6
  15. package/android/src/main/java/dev/apexnative/navigation/react/ScreenEnvironment.kt +177 -0
  16. package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +19 -51
  17. package/android/src/main/java/dev/apexnative/navigation/react/ScrollEvents.kt +49 -128
  18. package/android/src/main/java/dev/apexnative/navigation/react/events/AppearanceDispatcher.kt +30 -39
  19. package/android/src/main/java/dev/apexnative/navigation/react/events/EventEmitter.kt +34 -7
  20. package/android/src/main/java/dev/apexnative/navigation/react/events/NavigationState.kt +154 -0
  21. package/android/src/main/java/dev/apexnative/navigation/safearea/SafeAreaView.kt +12 -10
  22. package/android/src/main/java/dev/apexnative/navigation/slots/SlotScreenView.kt +12 -12
  23. package/android/src/main/java/dev/apexnative/navigation/utils/ViewExtensions.kt +0 -13
  24. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/ArrangementController.kt +10 -7
  25. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsCollapseBehavior.kt +5 -3
  26. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +22 -21
  27. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/CustomTabRowGeometry.kt +22 -0
  28. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/TabsRoom.kt +0 -5
  29. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentViewController.kt +12 -9
  30. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ContentReadiness.kt +1 -1
  31. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenReadiness.kt +103 -56
  32. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenSafeArea.kt +29 -11
  33. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/externalcomponent/ExternalComponentViewController.kt +2 -6
  34. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +20 -2
  35. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +119 -8
  36. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +7 -0
  37. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetBack.kt +42 -0
  38. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackCheck.kt +50 -0
  39. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackState.kt +45 -14
  40. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +25 -5
  41. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt +2 -2
  42. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ParentController.kt +3 -6
  43. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitViewController.kt +3 -0
  44. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/PreparedFrame.kt +3 -3
  45. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackCommands.kt +2 -2
  46. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +13 -5
  47. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +3 -0
  48. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/ScreenSearch.kt +3 -5
  49. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +2 -5
  50. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/BarComponents.kt +5 -51
  51. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +4 -9
  52. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ScrollEventListener.kt +6 -9
  53. package/android/src/main/java/dev/apexnative/navigation/views/animations/AnimationLayer.kt +73 -0
  54. package/android/src/main/java/dev/apexnative/navigation/views/animations/ValueInterpolation.kt +14 -0
  55. package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabsLayout.kt +61 -2
  56. package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/CustomBottomTabItemView.kt +0 -5
  57. package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/CustomTabRow.kt +72 -10
  58. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +9 -2
  59. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +11 -0
  60. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt +318 -25
  61. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +18 -20
  62. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +17 -25
  63. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +15 -22
  64. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +12 -9
  65. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/TextChangeAnimator.kt +84 -35
  66. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +49 -40
  67. package/cli/doctor/checks/splash.js +4 -22
  68. package/cli/splash/index.js +3 -1
  69. package/docs-md/architecture/transitions.md +4 -2
  70. package/docs-md/containers/modals.md +4 -2
  71. package/docs-md/containers/overlays.md +1 -1
  72. package/docs-md/containers/side-menu.md +1 -1
  73. package/docs-md/containers/tabs.md +2 -0
  74. package/docs-md/flows/bottom-sheets.md +1 -1
  75. package/docs-md/fundamentals/starting-the-app.md +1 -1
  76. package/docs-md/guides/cli.md +1 -2
  77. package/docs-md/help/glossary.md +2 -2
  78. package/docs-md/help/troubleshooting.md +23 -2
  79. package/docs-md/index.json +12 -0
  80. package/docs-md/migration/from-expo-router.md +1 -1
  81. package/docs-md/migration/from-react-native-navigation.md +1 -1
  82. package/docs-md/migration/from-react-navigation.md +2 -2
  83. package/docs-md/screen-ui/back-and-gestures.md +36 -0
  84. package/docs-md/screen-ui/bar-buttons.md +4 -4
  85. package/docs-md/screen-ui/search.md +1 -1
  86. package/docs-md/screen-ui/system-ui.md +16 -11
  87. package/docs-md/screen-ui/toolbar-and-fab.md +2 -2
  88. package/docs-md/screen-ui/top-bar.md +8 -4
  89. package/docs-md/start/installation.md +1 -1
  90. package/ios/Animations/ANNFrameDriver.mm +2 -0
  91. package/ios/Animations/Elements/ANNSharedElementFlight.mm +70 -4
  92. package/ios/Animations/Interpolators/ANNInterpolate.h +2 -30
  93. package/ios/Animations/Interpolators/ANNTransformParts.h +48 -0
  94. package/ios/Animations/Interpolators/ANNTransformParts.mm +507 -0
  95. package/ios/Animations/Interpolators/ANNViewTracks.h +6 -4
  96. package/ios/Animations/Interpolators/ANNViewTracks.mm +49 -9
  97. package/ios/CLAUDE.md +17 -15
  98. package/ios/CORE.md +30 -26
  99. package/ios/Contract/ANNCommandAnswer.h +9 -2
  100. package/ios/Contract/ANNCommandAnswer.mm +25 -12
  101. package/ios/Contract/ANNCommandRunner.h +8 -7
  102. package/ios/Contract/ANNCommandRunner.mm +21 -27
  103. package/ios/Contract/ANNLayoutNode.h +3 -3
  104. package/ios/Contract/ANNLayoutReader.h +3 -3
  105. package/ios/Core/ANNAppearanceDispatcher.h +5 -4
  106. package/ios/Core/ANNAppearanceDispatcher.mm +12 -1
  107. package/ios/Core/ANNJavaScriptCalls.h +11 -12
  108. package/ios/Core/ANNJavaScriptCalls.mm +7 -16
  109. package/ios/Core/ANNNavigationState.h +4 -6
  110. package/ios/Core/ANNNavigationState.mm +0 -4
  111. package/ios/Core/ANNReloadCoordinator.mm +4 -8
  112. package/ios/Core/ApexNavigation+Internal.h +1 -1
  113. package/ios/Layouts/Arrangement/ANNArrangementState.h +2 -17
  114. package/ios/Layouts/Arrangement/ANNArrangementState.mm +0 -38
  115. package/ios/Layouts/Arrangement/ANNArrangementViewController.mm +4 -4
  116. package/ios/Layouts/Root/ANNSplashViewController.h +5 -3
  117. package/ios/Layouts/Root/ANNSplashViewController.mm +147 -0
  118. package/ios/Layouts/Screen/ANNComponentViewController.h +2 -2
  119. package/ios/Layouts/Screen/ANNComponentViewController.mm +9 -22
  120. package/ios/Layouts/Screen/ANNPreviewInteraction.h +7 -4
  121. package/ios/Layouts/Screen/ANNPreviewInteraction.mm +72 -10
  122. package/ios/Layouts/Screen/ANNScreenEnvironment.h +63 -0
  123. package/ios/Layouts/Screen/ANNScreenEnvironment.mm +181 -0
  124. package/ios/Layouts/Screen/ANNScreenRegions.h +3 -18
  125. package/ios/Layouts/Screen/ANNScreenRegions.mm +0 -38
  126. package/ios/Layouts/Screen/ANNScreenSafeArea.h +4 -9
  127. package/ios/Layouts/Screen/ANNScreenSafeArea.mm +14 -16
  128. package/ios/Layouts/Stack/ANNPreviewPush.h +7 -6
  129. package/ios/Layouts/Stack/ANNPreviewPush.mm +142 -27
  130. package/ios/Layouts/Tabs/ANNBottomTabsAccessoryView.h +12 -1
  131. package/ios/Layouts/Tabs/ANNBottomTabsAccessoryView.mm +23 -0
  132. package/ios/Layouts/Tabs/ANNBottomTabsController.mm +2 -1
  133. package/ios/Layouts/Tabs/ANNBottomTabsCustomRow.mm +8 -3
  134. package/ios/Layouts/Tabs/ANNBottomTabsPresenter.h +4 -0
  135. package/ios/Layouts/Tabs/ANNBottomTabsPresenter.mm +123 -36
  136. package/ios/Layouts/Tabs/ANNTabsDesign.h +6 -5
  137. package/ios/Layouts/Tabs/ANNTabsDesign.mm +9 -1
  138. package/ios/Layouts/Tabs/UITabBar+utils.h +5 -5
  139. package/ios/Layouts/Tabs/UITabBar+utils.mm +32 -13
  140. package/ios/Options/ANNBottomTabsOptions.h +2 -0
  141. package/ios/Options/ANNBottomTabsOptions.mm +1 -0
  142. package/ios/Options/ANNOptions.h +18 -0
  143. package/ios/Options/ANNOptions.mm +112 -1
  144. package/ios/Options/ANNOptionsReader.mm +110 -0
  145. package/ios/Options/Params/ANNParam.h +15 -0
  146. package/ios/Options/Params/ANNParam.mm +61 -0
  147. package/ios/Options/Params/Color.h +3 -0
  148. package/ios/Options/Params/Color.mm +21 -0
  149. package/ios/Options/Params/Image.h +4 -0
  150. package/ios/Options/Params/Image.mm +20 -0
  151. package/ios/React/ANNReactView.mm +6 -25
  152. package/ios/React/ANNSafeAreaViewComponentView.mm +26 -15
  153. package/ios/React/ANNSurfaceHostingView.h +6 -0
  154. package/ios/React/ANNSurfaceHostingView.mm +116 -0
  155. package/ios/TopBar/ANNBackButtonPresenter.mm +6 -5
  156. package/ios/TopBar/ANNBarAppearance.h +2 -1
  157. package/ios/TopBar/ANNBarAppearance.mm +111 -30
  158. package/ios/TopBar/ANNBarBackgroundComponent.mm +16 -0
  159. package/ios/TopBar/ANNBarButtonComponentView.h +4 -4
  160. package/ios/TopBar/ANNBarButtonComponentView.mm +1 -1
  161. package/ios/TopBar/ANNBarButtons.h +5 -3
  162. package/ios/TopBar/ANNBarButtons.mm +186 -17
  163. package/ios/TopBar/ANNLargeTitlePresenter.mm +24 -4
  164. package/ios/TopBar/ANNMainList.h +3 -0
  165. package/ios/TopBar/ANNMainList.mm +4 -0
  166. package/ios/TopBar/ANNScrollEdgeEffects.h +22 -6
  167. package/ios/TopBar/ANNScrollEdgeEffects.mm +123 -16
  168. package/ios/TopBar/ANNSearchPresenter.mm +2 -2
  169. package/ios/TopBar/ANNSearchResultsViewController.mm +5 -5
  170. package/ios/TopBar/ANNTopBarPresenter.h +3 -2
  171. package/ios/TopBar/ANNTopBarPresenter.mm +95 -22
  172. package/ios/TurboModules/ANNTurboModule.mm +20 -16
  173. package/lib/module/ARCHITECTURE.md +8 -4
  174. package/lib/module/api/commands/commands.js +54 -6
  175. package/lib/module/api/commands/commands.js.map +1 -1
  176. package/lib/module/api/hooks/useSafeAreaInsets.js +4 -3
  177. package/lib/module/api/hooks/useSafeAreaInsets.js.map +1 -1
  178. package/lib/module/api/options/OptionsChain.js +2 -2
  179. package/lib/module/engine/commands.js +21 -1
  180. package/lib/module/engine/commands.js.map +1 -1
  181. package/lib/module/engine/places.js +8 -0
  182. package/lib/module/engine/places.js.map +1 -1
  183. package/lib/module/engine/screens.js +29 -4
  184. package/lib/module/engine/screens.js.map +1 -1
  185. package/lib/typescript/api/app/createNavigation.d.ts +3 -3
  186. package/lib/typescript/api/commands/commands.d.ts.map +1 -1
  187. package/lib/typescript/api/commands/presentation.d.ts +4 -3
  188. package/lib/typescript/api/commands/presentation.d.ts.map +1 -1
  189. package/lib/typescript/api/hooks/useSafeAreaInsets.d.ts +4 -3
  190. package/lib/typescript/api/hooks/useSafeAreaInsets.d.ts.map +1 -1
  191. package/lib/typescript/api/options/OptionsChain.d.ts +2 -2
  192. package/lib/typescript/engine/commands.d.ts.map +1 -1
  193. package/lib/typescript/engine/places.d.ts +2 -0
  194. package/lib/typescript/engine/places.d.ts.map +1 -1
  195. package/lib/typescript/engine/screens.d.ts +20 -6
  196. package/lib/typescript/engine/screens.d.ts.map +1 -1
  197. package/lib/typescript/interfaces/Options.d.ts +1 -1
  198. package/lib/typescript/interfaces/Options.d.ts.map +1 -1
  199. package/lib/typescript/interfaces/options/animations.d.ts +30 -9
  200. package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
  201. package/lib/typescript/interfaces/options/bottomTabs.d.ts +18 -5
  202. package/lib/typescript/interfaces/options/bottomTabs.d.ts.map +1 -1
  203. package/lib/typescript/interfaces/options/buttons.d.ts +8 -8
  204. package/lib/typescript/interfaces/options/stack.d.ts +9 -0
  205. package/lib/typescript/interfaces/options/stack.d.ts.map +1 -1
  206. package/lib/typescript/interfaces/options/system.d.ts +8 -5
  207. package/lib/typescript/interfaces/options/system.d.ts.map +1 -1
  208. package/lib/typescript/interfaces/options/toolbar.d.ts +2 -2
  209. package/lib/typescript/interfaces/options/topBar.d.ts +32 -29
  210. package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
  211. package/package.json +1 -1
  212. package/src/ARCHITECTURE.md +8 -4
  213. package/src/api/app/createNavigation.ts +3 -3
  214. package/src/api/commands/commands.ts +48 -7
  215. package/src/api/commands/presentation.ts +4 -3
  216. package/src/api/hooks/useSafeAreaInsets.ts +4 -3
  217. package/src/api/options/OptionsChain.ts +2 -2
  218. package/src/engine/commands.ts +30 -6
  219. package/src/engine/places.ts +9 -0
  220. package/src/engine/screens.tsx +29 -8
  221. package/src/interfaces/Options.ts +1 -0
  222. package/src/interfaces/options/animations.ts +31 -9
  223. package/src/interfaces/options/bottomTabs.ts +18 -5
  224. package/src/interfaces/options/buttons.ts +8 -8
  225. package/src/interfaces/options/stack.ts +9 -0
  226. package/src/interfaces/options/system.ts +8 -5
  227. package/src/interfaces/options/toolbar.ts +2 -2
  228. package/src/interfaces/options/topBar.ts +32 -29
  229. package/android/src/main/java/dev/apexnative/navigation/react/ArrangementStates.kt +0 -42
  230. package/android/src/main/java/dev/apexnative/navigation/react/ScreenInsets.kt +0 -70
  231. package/android/src/main/java/dev/apexnative/navigation/react/events/NavigationStateReporter.kt +0 -62
  232. package/android/src/main/java/dev/apexnative/navigation/react/events/StateHold.kt +0 -62
  233. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/CustomTabRowPlacer.kt +0 -223
  234. package/ios/Layouts/Screen/ANNScreenInsets.h +0 -30
  235. package/ios/Layouts/Screen/ANNScreenInsets.mm +0 -70
@@ -1,19 +1,25 @@
1
1
  #import "ANNPreviewPush.h"
2
2
  #import "ANNAssert.h"
3
+ #import "ANNCommandRunner.h"
4
+ #import "ANNComponentViewController.h"
3
5
  #import "ANNErrorTexts.h"
6
+ #import "ANNExternalViewController.h"
4
7
  #import "ANNLayoutFactory.h"
5
8
  #import "ANNParking.h"
6
9
  #import "ANNPopoverPresenter.h"
7
10
  #import "ANNPreviewInteraction.h"
8
11
  #import "ANNReloadCoordinator.h"
12
+ #import "ANNSlotContainers.h"
13
+ #import "ANNSlotScreenView.h"
9
14
  #import "ANNStackCommands.h"
10
15
  #import "ANNStackController.h"
11
16
  #import "ApexNavigation+Internal.h"
12
17
  #import "UIViewController+LayoutProtocol.h"
13
18
 
14
- // The view of a component mounts a frame or so after the command that rendered it: it is looked for this many times,
15
- // a frame apart.
16
- static const NSUInteger ANNPreviewSearchTries = 30;
19
+ // The view of a component mounts a frame or so after the command that rendered it: it is looked for at each mount of
20
+ // the source's content until this limit (30 frames), then the preview goes, as with a `nativeID` no view has.
21
+ static const NSTimeInterval ANNPreviewSearchLimit = 30.0 / 60;
22
+ // Where the source's mounts can't be heard, a frame apart.
17
23
  static const NSTimeInterval ANNPreviewSearchInterval = 1.0 / 60;
18
24
 
19
25
  // One preview from its push to its end: the tap took it, or it went.
@@ -28,9 +34,14 @@ static const NSTimeInterval ANNPreviewSearchInterval = 1.0 / 60;
28
34
  @property(nonatomic, copy) NSString *componentId;
29
35
  @property(nonatomic, copy) NSString *what;
30
36
  @property(nonatomic) NSUInteger generation;
31
- @property(nonatomic) NSUInteger tries;
37
+ // The content whose mounts the search hears, while it looks.
38
+ @property(nonatomic, weak, nullable) UIView<ANNComponentProtocol> *watched;
32
39
  @property(nonatomic) BOOL attached;
33
40
  @property(nonatomic) BOOL done;
41
+ // The size of the preview, taken at the push.
42
+ @property(nonatomic) CGSize size;
43
+ // The screen rendered: at the push, or at the press for a screen of its own React root.
44
+ @property(nonatomic) BOOL rendered;
34
45
  // The screen's unseen place in the main window, at its preview size, until UIKit takes its view for the menu.
35
46
  @property(nonatomic, strong, nullable) UIView *host;
36
47
  @property(nonatomic, strong, nullable) ANNParking *parking;
@@ -48,6 +59,15 @@ static NSMutableDictionary<NSString *, ANNPreviewRecord *> *ANNPreviewRecords(vo
48
59
  return records;
49
60
  }
50
61
 
62
+ // A React screen with a root of its own renders at the press: a view with a preview costs no React root, no render and
63
+ // no effects of the screen until the user presses it. A native screen (a kind of component screen) renders as it is
64
+ // made, and a screen of the app's one tree renders in JavaScript as its slot is added: both render at the push.
65
+ static BOOL ANNPreviewRendersAtPress(UIViewController *screen) {
66
+ return [screen isKindOfClass:ANNComponentViewController.class] &&
67
+ ![screen isKindOfClass:ANNExternalViewController.class] &&
68
+ ![screen.layoutInfo.name hasPrefix:ANNSlotPrefix];
69
+ }
70
+
51
71
  @implementation ANNPreviewPush
52
72
 
53
73
  + (void)push:(ANNLayoutNode *)layout
@@ -68,6 +88,7 @@ static NSMutableDictionary<NSString *, ANNPreviewRecord *> *ANNPreviewRecords(vo
68
88
  ANNPreviewRecord *record = [ANNPreviewRecord new];
69
89
  record.previewId = layout.nodeId;
70
90
  record.screen = screen;
91
+ record.size = size;
71
92
  record.source = source;
72
93
  record.answer = answer;
73
94
  // The key applies only once the tap made the preview a screen of the stack.
@@ -78,18 +99,22 @@ static NSMutableDictionary<NSString *, ANNPreviewRecord *> *ANNPreviewRecords(vo
78
99
  record.generation = answer.generation;
79
100
  ANNPreviewRecords()[record.previewId] = record;
80
101
  @try {
81
- [self parkAndRender:record size:size];
102
+ if (!ANNPreviewRendersAtPress(screen)) {
103
+ [self parkAndRender:record];
104
+ }
82
105
  } @catch (NSException *exception) {
83
106
  [self finish:record];
84
107
  [screen destroy];
85
108
  @throw;
86
109
  }
87
- [self search:record];
110
+ [self startSearch:record];
88
111
  }
89
112
 
90
- // The screen renders as now: its view waits unseen in the main window, at the size of the preview, so that its content
113
+ // The screen renders, once: its view waits unseen in the main window, at the size of the preview, so that its content
91
114
  // lays out; nothing waits for it.
92
- + (void)parkAndRender:(ANNPreviewRecord *)record size:(CGSize)size {
115
+ + (void)parkAndRender:(ANNPreviewRecord *)record {
116
+ record.rendered = YES;
117
+ CGSize size = record.size;
93
118
  UIWindow *window = ApexNavigation.mainWindow;
94
119
  if (window) {
95
120
  UIView *host = [[UIView alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)];
@@ -110,42 +135,103 @@ static NSMutableDictionary<NSString *, ANNPreviewRecord *> *ANNPreviewRecords(vo
110
135
 
111
136
  #pragma mark - The search
112
137
 
113
- // Each try asks first whether the preview still lives: a source that went makes it go, a reload stops it silently.
114
- + (void)search:(ANNPreviewRecord *)record {
115
- if (record.done || record.attached) {
138
+ // The view is looked for at once; else again at each mount of the source's content, where it comes with one of them,
139
+ // and a last time at the limit, when the preview goes. Both later steps keep the generation they were made with: after
140
+ // a reload they do nothing.
141
+ + (void)startSearch:(ANNPreviewRecord *)record {
142
+ if ([self search:record last:NO]) {
116
143
  return;
117
144
  }
145
+ UIViewController *source = record.source;
146
+ // A source that is a container (a stack) is heard through the screen it shows.
147
+ UIView<ANNComponentProtocol> *content =
148
+ source.reactView ?: source.presentedComponentViewController.reactView;
149
+ __weak ANNPreviewRecord *weakRecord = record;
150
+ NSUInteger generation = record.generation;
151
+ // A slot screen hears only the first mount of its slot, not those deeper in it, and a container may show no React
152
+ // content yet: there the view is looked for on each frame until the limit.
153
+ if (!content || [content isKindOfClass:ANNSlotScreenView.class]) {
154
+ [self searchNextFrame:record];
155
+ } else {
156
+ record.watched = content;
157
+ [content setContentDidMount:^{
158
+ ANNPreviewRecord *searching = weakRecord;
159
+ if (searching && generation == ANNReloadCoordinator.generation) {
160
+ [ANNPreviewPush search:searching last:NO];
161
+ }
162
+ }
163
+ forKey:[self mountKeyOf:record]];
164
+ }
165
+ int64_t delay = (int64_t)(ANNPreviewSearchLimit * NSEC_PER_SEC);
166
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay), dispatch_get_main_queue(), ^{
167
+ ANNPreviewRecord *searching = weakRecord;
168
+ if (searching && generation == ANNReloadCoordinator.generation) {
169
+ [ANNPreviewPush search:searching last:YES];
170
+ }
171
+ });
172
+ }
173
+
174
+ // One more search on the next frame, and so on until the view is found or the preview goes; after a reload, none.
175
+ + (void)searchNextFrame:(ANNPreviewRecord *)record {
176
+ __weak ANNPreviewRecord *weakRecord = record;
177
+ NSUInteger generation = record.generation;
178
+ int64_t delay = (int64_t)(ANNPreviewSearchInterval * NSEC_PER_SEC);
179
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay), dispatch_get_main_queue(), ^{
180
+ ANNPreviewRecord *searching = weakRecord;
181
+ if (searching && generation == ANNReloadCoordinator.generation &&
182
+ ![ANNPreviewPush search:searching last:NO]) {
183
+ [ANNPreviewPush searchNextFrame:searching];
184
+ }
185
+ });
186
+ }
187
+
188
+ // YES when the search is over: the view was found, or the preview went or stopped. Each search asks first whether the
189
+ // preview still lives: a source that went makes it go, a reload stops it silently. The last one, not finding the view,
190
+ // makes the preview go.
191
+ + (BOOL)search:(ANNPreviewRecord *)record last:(BOOL)last {
192
+ if (record.done || record.attached) {
193
+ return YES;
194
+ }
118
195
  if (record.generation != ANNReloadCoordinator.generation) {
119
- return;
196
+ return YES;
120
197
  }
121
198
  UIViewController *source = record.source;
122
199
  if (!source || source.nodeHelper.destroyed) {
123
200
  [self gone:record];
124
- return;
201
+ return YES;
125
202
  }
126
203
  // Read only: the view is found by its `nativeID`, and its tag is its current Fabric tag.
127
204
  UIView *view = [ANNPopoverPresenter viewWithNativeID:record.nativeID in:source.viewIfLoaded];
128
205
  if (view) {
129
206
  [self attach:record toView:view source:source];
130
- return;
207
+ return YES;
131
208
  }
132
- if (++record.tries >= ANNPreviewSearchTries) {
209
+ if (last) {
133
210
  [self gone:record];
134
- return;
211
+ return YES;
135
212
  }
136
- __weak ANNPreviewRecord *weakRecord = record;
137
- int64_t delay = (int64_t)(ANNPreviewSearchInterval * NSEC_PER_SEC);
138
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay), dispatch_get_main_queue(), ^{
139
- ANNPreviewRecord *searching = weakRecord;
140
- if (searching) {
141
- [ANNPreviewPush search:searching];
142
- }
143
- });
213
+ return NO;
214
+ }
215
+
216
+ // The key of the search's mount handler: one per preview, so that two previews of one source don't replace each other.
217
+ + (NSString *)mountKeyOf:(ANNPreviewRecord *)record {
218
+ return [@"preview." stringByAppendingString:record.previewId];
219
+ }
220
+
221
+ // The search stops hearing the mounts: only its own handler goes.
222
+ + (void)stopWatching:(ANNPreviewRecord *)record {
223
+ UIView<ANNComponentProtocol> *content = record.watched;
224
+ record.watched = nil;
225
+ [content setContentDidMount:nil forKey:[self mountKeyOf:record]];
144
226
  }
145
227
 
146
228
  + (void)attach:(ANNPreviewRecord *)record toView:(UIView *)view source:(UIViewController *)source {
147
229
  record.attached = YES;
230
+ [self stopWatching:record];
148
231
  __weak ANNPreviewRecord *weakRecord = record;
232
+ BOOL (^prepare)(void) = ^BOOL {
233
+ return [ANNPreviewPush renderAtPress:weakRecord];
234
+ };
149
235
  BOOL (^commit)(void) = ^BOOL {
150
236
  return [ANNPreviewPush commit:weakRecord];
151
237
  };
@@ -156,10 +242,35 @@ static NSMutableDictionary<NSString *, ANNPreviewRecord *> *ANNPreviewRecords(vo
156
242
  toView:view
157
243
  reactTag:view.tag
158
244
  source:source
245
+ prepare:prepare
159
246
  commit:commit
160
247
  dismissed:dismissed];
161
248
  }
162
249
 
250
+ #pragma mark - The press
251
+
252
+ // The press starts (UIKit asks for the menu's configuration, about half a second before it shows the preview): the
253
+ // screen renders now if it hasn't yet, so that its content is there when the preview shows. Out of the command's
254
+ // runner, a failure rejects the push with its text and the preview goes. YES when the screen rendered. A preview of the
255
+ // JavaScript before a reload renders nothing: its screen would start in the new one.
256
+ + (BOOL)renderAtPress:(ANNPreviewRecord *)record {
257
+ if (!record || record.done || !record.answer.isCurrent) {
258
+ return NO;
259
+ }
260
+ if (record.rendered) {
261
+ return YES;
262
+ }
263
+ @try {
264
+ [self parkAndRender:record];
265
+ } @catch (NSException *exception) {
266
+ [self finish:record];
267
+ [record.screen destroy];
268
+ [record.answer reject:[ANNCommandRunner rejectionOf:exception what:record.what]];
269
+ return NO;
270
+ }
271
+ return YES;
272
+ }
273
+
163
274
  #pragma mark - The tap
164
275
 
165
276
  // YES once the core took the preview: a key that leads back to an entry, or the push the queue accepted. From then on
@@ -206,7 +317,9 @@ static NSMutableDictionary<NSString *, ANNPreviewRecord *> *ANNPreviewRecords(vo
206
317
  return YES;
207
318
  }
208
319
  // The preview grows into the screen: a push without an animation, and without a wait, as the screen rendered
209
- // already; its answer at the end of the transaction.
320
+ // already; its answer at the end of the transaction. A screen that rendered only as the menu opened (a pointer's
321
+ // right click, then a quick tap) may have no content yet: its push waits for it as a push does.
322
+ BOOL waits = ANNPreviewRendersAtPress(screen) && !screen.reactView.isReady;
210
323
  [stack setKey:key ofEntry:screen];
211
324
  [stack.queue push:screen
212
325
  answer:record.answer
@@ -214,7 +327,7 @@ static NSMutableDictionary<NSString *, ANNPreviewRecord *> *ANNPreviewRecords(vo
214
327
  componentId:record.componentId
215
328
  source:entry
216
329
  animated:NO
217
- waits:NO];
330
+ waits:waits];
218
331
  return YES;
219
332
  }
220
333
 
@@ -231,9 +344,10 @@ static NSMutableDictionary<NSString *, ANNPreviewRecord *> *ANNPreviewRecords(vo
231
344
  [record.answer reject:[ANNErrorTexts previewGone:record.previewId]];
232
345
  }
233
346
 
234
- // The record is done: the screen leaves its unseen place, the table forgets it.
347
+ // The record is done: the search stops, the screen leaves its unseen place, the table forgets it.
235
348
  + (void)finish:(ANNPreviewRecord *)record {
236
349
  record.done = YES;
350
+ [self stopWatching:record];
237
351
  [self unpark:record];
238
352
  if (ANNPreviewRecords()[record.previewId] == record) {
239
353
  [ANNPreviewRecords() removeObjectForKey:record.previewId];
@@ -286,6 +400,7 @@ static NSMutableDictionary<NSString *, ANNPreviewRecord *> *ANNPreviewRecords(vo
286
400
  NSMutableArray<UIViewController *> *screens = [NSMutableArray new];
287
401
  for (ANNPreviewRecord *record in ANNPreviewRecords().allValues) {
288
402
  record.done = YES;
403
+ [self stopWatching:record];
289
404
  [self unpark:record];
290
405
  if (!record.attached) {
291
406
  [screens addObject:record.screen];
@@ -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;