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

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 (242) hide show
  1. package/ApexNavigation.podspec +2 -1
  2. package/CHANGELOG.md +15 -0
  3. package/android/CLAUDE.md +19 -10
  4. package/android/CORE.md +3 -2
  5. package/android/consumer-rules.pro +6 -0
  6. package/android/src/main/java/dev/apexnative/navigation/options/BottomTabOptions.kt +1 -1
  7. package/android/src/main/java/dev/apexnative/navigation/options/ButtonOptions.kt +1 -1
  8. package/android/src/main/java/dev/apexnative/navigation/options/LargeTitleOptions.kt +1 -1
  9. package/android/src/main/java/dev/apexnative/navigation/options/LayoutAnimation.kt +1 -1
  10. package/android/src/main/java/dev/apexnative/navigation/options/OptionKey.kt +18 -0
  11. package/android/src/main/java/dev/apexnative/navigation/options/OptionPaths.kt +91 -0
  12. package/android/src/main/java/dev/apexnative/navigation/options/SheetOptions.kt +7 -4
  13. package/android/src/main/java/dev/apexnative/navigation/options/TitleOptions.kt +2 -2
  14. package/android/src/main/java/dev/apexnative/navigation/options/TopBarAppearanceOptions.kt +1 -1
  15. package/android/src/main/java/dev/apexnative/navigation/options/TopBarOptions.kt +16 -7
  16. package/android/src/main/java/dev/apexnative/navigation/react/ContentHeights.kt +95 -0
  17. package/android/src/main/java/dev/apexnative/navigation/react/ContentMounts.kt +66 -0
  18. package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +4 -0
  19. package/android/src/main/java/dev/apexnative/navigation/react/OptionsCommands.kt +41 -3
  20. package/android/src/main/java/dev/apexnative/navigation/react/ReactView.kt +16 -1
  21. package/android/src/main/java/dev/apexnative/navigation/slots/SlotScreenView.kt +27 -7
  22. package/android/src/main/java/dev/apexnative/navigation/slots/SlotView.kt +12 -1
  23. package/android/src/main/java/dev/apexnative/navigation/utils/MaterialTheme.kt +5 -6
  24. package/android/src/main/java/dev/apexnative/navigation/utils/SystemAnimations.kt +36 -0
  25. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +329 -55
  26. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +26 -5
  27. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetStops.kt +60 -2
  28. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuMotion.kt +8 -2
  29. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/BackPreview.kt +32 -4
  30. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +46 -7
  31. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +15 -3
  32. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +7 -4
  33. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabMotion.kt +142 -0
  34. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt +215 -43
  35. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +25 -26
  36. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/BarRowTransition.kt +3 -2
  37. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/ButtonPlaces.kt +11 -5
  38. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +86 -39
  39. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarVisibility.kt +9 -2
  40. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +124 -181
  41. package/android/src/main/java/dev/apexnative/navigation/views/element/TargetsWait.kt +8 -1
  42. package/android/src/main/java/dev/apexnative/navigation/views/element/TransitionLayer.kt +37 -112
  43. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +56 -3
  44. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt +170 -153
  45. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMorph.kt +199 -0
  46. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +55 -45
  47. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/{TextChangeAnimator.kt → TextMorph.kt} +79 -129
  48. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewMorph.kt +108 -0
  49. package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabCopy.kt +85 -0
  50. package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabMenu.kt +13 -0
  51. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TitleBar.kt +5 -24
  52. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TopBar.kt +51 -34
  53. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonComponentCell.kt +6 -6
  54. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonView.kt +113 -0
  55. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarMenu.kt +219 -0
  56. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonBar.kt +121 -175
  57. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonMenus.kt +34 -64
  58. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonRow.kt +216 -0
  59. package/android/src/main/jni/ANNContentHeights.cpp +82 -0
  60. package/android/src/main/res/drawable/apex_navigation_ic_arrow_right.xml +14 -0
  61. package/android/src/main/res/layout/apex_navigation_menu_header.xml +19 -0
  62. package/android/src/main/res/layout/apex_navigation_menu_item.xml +68 -0
  63. package/android/src/main/res/values/styles.xml +1 -1
  64. package/android/src/main/res/values-night/styles.xml +1 -1
  65. package/cli/doctor/checks/app.js +16 -1
  66. package/cli/project.js +4 -0
  67. package/cli/setup/index.js +5 -0
  68. package/cli/setup/steps/metro.js +46 -0
  69. package/docs-md/architecture/native-screens.md +4 -0
  70. package/docs-md/architecture/theme-and-context.md +131 -12
  71. package/docs-md/architecture/transitions.md +43 -4
  72. package/docs-md/flows/bottom-sheets.md +4 -2
  73. package/docs-md/fundamentals/screens-and-props.md +9 -1
  74. package/docs-md/guides/cli.md +3 -1
  75. package/docs-md/help/troubleshooting.md +1 -1
  76. package/docs-md/index.json +29 -1
  77. package/docs-md/screen-ui/back-and-gestures.md +3 -1
  78. package/docs-md/screen-ui/bar-buttons.md +1 -1
  79. package/docs-md/screen-ui/toolbar-and-fab.md +2 -0
  80. package/docs-md/start/installation.md +16 -1
  81. package/ios/Animations/ANNFrameDriver.h +55 -15
  82. package/ios/Animations/ANNFrameDriver.mm +214 -60
  83. package/ios/Animations/ANNTransitionAnimator.h +26 -5
  84. package/ios/Animations/ANNTransitionAnimator.mm +49 -3
  85. package/ios/Animations/ANNTransitionPlan.h +2 -2
  86. package/ios/Animations/ANNTransitionPlan.mm +10 -0
  87. package/ios/Animations/Elements/ANNElementFinder.h +2 -2
  88. package/ios/Animations/Elements/ANNElementFinder.mm +2 -2
  89. package/ios/Animations/Elements/ANNElementTransition.h +3 -3
  90. package/ios/Animations/Elements/ANNElementTransition.mm +2 -2
  91. package/ios/Animations/Elements/ANNSharedElementFlight.h +3 -3
  92. package/ios/Animations/Elements/ANNSharedElementFlight.mm +181 -43
  93. package/ios/Animations/Interpolators/ANNViewTracks.h +6 -5
  94. package/ios/Animations/Interpolators/ANNViewTracks.mm +51 -21
  95. package/ios/CLAUDE.md +19 -8
  96. package/ios/CORE.md +1 -1
  97. package/ios/Core/ANNAppDelegate.mm +0 -2
  98. package/ios/Core/ANNDeepLinkQueue.h +6 -5
  99. package/ios/Core/ANNDeepLinkQueue.mm +22 -10
  100. package/ios/Core/ANNSceneDelegate.mm +2 -2
  101. package/ios/Core/ANNUtils.h +7 -0
  102. package/ios/Core/ANNUtils.mm +10 -0
  103. package/ios/Core/ApexNavigation.mm +6 -2
  104. package/ios/Layouts/Overlay/ANNOverlayCoordinator.mm +1 -7
  105. package/ios/Layouts/Root/ANNRootCoordinator.mm +45 -15
  106. package/ios/Layouts/Screen/ANNComponentViewController.mm +4 -1
  107. package/ios/Layouts/Stack/ANNStackAnimatingDelegate.h +11 -2
  108. package/ios/Layouts/Stack/ANNStackAnimatingDelegate.mm +14 -7
  109. package/ios/Layouts/Stack/ANNStackController.h +8 -0
  110. package/ios/Layouts/Stack/ANNStackController.mm +153 -11
  111. package/ios/Layouts/Stack/ANNSwipePopInteraction.h +26 -0
  112. package/ios/Layouts/Stack/ANNSwipePopInteraction.mm +185 -0
  113. package/ios/Layouts/Stack/InteractivePopGestureDelegate.mm +28 -4
  114. package/ios/Layouts/Tabs/ANNTabsPreloadWindow.mm +2 -4
  115. package/ios/Options/ANNAnimationOptions.h +2 -1
  116. package/ios/Options/ANNAnimationOptions.mm +1 -1
  117. package/ios/Options/ANNOptions.h +7 -0
  118. package/ios/Options/ANNOptions.mm +45 -0
  119. package/ios/Options/ANNOptionsReader.mm +2 -4
  120. package/ios/Options/ANNStackPopAnimationOptions.h +13 -0
  121. package/ios/Options/ANNStackPopAnimationOptions.mm +11 -0
  122. package/ios/Options/Params/ColorParser.h +4 -3
  123. package/ios/Options/Params/ColorParser.mm +22 -4
  124. package/ios/React/ANNSurfaceHostingView.h +8 -0
  125. package/ios/React/ANNSurfaceHostingView.mm +28 -1
  126. package/ios/React/Slots/ANNSlotHost.mm +1 -3
  127. package/ios/React/Slots/ANNSlotScreenView.mm +8 -2
  128. package/ios/TurboModules/ANNTurboModule.mm +46 -0
  129. package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.cpp +20 -0
  130. package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.h +5 -14
  131. package/lib/module/ARCHITECTURE.md +9 -3
  132. package/lib/module/adapters/NativeANNTurboModule.js.map +1 -1
  133. package/lib/module/api/app/AppRegistry.js +14 -2
  134. package/lib/module/api/app/AppRegistry.js.map +1 -1
  135. package/lib/module/api/app/createNavigation.js +188 -66
  136. package/lib/module/api/app/createNavigation.js.map +1 -1
  137. package/lib/module/api/events/navigationEvents.js +6 -2
  138. package/lib/module/api/events/navigationEvents.js.map +1 -1
  139. package/lib/module/api/linking/linking.js +11 -0
  140. package/lib/module/api/linking/linking.js.map +1 -1
  141. package/lib/module/api/screens/defineScreen.js +45 -3
  142. package/lib/module/api/screens/defineScreen.js.map +1 -1
  143. package/lib/module/api/screens/nativeScreens.js +31 -6
  144. package/lib/module/api/screens/nativeScreens.js.map +1 -1
  145. package/lib/module/api/screens/registerScreens.js +193 -35
  146. package/lib/module/api/screens/registerScreens.js.map +1 -1
  147. package/lib/module/api/theme/theme.js +5 -0
  148. package/lib/module/api/theme/theme.js.map +1 -1
  149. package/lib/module/engine/commands.js +21 -1
  150. package/lib/module/engine/commands.js.map +1 -1
  151. package/lib/module/engine/layout.js +7 -2
  152. package/lib/module/engine/layout.js.map +1 -1
  153. package/lib/module/engine/nativeScreens.js +9 -0
  154. package/lib/module/engine/nativeScreens.js.map +1 -1
  155. package/lib/module/engine/options.js +5 -4
  156. package/lib/module/engine/options.js.map +1 -1
  157. package/lib/module/engine/optionsChange.js +102 -0
  158. package/lib/module/engine/optionsChange.js.map +1 -0
  159. package/lib/module/engine/refresh.js +85 -0
  160. package/lib/module/engine/refresh.js.map +1 -0
  161. package/lib/module/engine/rememberedOptions.js +60 -0
  162. package/lib/module/engine/rememberedOptions.js.map +1 -1
  163. package/lib/module/engine/screens.js +26 -0
  164. package/lib/module/engine/screens.js.map +1 -1
  165. package/lib/typescript/adapters/NativeANNTurboModule.d.ts +7 -0
  166. package/lib/typescript/adapters/NativeANNTurboModule.d.ts.map +1 -1
  167. package/lib/typescript/api/app/AppRegistry.d.ts.map +1 -1
  168. package/lib/typescript/api/app/createNavigation.d.ts +5 -0
  169. package/lib/typescript/api/app/createNavigation.d.ts.map +1 -1
  170. package/lib/typescript/api/events/navigationEvents.d.ts +2 -2
  171. package/lib/typescript/api/events/navigationEvents.d.ts.map +1 -1
  172. package/lib/typescript/api/index.d.ts +1 -1
  173. package/lib/typescript/api/index.d.ts.map +1 -1
  174. package/lib/typescript/api/linking/linking.d.ts +8 -0
  175. package/lib/typescript/api/linking/linking.d.ts.map +1 -1
  176. package/lib/typescript/api/screens/defineScreen.d.ts +23 -4
  177. package/lib/typescript/api/screens/defineScreen.d.ts.map +1 -1
  178. package/lib/typescript/api/screens/nativeScreens.d.ts +8 -1
  179. package/lib/typescript/api/screens/nativeScreens.d.ts.map +1 -1
  180. package/lib/typescript/api/screens/registerScreens.d.ts.map +1 -1
  181. package/lib/typescript/api/theme/theme.d.ts +2 -0
  182. package/lib/typescript/api/theme/theme.d.ts.map +1 -1
  183. package/lib/typescript/engine/commands.d.ts +5 -0
  184. package/lib/typescript/engine/commands.d.ts.map +1 -1
  185. package/lib/typescript/engine/layout.d.ts.map +1 -1
  186. package/lib/typescript/engine/nativeScreens.d.ts +5 -0
  187. package/lib/typescript/engine/nativeScreens.d.ts.map +1 -1
  188. package/lib/typescript/engine/optionsChange.d.ts +28 -0
  189. package/lib/typescript/engine/optionsChange.d.ts.map +1 -0
  190. package/lib/typescript/engine/refresh.d.ts +35 -0
  191. package/lib/typescript/engine/refresh.d.ts.map +1 -0
  192. package/lib/typescript/engine/rememberedOptions.d.ts +13 -0
  193. package/lib/typescript/engine/rememberedOptions.d.ts.map +1 -1
  194. package/lib/typescript/engine/screens.d.ts +7 -0
  195. package/lib/typescript/engine/screens.d.ts.map +1 -1
  196. package/lib/typescript/interfaces/ComponentEvents.d.ts +2 -2
  197. package/lib/typescript/interfaces/options/animations.d.ts +40 -15
  198. package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
  199. package/lib/typescript/interfaces/options/bottomTabs.d.ts +2 -2
  200. package/lib/typescript/interfaces/options/buttons.d.ts +2 -2
  201. package/lib/typescript/interfaces/options/modals.d.ts +6 -7
  202. package/lib/typescript/interfaces/options/modals.d.ts.map +1 -1
  203. package/lib/typescript/interfaces/options/stack.d.ts +8 -3
  204. package/lib/typescript/interfaces/options/stack.d.ts.map +1 -1
  205. package/lib/typescript/interfaces/options/topBar.d.ts +14 -0
  206. package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
  207. package/metro/acceptPlugin.js +57 -0
  208. package/metro/babelTransformer.js +45 -0
  209. package/metro/index.js +36 -0
  210. package/package.json +3 -1
  211. package/src/ARCHITECTURE.md +9 -3
  212. package/src/adapters/NativeANNTurboModule.ts +7 -0
  213. package/src/api/app/AppRegistry.ts +16 -2
  214. package/src/api/app/createNavigation.ts +198 -66
  215. package/src/api/events/navigationEvents.ts +9 -3
  216. package/src/api/index.ts +1 -0
  217. package/src/api/linking/linking.ts +22 -1
  218. package/src/api/screens/defineScreen.ts +49 -5
  219. package/src/api/screens/nativeScreens.ts +40 -4
  220. package/src/api/screens/registerScreens.tsx +185 -42
  221. package/src/api/theme/theme.ts +5 -0
  222. package/src/engine/commands.ts +21 -0
  223. package/src/engine/layout.ts +11 -2
  224. package/src/engine/nativeScreens.ts +10 -1
  225. package/src/engine/options.ts +6 -8
  226. package/src/engine/optionsChange.ts +107 -0
  227. package/src/engine/refresh.ts +97 -0
  228. package/src/engine/rememberedOptions.ts +70 -0
  229. package/src/engine/screens.tsx +24 -0
  230. package/src/interfaces/ComponentEvents.ts +2 -2
  231. package/src/interfaces/options/animations.ts +40 -15
  232. package/src/interfaces/options/bottomTabs.ts +2 -2
  233. package/src/interfaces/options/buttons.ts +2 -2
  234. package/src/interfaces/options/modals.ts +6 -7
  235. package/src/interfaces/options/stack.ts +8 -3
  236. package/src/interfaces/options/topBar.ts +14 -0
  237. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementStub.kt +0 -31
  238. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +0 -134
  239. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +0 -78
  240. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PropertyAnimatorCreator.kt +0 -33
  241. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +0 -57
  242. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +0 -146
@@ -9,9 +9,9 @@ NS_ASSUME_NONNULL_BEGIN
9
9
  /// One shared pair in flight: a plain view of the core, the copy, flies in the transition's container from the `from`
10
10
  /// view to the `to` view with two pictures of them (`renderInContext:` at the screen's scale). The React views are
11
11
  /// neither moved nor changed: they are only `hidden` while the copy flies and get back what they had before the
12
- /// transition completes. All geometry is in the container's coordinates. Each frame the copy's size, center, visible
13
- /// part (a mask), corner radius and layer transform go from the `from` values to the `to` values on the pair's curve,
14
- /// delay and duration.
12
+ /// transition completes. All geometry is in the container's coordinates. The copy's size, center, visible part (a
13
+ /// mask), corner radius and layer transform go from the `from` values to the `to` values on the pair's curve, delay and
14
+ /// duration, in Core Animation's animations of the copy's layers.
15
15
  ///
16
16
  /// A view (not a text): both pictures fill the copy, the copy clips to its corners, the `to` picture fades in over the
17
17
  /// flight. A text (React's paragraph): the pictures keep their aspect, as tall as the copy, at the line's start (the
@@ -29,6 +29,21 @@ typedef struct {
29
29
  BOOL clipped;
30
30
  } ANNFlightEnd;
31
31
 
32
+ // What the copy shows at one time: its size, center, transform and corners, its two pictures' frames and alphas, and
33
+ // its visible part (in the copy's coordinates) with its corners.
34
+ typedef struct {
35
+ CGSize size;
36
+ CGPoint center;
37
+ CATransform3D transform;
38
+ CGFloat cornerRadius;
39
+ CGRect fromFrame;
40
+ CGRect toFrame;
41
+ CGFloat fromAlpha;
42
+ CGFloat toAlpha;
43
+ CGRect mask;
44
+ CGFloat maskRadius;
45
+ } ANNFlightState;
46
+
32
47
  static BOOL ANNIsText(UIView *view) {
33
48
  static Class paragraph;
34
49
  static dispatch_once_t once;
@@ -200,6 +215,30 @@ static UIImage *ANNPictureOf(UIView *view) {
200
215
  }];
201
216
  }
202
217
 
218
+ // A layer at `frame` in its superlayer: its bounds and its position (its anchor is its center).
219
+ static void ANNPlaceLayer(CALayer *layer, CGRect frame) {
220
+ layer.bounds = CGRectMake(0, 0, frame.size.width, frame.size.height);
221
+ layer.position = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
222
+ }
223
+
224
+ // The core's animation of `keyPath` on `layer` (`ANNKeyframes`), under its key.
225
+ static void ANNAddKeyframes(CALayer *layer, NSString *keyPath, NSArray *values,
226
+ NSArray<NSNumber *> *times, NSTimeInterval length) {
227
+ CAKeyframeAnimation *animation = ANNKeyframes(keyPath, values, times, length);
228
+ if (animation) {
229
+ [layer addAnimation:animation forKey:ANNAnimationKey(keyPath)];
230
+ }
231
+ }
232
+
233
+ // The bounds and the position of a layer at `frame`, as values of its animations.
234
+ static NSValue *ANNBoundsValue(CGRect frame) {
235
+ return [NSValue valueWithCGRect:CGRectMake(0, 0, frame.size.width, frame.size.height)];
236
+ }
237
+
238
+ static NSValue *ANNPositionValue(CGRect frame) {
239
+ return [NSValue valueWithCGPoint:CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame))];
240
+ }
241
+
203
242
  @implementation ANNSharedElementFlight {
204
243
  __weak UIView *_fromView;
205
244
  __weak UIView *_toView;
@@ -208,7 +247,9 @@ static UIImage *ANNPictureOf(UIView *view) {
208
247
  UIView *_copy;
209
248
  UIImageView *_fromPicture;
210
249
  UIImageView *_toPicture;
211
- CAShapeLayer *_mask;
250
+ // The visible part of the copy: a plain layer with corners, as Core Animation can't animate a path whose corners
251
+ // change.
252
+ CALayer *_mask;
212
253
  ANNFlightEnd _from;
213
254
  ANNFlightEnd _to;
214
255
  ANNTrack *_track;
@@ -343,7 +384,8 @@ static UIImage *ANNPictureOf(UIView *view) {
343
384
  // A text's pictures are neither clipped nor masked.
344
385
  _copy.clipsToBounds = !_text;
345
386
  if (!_text && (_from.clipped || _to.clipped)) {
346
- _mask = [CAShapeLayer layer];
387
+ _mask = [CALayer layer];
388
+ _mask.backgroundColor = UIColor.blackColor.CGColor;
347
389
  _copy.layer.mask = _mask;
348
390
  }
349
391
  [_copy addSubview:_fromPicture];
@@ -360,72 +402,168 @@ static UIImage *ANNPictureOf(UIView *view) {
360
402
  [container addSubview:_copy];
361
403
  from.hidden = YES;
362
404
  to.hidden = YES;
363
- [self applyAtTime:0];
405
+ [self placeAtTime:0];
364
406
  }
365
407
 
366
408
  - (UIView *)copyView {
367
409
  return _copy;
368
410
  }
369
411
 
370
- #pragma mark - ANNFrameClient
371
-
372
- - (NSTimeInterval)endTime {
373
- return _track.endTime;
374
- }
375
-
376
- - (void)applyAtTime:(NSTimeInterval)time {
377
- if (_restored) {
378
- return;
379
- }
412
+ // The copy's whole state at `time`: its own geometry, its pictures' and its visible part's.
413
+ - (ANNFlightState)stateAtTime:(NSTimeInterval)time {
414
+ ANNFlightState state;
380
415
  double f = [_track fractionAt:time];
381
- CGSize size = ANNInterpolateSize(_from.size, _to.size, f);
382
- _copy.layer.transform = CATransform3DIdentity;
383
- _copy.bounds = CGRectMake(0, 0, size.width, size.height);
384
- _copy.center = ANNInterpolatePoint(_from.center, _to.center, f);
385
- _copy.layer.transform =
386
- ANNTransformOfParts(ANNInterpolateTransformParts(_from.parts, _to.parts, f));
416
+ state.size = ANNInterpolateSize(_from.size, _to.size, f);
417
+ state.center = ANNInterpolatePoint(_from.center, _to.center, f);
418
+ state.transform = ANNTransformOfParts(ANNInterpolateTransformParts(_from.parts, _to.parts, f));
419
+ state.cornerRadius = 0;
420
+ state.mask = CGRectZero;
421
+ state.maskRadius = 0;
387
422
  if (_text) {
388
- [self placeTextPicturesAtTime:time size:size];
389
- return;
423
+ [self placeTextPicturesOf:&state atTime:time];
424
+ return state;
390
425
  }
391
- _copy.layer.cornerRadius = ANNInterpolateNumber(_from.cornerRadius, _to.cornerRadius, f);
392
- _fromPicture.frame = _copy.bounds;
393
- _toPicture.frame = _copy.bounds;
394
- _fromPicture.alpha = 1;
395
- _toPicture.alpha = (CGFloat)f;
426
+ state.cornerRadius = ANNInterpolateNumber(_from.cornerRadius, _to.cornerRadius, f);
427
+ CGRect whole = CGRectMake(0, 0, state.size.width, state.size.height);
428
+ state.fromFrame = whole;
429
+ state.toFrame = whole;
430
+ state.fromAlpha = 1;
431
+ state.toAlpha = (CGFloat)f;
396
432
  if (_mask) {
397
433
  CGRect fromWhole = CGRectMake(0, 0, _from.size.width, _from.size.height);
398
434
  CGRect toWhole = CGRectMake(0, 0, _to.size.width, _to.size.height);
399
435
  CGRect fromVisible = _from.clipped ? _from.visible : fromWhole;
400
436
  CGRect toVisible = _to.clipped ? _to.visible : toWhole;
401
- CGRect visible = ANNInterpolateRect(fromVisible, toVisible, f);
437
+ state.mask = ANNInterpolateRect(fromVisible, toVisible, f);
438
+ // At most half the shorter side, as a rounded path has it.
402
439
  CGFloat radius = ANNInterpolateNumber(_from.visibleRadius, _to.visibleRadius, f);
403
- _mask.frame = _copy.bounds;
404
- _mask.path = [UIBezierPath bezierPathWithRoundedRect:visible cornerRadius:radius].CGPath;
440
+ CGFloat half = MIN(state.mask.size.width, state.mask.size.height) / 2;
441
+ state.maskRadius = MAX(MIN(radius, half), 0);
405
442
  }
443
+ return state;
406
444
  }
407
445
 
408
446
  // The text's pictures keep their aspect at the height of the copy, at the line's start; they swap by the clock,
409
447
  // linearly, in the first quarter of the flight when the text grows, in the last when it shrinks.
410
- - (void)placeTextPicturesAtTime:(NSTimeInterval)time size:(CGSize)size {
411
- for (UIImageView *picture in @[ _fromPicture, _toPicture ]) {
412
- CGSize imageSize = picture.image.size;
413
- CGFloat height = size.height;
448
+ - (void)placeTextPicturesOf:(ANNFlightState *)state atTime:(NSTimeInterval)time {
449
+ CGRect frames[2];
450
+ UIImageView *pictures[2] = {_fromPicture, _toPicture};
451
+ for (int index = 0; index < 2; index++) {
452
+ CGSize imageSize = pictures[index].image.size;
453
+ CGFloat height = state->size.height;
414
454
  CGFloat width = imageSize.height > 0 ? imageSize.width * height / imageSize.height : 0;
415
- CGFloat x = _rightToLeft ? size.width - width : 0;
416
- picture.frame = CGRectMake(x, 0, width, height);
455
+ CGFloat x = _rightToLeft ? state->size.width - width : 0;
456
+ frames[index] = CGRectMake(x, 0, width, height);
417
457
  }
458
+ state->fromFrame = frames[0];
459
+ state->toFrame = frames[1];
460
+ double swap = [self swapAtTime:time];
461
+ state->fromAlpha = (CGFloat)(1 - swap);
462
+ state->toAlpha = (CGFloat)swap;
463
+ }
464
+
465
+ // The quarter of the flight the text's pictures swap in.
466
+ - (NSTimeInterval)swapStart {
418
467
  NSTimeInterval duration = _track.duration;
419
- NSTimeInterval quarter = duration / 4;
420
- NSTimeInterval start = _track.delay + (_grows ? 0 : duration - quarter);
421
- double swap;
468
+ return _track.delay + (_grows ? 0 : duration - duration / 4);
469
+ }
470
+
471
+ - (double)swapAtTime:(NSTimeInterval)time {
472
+ NSTimeInterval quarter = _track.duration / 4;
422
473
  if (quarter <= 0) {
423
- swap = time >= _track.delay ? 1 : 0;
424
- } else {
425
- swap = MIN(MAX((time - start) / quarter, 0.0), 1.0);
474
+ return time >= _track.delay ? 1 : 0;
475
+ }
476
+ return MIN(MAX((time - [self swapStart]) / quarter, 0.0), 1.0);
477
+ }
478
+
479
+ // The copy shows its state at `time` (its own values, with no animation).
480
+ - (void)placeAtTime:(NSTimeInterval)time {
481
+ ANNFlightState state = [self stateAtTime:time];
482
+ [CATransaction begin];
483
+ [CATransaction setDisableActions:YES];
484
+ _copy.layer.transform = CATransform3DIdentity;
485
+ _copy.bounds = CGRectMake(0, 0, state.size.width, state.size.height);
486
+ _copy.center = state.center;
487
+ _copy.layer.transform = state.transform;
488
+ if (!_text) {
489
+ _copy.layer.cornerRadius = state.cornerRadius;
490
+ }
491
+ ANNPlaceLayer(_fromPicture.layer, state.fromFrame);
492
+ ANNPlaceLayer(_toPicture.layer, state.toFrame);
493
+ _fromPicture.alpha = state.fromAlpha;
494
+ _toPicture.alpha = state.toAlpha;
495
+ if (_mask) {
496
+ ANNPlaceLayer(_mask, state.mask);
497
+ _mask.cornerRadius = state.maskRadius;
498
+ }
499
+ [CATransaction commit];
500
+ }
501
+
502
+ #pragma mark - ANNFrameClient
503
+
504
+ - (NSTimeInterval)endTime {
505
+ return _track.endTime;
506
+ }
507
+
508
+ - (void)addAnimationsWithLength:(NSTimeInterval)length {
509
+ if (_restored) {
510
+ return;
511
+ }
512
+ NSMutableArray<NSNumber *> *marks =
513
+ [NSMutableArray arrayWithObjects:@(_track.delay), @(_track.endTime), nil];
514
+ if (_text) {
515
+ [marks addObject:@([self swapStart])];
516
+ [marks addObject:@([self swapStart] + _track.duration / 4)];
517
+ }
518
+ NSArray<NSNumber *> *times = ANNSampleTimes(_track.endTime, length, marks);
519
+ NSMutableArray *bounds = [NSMutableArray new];
520
+ NSMutableArray *positions = [NSMutableArray new];
521
+ NSMutableArray *transforms = [NSMutableArray new];
522
+ NSMutableArray *radii = [NSMutableArray new];
523
+ NSMutableArray *fromBounds = [NSMutableArray new];
524
+ NSMutableArray *fromPositions = [NSMutableArray new];
525
+ NSMutableArray *fromAlphas = [NSMutableArray new];
526
+ NSMutableArray *toBounds = [NSMutableArray new];
527
+ NSMutableArray *toPositions = [NSMutableArray new];
528
+ NSMutableArray *toAlphas = [NSMutableArray new];
529
+ NSMutableArray *maskBounds = [NSMutableArray new];
530
+ NSMutableArray *maskPositions = [NSMutableArray new];
531
+ NSMutableArray *maskRadii = [NSMutableArray new];
532
+ for (NSNumber *time in times) {
533
+ ANNFlightState state = [self stateAtTime:time.doubleValue];
534
+ CGRect whole = CGRectMake(0, 0, state.size.width, state.size.height);
535
+ [bounds addObject:ANNBoundsValue(whole)];
536
+ [positions addObject:[NSValue valueWithCGPoint:state.center]];
537
+ [transforms addObject:[NSValue valueWithCATransform3D:state.transform]];
538
+ [radii addObject:@(state.cornerRadius)];
539
+ [fromBounds addObject:ANNBoundsValue(state.fromFrame)];
540
+ [fromPositions addObject:ANNPositionValue(state.fromFrame)];
541
+ [fromAlphas addObject:@(state.fromAlpha)];
542
+ [toBounds addObject:ANNBoundsValue(state.toFrame)];
543
+ [toPositions addObject:ANNPositionValue(state.toFrame)];
544
+ [toAlphas addObject:@(state.toAlpha)];
545
+ [maskBounds addObject:ANNBoundsValue(state.mask)];
546
+ [maskPositions addObject:ANNPositionValue(state.mask)];
547
+ [maskRadii addObject:@(state.maskRadius)];
548
+ }
549
+ CALayer *copy = _copy.layer;
550
+ ANNAddKeyframes(copy, @"bounds", bounds, times, length);
551
+ ANNAddKeyframes(copy, @"position", positions, times, length);
552
+ ANNAddKeyframes(copy, @"transform", transforms, times, length);
553
+ if (!_text) {
554
+ ANNAddKeyframes(copy, @"cornerRadius", radii, times, length);
555
+ }
556
+ ANNAddKeyframes(_fromPicture.layer, @"bounds", fromBounds, times, length);
557
+ ANNAddKeyframes(_fromPicture.layer, @"position", fromPositions, times, length);
558
+ ANNAddKeyframes(_fromPicture.layer, @"opacity", fromAlphas, times, length);
559
+ ANNAddKeyframes(_toPicture.layer, @"bounds", toBounds, times, length);
560
+ ANNAddKeyframes(_toPicture.layer, @"position", toPositions, times, length);
561
+ ANNAddKeyframes(_toPicture.layer, @"opacity", toAlphas, times, length);
562
+ if (_mask) {
563
+ ANNAddKeyframes(_mask, @"bounds", maskBounds, times, length);
564
+ ANNAddKeyframes(_mask, @"position", maskPositions, times, length);
565
+ ANNAddKeyframes(_mask, @"cornerRadius", maskRadii, times, length);
426
566
  }
427
- _fromPicture.alpha = (CGFloat)(1 - swap);
428
- _toPicture.alpha = (CGFloat)swap;
429
567
  }
430
568
 
431
569
  - (void)restore {
@@ -20,13 +20,14 @@ FOUNDATION_EXPORT CGFloat ANNCameraDistance(void);
20
20
  /// position and bounds, never its frame) moved by its own translation, `alpha` its alpha before any hide, the
21
21
  /// translations, scales and rotations those of its own transform (React Native's `transform`, split into its parts as
22
22
  /// Android's views hold it: the shorter angle, −180…180). A property that doesn't play keeps the view's own value, its
23
- /// skew and perspective stay. Each frame writes only what plays: `alpha` only with an `alpha` track, `layer.transform`
24
- /// only when a property of the transform plays; with `translationX` and `x` on one side the translation wins. The frame
25
- /// is never changed (React keeps the layout). `restore` puts back the alpha and the transform as they were read.
23
+ /// skew and perspective stay. Only what plays animates, in Core Animation (`ANNKeyframes`): `opacity` only with an
24
+ /// `alpha` track, `transform` only when a property of the transform plays; with `translationX` and `x` on one side the
25
+ /// translation wins. The view's own values (its frame, alpha and transform) are never written: the animations show
26
+ /// over them, and `restore` removes the animations.
26
27
  @interface ANNViewTracks : NSObject <ANNFrameClient>
27
28
 
28
29
  /// The playing properties of `side` on `view`. `alphaBefore`: the view's alpha before the core hid it for the
29
- /// transition (the arriving screen), else its alpha now; it is also what `restore` puts back.
30
+ /// transition (the arriving screen), else its alpha now.
30
31
  - (instancetype)initWithView:(UIView *)view
31
32
  side:(ANNViewAnimationOptions *)side
32
33
  alphaBefore:(CGFloat)alphaBefore;
@@ -35,7 +36,7 @@ FOUNDATION_EXPORT CGFloat ANNCameraDistance(void);
35
36
  /// disappearing from it to `to`, or both ends given.
36
37
  - (instancetype)initWithView:(UIView *)view element:(ANNElementTransitionOptions *)element;
37
38
 
38
- /// A linear fade of `view`'s alpha (a dismissed popover with nothing to play); `restore` puts back `alphaBefore`.
39
+ /// A linear fade of `view`'s alpha (a dismissed popover with nothing to play, a transition under Reduce Motion).
39
40
  - (instancetype)initWithView:(UIView *)view
40
41
  fadeFrom:(CGFloat)from
41
42
  to:(CGFloat)to
@@ -93,8 +93,8 @@ static ANNTrack *ANNTrackOf(Double *from, Double *to, Double *duration, Double *
93
93
  CGFloat _ownPerspective;
94
94
  BOOL _animatesTransform;
95
95
  BOOL _tilts;
96
- BOOL _wroteAlpha;
97
- BOOL _wroteTransform;
96
+ BOOL _addedAlpha;
97
+ BOOL _addedTransform;
98
98
  BOOL _restored;
99
99
  NSTimeInterval _endTime;
100
100
  }
@@ -235,17 +235,9 @@ static ANNTrack *ANNTrackOf(Double *from, Double *to, Double *duration, Double *
235
235
  return track ? [track valueAt:time] : [self viewValueOf:property];
236
236
  }
237
237
 
238
- - (void)applyAtTime:(NSTimeInterval)time {
239
- if (_restored) {
240
- return;
241
- }
242
- if (self.animatesAlpha) {
243
- _view.alpha = (CGFloat)[_tracks[ANNViewPropertyAlpha] valueAt:time];
244
- _wroteAlpha = YES;
245
- }
246
- if (!_animatesTransform) {
247
- return;
248
- }
238
+ // The view's transform at `time`: every property of the transform at its value then, the ones that don't play at the
239
+ // view's own.
240
+ - (CATransform3D)transformAtTime:(NSTimeInterval)time {
249
241
  CGFloat scaleX = (CGFloat)[self valueOf:ANNViewPropertyScaleX at:time];
250
242
  CGFloat scaleY = (CGFloat)[self valueOf:ANNViewPropertyScaleY at:time];
251
243
  // The translation wins over the place when a side has both.
@@ -279,10 +271,48 @@ static ANNTrack *ANNTrackOf(Double *from, Double *to, Double *duration, Double *
279
271
  perspective.m34 = _ownPerspective != 0 ? _ownPerspective : -1.0 / ANNCameraDistance();
280
272
  transform = CATransform3DConcat(transform, perspective);
281
273
  }
282
- transform = CATransform3DConcat(transform,
283
- CATransform3DMakeTranslation(translationX, translationY, 0));
284
- _view.layer.transform = transform;
285
- _wroteTransform = YES;
274
+ return CATransform3DConcat(transform,
275
+ CATransform3DMakeTranslation(translationX, translationY, 0));
276
+ }
277
+
278
+ - (void)addAnimationsWithLength:(NSTimeInterval)length {
279
+ if (_restored || !self.hasTracks) {
280
+ return;
281
+ }
282
+ // Every track's start and end is a sample, so a track that starts late or ends early does it on time.
283
+ NSMutableArray<NSNumber *> *marks = [NSMutableArray new];
284
+ for (NSInteger index = 0; index < ANNViewPropertyCount; index++) {
285
+ ANNTrack *track = _tracks[index];
286
+ if (track) {
287
+ [marks addObject:@(track.delay)];
288
+ [marks addObject:@(track.endTime)];
289
+ }
290
+ }
291
+ NSArray<NSNumber *> *times = ANNSampleTimes(_endTime, length, marks);
292
+ CALayer *layer = _view.layer;
293
+ if (self.animatesAlpha) {
294
+ NSMutableArray<NSNumber *> *values = [NSMutableArray arrayWithCapacity:times.count];
295
+ for (NSNumber *time in times) {
296
+ [values addObject:@([_tracks[ANNViewPropertyAlpha] valueAt:time.doubleValue])];
297
+ }
298
+ CAKeyframeAnimation *animation = ANNKeyframes(@"opacity", values, times, length);
299
+ if (animation) {
300
+ [layer addAnimation:animation forKey:ANNAnimationKey(@"opacity")];
301
+ _addedAlpha = YES;
302
+ }
303
+ }
304
+ if (_animatesTransform) {
305
+ NSMutableArray<NSValue *> *values = [NSMutableArray arrayWithCapacity:times.count];
306
+ for (NSNumber *time in times) {
307
+ CATransform3D transform = [self transformAtTime:time.doubleValue];
308
+ [values addObject:[NSValue valueWithCATransform3D:transform]];
309
+ }
310
+ CAKeyframeAnimation *animation = ANNKeyframes(@"transform", values, times, length);
311
+ if (animation) {
312
+ [layer addAnimation:animation forKey:ANNAnimationKey(@"transform")];
313
+ _addedTransform = YES;
314
+ }
315
+ }
286
316
  }
287
317
 
288
318
  - (void)restore {
@@ -290,11 +320,11 @@ static ANNTrack *ANNTrackOf(Double *from, Double *to, Double *duration, Double *
290
320
  return;
291
321
  }
292
322
  _restored = YES;
293
- if (_wroteAlpha) {
294
- _view.alpha = _alphaBefore;
323
+ if (_addedAlpha) {
324
+ [_view.layer removeAnimationForKey:ANNAnimationKey(@"opacity")];
295
325
  }
296
- if (_wroteTransform) {
297
- _view.layer.transform = _transformBefore;
326
+ if (_addedTransform) {
327
+ [_view.layer removeAnimationForKey:ANNAnimationKey(@"transform")];
298
328
  }
299
329
  }
300
330
 
package/ios/CLAUDE.md CHANGED
@@ -44,7 +44,7 @@ Top-level files: `Package.swift` (SwiftPM; `headerFolders` lists every folder wh
44
44
  - Screen: React root, slot, native screen — `Layouts/Screen/`: `ANNComponentViewController`; `React/`: `ANNReactView`, `ANNReactViewCreator`, `Slots/`; `Layouts/External/`
45
45
  - Screen: insets, background, keyboard focus — `Layouts/Screen/`: `ANNScreenEnvironment`, `ANNScreenSafeArea`, `ANNScreenBars`, `ANNScreenPlace` (the safe area of a parked screen), `ANNComponentPresenter` (background, status bar blur), `ANNComponentViewController` (key commands)
46
46
  - Safe area view — `React/`: `ANNSafeAreaViewComponentView`; `Layouts/Screen/`: `ANNScreenReadiness`; `cpp/`
47
- - Stack: queue, commands, back, gestures — `Layouts/Stack/`: `ANNStackQueue`, `ANNStackCommands`, `ANNStackController`, `InteractivePopGestureDelegate`, `ANNStackAnimatingDelegate`
47
+ - Stack: queue, commands, back, gestures — `Layouts/Stack/`: `ANNStackQueue`, `ANNStackCommands`, `ANNStackController`, `InteractivePopGestureDelegate`, `ANNStackAnimatingDelegate`, `ANNSwipePopInteraction`
48
48
  - Top bar: title, buttons, menus, large title — `TopBar/`: `ANNTopBarPresenter`, `ANNBarButtons`, `ANNBarAppearance`, `ANNLargeTitlePresenter`, `ANNBackButtonPresenter`, `ANNTitleView`, `ANNTitleGlassView`
49
49
  - Top bar: components, waits — `TopBar/`: `ANNBarComponentsWait`, `ANNBarBackgroundComponent`, `ANNTitleComponentView`, `ANNBarButtonComponentView`; `React/`: `ANNReactComponentRegistry`
50
50
  - Search — `TopBar/`: `ANNSearchPresenter`, `ANNSearchResultsViewController`
@@ -133,16 +133,26 @@ Top-level files: `Package.swift` (SwiftPM; `headerFolders` lists every folder wh
133
133
  - Use an `@available` check for newer OS APIs, and `#if __IPHONE_OS_VERSION_MAX_ALLOWED >= …` for names of a newer SDK.
134
134
  - React Native hears of the app's light or dark style only from its hosting views in the window. A view that stands in
135
135
  for one (`ANNSlotScreenView`, the screens of one tree) posts the trait change itself, with the window filter of
136
- `ANNSurfaceHostingView`; without it `useColorScheme` keeps the old theme.
136
+ `ANNSurfaceHostingView`; without it `useColorScheme` keeps the old theme. `Appearance.setColorScheme` sets the style
137
+ only on the windows there are at that moment: every window the core makes but the main window comes from
138
+ `+[ANNUtils makeWindowOfClass:]`, which gives it the main window's style. A window of the system's style would draw
139
+ its screens in the other theme, and its hosting views would switch the app's theme in JS.
137
140
  - A step that runs later in a command (a ready handler, a timer, a transition's end) goes through the owner's guard
138
141
  (`protect:step:`), so an exception rejects the command instead of crashing the app. So does the start of a wait
139
142
  (the root, a modal, an overlay: their record is listed and parked by then), and the modal's end steps inside UIKit's
140
143
  completions and delegate calls (`protectEndOf:showing:step:`, `protectBatch:step:`); the overlay's guard is
141
144
  `protectWaitOf:step:`.
142
- - Each step of a transition's element (a pair's start and take-off, an element's start, every frame and restore
145
+ - Each step of a transition's element (a pair's start and take-off, an element's start, its animations and restore
143
146
  through `ANNIsolatedElement`) runs in `ANNElementStep`: an exception skips that element only. A new step of an
144
- element goes there too, or one bad element stops the transition (a frame's exception ends the app). The elements of
145
- a transition are found in one walk per screen (`ANNTransitionViews`).
147
+ element goes there too, or one bad element stops the transition. The elements of a transition are found in one walk
148
+ per screen (`ANNTransitionViews`).
149
+ - A custom transition runs in Core Animation (`ANNFrameDriver`): each client samples its values once, at the start,
150
+ into keyframe animations under the core's keys (`ANNAnimationKey`), and `restore` removes them. It never writes a
151
+ view's own alpha or transform for a frame; a value the core writes for the transition (the arriving view's hide, a
152
+ hidden flag, a depth) goes back as before. A pop that follows the swipe back also stops the time of the transition's
153
+ container (`startPausedOn:done:`), and the container gets its time back before the clients restore. UIKit runs its
154
+ swipe back without asking the navigation's delegate for a transition (iOS 26 checked), so that pop follows the
155
+ stack's own swipe from the leading edge, and UIKit's swipes stand aside meanwhile (`swipeBackTakes:`).
146
156
  - An `ANNCommandAnswer` that goes away unanswered rejects (`notAnswered:`), but only once nothing holds it: a record,
147
157
  list or queue that keeps an answer still answers it on every exit, or the promise waits as long as the record lives.
148
158
  - On iOS 26 UIKit draws a screen's large title, for a whole pop or swipe back, inside the scroll view the bar follows
@@ -152,9 +162,10 @@ Top-level files: `Package.swift` (SwiftPM; `headerFolders` lists every folder wh
152
162
  - Under a hidden bar UIKit's swipe delegate refuses the swipes in questions of its own: `InteractivePopGestureDelegate`
153
163
  stops passing those on while the bar is hidden (`barHidden`, set again on the recognizer at each change, from
154
164
  `swipeBackFollowsBar`), and the core sorts the pans itself.
155
- - UIKit works out the safe area of the window's root view from where it is drawn: a `transform` that moves it (a
156
- root's own `enter`) leaves it with no top inset, and putting the transform back doesn't work it out again. Lay the
157
- root out after a restore (`+[ANNRootCoordinator stopAnimationOf:]`).
165
+ - UIKit works out the safe area of the window's root view from where it is: a `transform` written on it that moves it
166
+ leaves it with no top inset, and putting the transform back doesn't work it out again. A root's own `enter` moves
167
+ only what Core Animation draws; the root is still laid out after a restore (`+[ANNRootCoordinator stopAnimationOf:]`).
168
+ While it animates, the new root takes no touches: it is drawn elsewhere than where it is.
158
169
  - UIKit's `hidesBarsWhenKeyboardAppears`, once on, keeps hiding the bar after it is set back to `NO`: the stack hides
159
170
  the bars for `topBar.hideOnKeyboard` itself (`keyboardWillShow:` / `keyboardDidHide:` of `ANNStackController`) and
160
171
  leaves the flag off.
package/ios/CORE.md CHANGED
@@ -139,7 +139,7 @@ Each of these owns one rule. Call it; do not copy it.
139
139
  - `ANNReloadCoordinator`: The generation of the JavaScript; the reset after a reload. Called by: Runner, emitter, answer.
140
140
  - Stack answer id: `ANNStackAnswerIdOf(entry, command)` in `ANNStackQueue.h`: the id a stack command answers with. `push` answers the node's own id (a pair's id); every other command, the entry's options screen (a pair's primary). Called by: The queue, `ANNStackCommands`, `ANNPreviewPush`, `ANNSplitViewCommands`.
141
141
  - Waited screens: `waitedComponentViewControllers`: the screens a wait is about (a pair: the ones it puts in the window). `ANNStackScreensOfEntry` is every screen of an entry, for what means all of them. Called by: `ANNRenderWait` owners: root, stack queue, modals, overlays, `ANNSplitViewCommands`.
142
- - Back: The stack: `ANNStackController` (`userGoBackTo:`, `backAction`, the hold) and `InteractivePopGestureDelegate`; UIKit's own pops are followed by `ANNStackQueue uikitShows:`. Called by: UIKit, the user.
142
+ - Back: The stack: `ANNStackController` (`userGoBackTo:`, `backAction`, the hold) and `InteractivePopGestureDelegate`; UIKit's own pops are followed by `ANNStackQueue uikitShows:`. A top whose own pop is `interactive` gets the stack's own swipe from the leading edge instead of UIKit's (UIKit runs its swipe without asking the delegate for a transition): `swipeBackTakes:` picks the swipe, `ownSwipeBack:` pops, and `swipeAnimatorFrom:to:` and `ANNSwipePopInteraction` play the pop under the finger. Called by: UIKit, the user.
143
143
 
144
144
  ## Modules
145
145
 
@@ -71,8 +71,6 @@ static BOOL ANNAppUsesScenes(void) {
71
71
  rootViewFactory.reactHost = host;
72
72
  [ApexNavigation bootstrapWithHost:host];
73
73
  self.window = ApexNavigation.mainWindow;
74
- // The links that come before React content wait for it; the launch link waits for the scene.
75
- [ANNDeepLinkQueue start];
76
74
  if (!ANNAppUsesScenes()) {
77
75
  [self.window makeKeyAndVisible];
78
76
  }
@@ -14,7 +14,8 @@ typedef void (^ANNLaunchLinkCompletion)(NSURL *_Nullable url);
14
14
  */
15
15
  @interface ANNDeepLinkQueue : NSObject
16
16
 
17
- /// Starts the queue as the app launches (`ANNAppDelegate`). An app that never starts it has no launch link.
17
+ /// Starts the queue as the app launches (`+[ApexNavigation bootstrapWithHost:]`, so also under an app delegate of the
18
+ /// app's own). Once: a second call does nothing.
18
19
  + (void)start;
19
20
 
20
21
  /// Hands `url` to `Linking`, or keeps it until React content first appears and no `takeLaunchLink:` waits.
@@ -33,11 +34,11 @@ typedef void (^ANNLaunchLinkCompletion)(NSURL *_Nullable url);
33
34
  ofScene:(UIScene *)scene
34
35
  through:(NS_NOESCAPE dispatch_block_t)forward;
35
36
 
36
- /// Hands the scene's link to `Linking` at once, past the queue: for an app delegate that doesn't take links.
37
- + (void)openURLContext:(UIOpenURLContext *)context ofScene:(UIScene *)scene;
37
+ /// `dispatchURL:` for the scene's link, with its scene and context: for an app delegate that doesn't take links.
38
+ + (void)dispatchURLContext:(UIOpenURLContext *)context ofScene:(UIScene *)scene;
38
39
 
39
- /// `openURLContext:ofScene:` for a web link (`webpageURL`) of the scene; an activity without one does nothing.
40
- + (void)openUserActivity:(NSUserActivity *)activity ofScene:(UIScene *)scene;
40
+ /// `dispatchURLContext:ofScene:` for a web link (`webpageURL`) of the scene; an activity without one does nothing.
41
+ + (void)dispatchUserActivity:(NSUserActivity *)activity ofScene:(UIScene *)scene;
41
42
 
42
43
  /// The app's window scene connected (`+[ApexNavigation attachToWindowScene:]`).
43
44
  + (void)windowSceneDidConnect;
@@ -71,19 +71,19 @@ static ANNLaunchLinkCompletion ANNWaitingLaunchLinkCompletion;
71
71
  selector:@selector(applicationDidBecomeActive:)
72
72
  name:UIApplicationDidBecomeActiveNotification
73
73
  object:nil];
74
+ // Started once the app is active (an app that starts React Native later, on demand), the queue missed the scene's
75
+ // connection and the notification: the links that launched the app went elsewhere, and none waits for them.
76
+ if (UIApplication.sharedApplication.applicationState == UIApplicationStateActive) {
77
+ [self launchLinksKnown];
78
+ }
74
79
  }
75
80
 
76
81
  + (void)dispatchURL:(NSURL *)url {
77
- ANNAssertMainQueue();
78
82
  // The URL of the scene's link keeps its context; one the app delegate changed is a URL of its own.
79
83
  ANNDeepLink *link = [ANNForwardedSceneLink.url isEqual:url]
80
84
  ? ANNForwardedSceneLink
81
85
  : [[ANNDeepLink alloc] initWithURL:url context:nil activity:nil scene:nil];
82
- if (ANNReactContentAppeared && ANNWaitingLaunchLinkCompletion == nil) {
83
- [self openLink:link];
84
- return;
85
- }
86
- [ANNPendingDeepLinks addObject:link];
86
+ [self dispatchLink:link];
87
87
  }
88
88
 
89
89
  + (void)forwardURLContext:(UIOpenURLContext *)context
@@ -104,15 +104,18 @@ static ANNLaunchLinkCompletion ANNWaitingLaunchLinkCompletion;
104
104
  through:forward];
105
105
  }
106
106
 
107
- + (void)openURLContext:(UIOpenURLContext *)context ofScene:(UIScene *)scene {
108
- [self openLink:[[ANNDeepLink alloc] initWithURL:context.URL context:context activity:nil scene:scene]];
107
+ + (void)dispatchURLContext:(UIOpenURLContext *)context ofScene:(UIScene *)scene {
108
+ [self dispatchLink:[[ANNDeepLink alloc] initWithURL:context.URL context:context activity:nil scene:scene]];
109
109
  }
110
110
 
111
- + (void)openUserActivity:(NSUserActivity *)activity ofScene:(UIScene *)scene {
111
+ + (void)dispatchUserActivity:(NSUserActivity *)activity ofScene:(UIScene *)scene {
112
112
  if (activity.webpageURL == nil) {
113
113
  return;
114
114
  }
115
- [self openLink:[[ANNDeepLink alloc] initWithURL:activity.webpageURL context:nil activity:activity scene:scene]];
115
+ [self dispatchLink:[[ANNDeepLink alloc] initWithURL:activity.webpageURL
116
+ context:nil
117
+ activity:activity
118
+ scene:scene]];
116
119
  }
117
120
 
118
121
  + (void)windowSceneDidConnect {
@@ -191,6 +194,15 @@ static ANNLaunchLinkCompletion ANNWaitingLaunchLinkCompletion;
191
194
  completion(url);
192
195
  }
193
196
 
197
+ + (void)dispatchLink:(ANNDeepLink *)link {
198
+ ANNAssertMainQueue();
199
+ if (ANNReactContentAppeared && ANNWaitingLaunchLinkCompletion == nil) {
200
+ [self openLink:link];
201
+ return;
202
+ }
203
+ [ANNPendingDeepLinks addObject:link];
204
+ }
205
+
194
206
  + (void)forwardLink:(ANNDeepLink *)link through:(NS_NOESCAPE dispatch_block_t)forward {
195
207
  ANNAssertMainQueue();
196
208
  ANNDeepLink *outer = ANNForwardedSceneLink;
@@ -77,7 +77,7 @@
77
77
  [appDelegate application:application openURL:context.URL options:options];
78
78
  }];
79
79
  } else {
80
- [ANNDeepLinkQueue openURLContext:context ofScene:scene];
80
+ [ANNDeepLinkQueue dispatchURLContext:context ofScene:scene];
81
81
  }
82
82
  }
83
83
 
@@ -95,7 +95,7 @@
95
95
  }];
96
96
  }];
97
97
  } else if ([activity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
98
- [ANNDeepLinkQueue openUserActivity:activity ofScene:scene];
98
+ [ANNDeepLinkQueue dispatchUserActivity:activity ofScene:scene];
99
99
  }
100
100
  }
101
101
 
@@ -23,6 +23,13 @@ NS_ASSUME_NONNULL_BEGIN
23
23
  /// The key window of `scene`; nil when none of its windows is key.
24
24
  + (nullable UIWindow *)keyWindowOfScene:(nullable UIWindowScene *)scene;
25
25
 
26
+ /// A new window of `windowClass` (`UIWindow` or a subclass) for the core: in the navigation window's scene, or at the
27
+ /// size of `mainScreen` before that window has one. It takes the navigation window's `overrideUserInterfaceStyle`:
28
+ /// React Native's `Appearance.setColorScheme` sets the style once, on the windows there are at that moment, and a
29
+ /// window of the system's style would show its screens in the other theme and switch the app's theme in JS when its
30
+ /// hosting views move in. Every window the core makes but the main window, the style's source, comes from here.
31
+ + (__kindof UIWindow *)makeWindowOfClass:(Class)windowClass;
32
+
26
33
  /// The status bar's frame in the navigation window's scene (its status bar manager); `CGRectZero` without a scene.
27
34
  @property(class, nonatomic, readonly) CGRect statusBarFrame;
28
35
 
@@ -26,6 +26,16 @@
26
26
  return windows;
27
27
  }
28
28
 
29
+ + (UIWindow *)makeWindowOfClass:(Class)windowClass {
30
+ UIWindowScene *scene = self.mainWindow.windowScene;
31
+ // `initWithFrame:` is deprecated from iOS 26 on and warns only with a deployment target from 26: a window without a
32
+ // scene is made so, and joins the scene as the main window gets it.
33
+ UIWindow *window = scene ? [[windowClass alloc] initWithWindowScene:scene]
34
+ : [[windowClass alloc] initWithFrame:self.mainScreen.bounds];
35
+ window.overrideUserInterfaceStyle = self.mainWindow.overrideUserInterfaceStyle;
36
+ return window;
37
+ }
38
+
29
39
  + (UIWindow *)keyWindowOfScene:(UIWindowScene *)scene {
30
40
  for (UIWindow *window in scene.windows) {
31
41
  if (window.isKeyWindow) {