@apexnative/navigation 1.0.0-alpha.1 → 1.0.0-alpha.3

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 (163) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +147 -51
  3. package/android/CLAUDE.md +31 -6
  4. package/android/CORE.md +7 -2
  5. package/android/RECIPES.md +4 -3
  6. package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +29 -1
  7. package/android/src/main/java/dev/apexnative/navigation/options/StatusBarOptions.kt +7 -3
  8. package/android/src/main/java/dev/apexnative/navigation/react/ArrangementStates.kt +5 -4
  9. package/android/src/main/java/dev/apexnative/navigation/react/ErrorTexts.kt +75 -60
  10. package/android/src/main/java/dev/apexnative/navigation/react/Hinge.kt +3 -2
  11. package/android/src/main/java/dev/apexnative/navigation/react/OptionsCommands.kt +1 -1
  12. package/android/src/main/java/dev/apexnative/navigation/react/ScreenInsets.kt +5 -4
  13. package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +7 -6
  14. package/android/src/main/java/dev/apexnative/navigation/react/events/AppearanceDispatcher.kt +3 -35
  15. package/android/src/main/java/dev/apexnative/navigation/react/events/EventContract.kt +226 -0
  16. package/android/src/main/java/dev/apexnative/navigation/react/events/EventEmitter.kt +101 -87
  17. package/android/src/main/java/dev/apexnative/navigation/react/events/ScreenAppearance.kt +0 -6
  18. package/android/src/main/java/dev/apexnative/navigation/utils/UiUtils.kt +3 -3
  19. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/SecondaryScreenPresenter.kt +13 -5
  20. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +76 -13
  21. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/TabItems.kt +3 -3
  22. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentPresenter.kt +28 -7
  23. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenFrame.kt +1 -1
  24. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalAnimations.kt +22 -24
  25. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +86 -16
  26. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalRecord.kt +21 -6
  27. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +41 -23
  28. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +24 -20
  29. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetStops.kt +25 -0
  30. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackState.kt +4 -0
  31. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +42 -4
  32. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt +29 -6
  33. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/overlay/OverlayCoordinator.kt +2 -0
  34. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ContainerBackground.kt +3 -3
  35. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ParentController.kt +3 -0
  36. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuController.kt +5 -3
  37. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuMotion.kt +4 -7
  38. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuPresenter.kt +16 -7
  39. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuRules.kt +8 -1
  40. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitViewController.kt +16 -2
  41. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/BackPreview.kt +287 -0
  42. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +148 -6
  43. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackDisplay.kt +24 -0
  44. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +51 -11
  45. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackTransitions.kt +26 -8
  46. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +105 -2
  47. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +123 -8
  48. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ViewController.kt +19 -0
  49. package/android/src/main/java/dev/apexnative/navigation/views/animations/Curves.kt +29 -11
  50. package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabs.kt +5 -5
  51. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementStep.kt +31 -0
  52. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +85 -27
  53. package/android/src/main/java/dev/apexnative/navigation/views/element/TargetsWait.kt +5 -4
  54. package/android/src/main/java/dev/apexnative/navigation/views/element/TransitionTargets.kt +9 -6
  55. package/android/src/main/java/dev/apexnative/navigation/views/element/TransitionViews.kt +100 -0
  56. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +45 -1
  57. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +3 -5
  58. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +3 -5
  59. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +3 -5
  60. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +2 -0
  61. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/TextChangeAnimator.kt +4 -6
  62. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +4 -6
  63. package/android/src/main/java/dev/apexnative/navigation/views/stack/ScreenEdge.kt +15 -0
  64. package/android/src/main/java/dev/apexnative/navigation/views/stack/StackLayout.kt +137 -1
  65. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TitleBar.kt +8 -1
  66. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TopBar.kt +26 -4
  67. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonBar.kt +4 -3
  68. package/cli/app.js +1 -1
  69. package/cli/project.js +6 -2
  70. package/cli/setup/index.js +13 -0
  71. package/cli/setup/steps/appDelegate.js +66 -11
  72. package/cli/setup/steps/sceneDelegate.js +108 -0
  73. package/docs-md/architecture/transitions.md +7 -1
  74. package/docs-md/containers/modals.md +4 -2
  75. package/docs-md/containers/overlays.md +3 -1
  76. package/docs-md/containers/side-menu.md +4 -2
  77. package/docs-md/containers/tabs.md +2 -0
  78. package/docs-md/flows/forms-in-a-modal.md +1 -1
  79. package/docs-md/guides/deep-links.md +15 -33
  80. package/docs-md/help/glossary.md +1 -1
  81. package/docs-md/index.json +8 -0
  82. package/docs-md/screen-ui/back-and-gestures.md +6 -0
  83. package/docs-md/screen-ui/system-ui.md +1 -1
  84. package/docs-md/start/installation.md +1 -0
  85. package/ios/Animations/ANNTransitionAnimator.mm +13 -11
  86. package/ios/Animations/ANNTransitionWait.mm +6 -7
  87. package/ios/Animations/Elements/ANNElementFinder.h +57 -0
  88. package/ios/Animations/Elements/ANNElementFinder.mm +174 -2
  89. package/ios/Animations/Elements/ANNElementTransition.h +7 -5
  90. package/ios/Animations/Elements/ANNElementTransition.mm +17 -19
  91. package/ios/Animations/Elements/ANNSharedElementFlight.h +13 -8
  92. package/ios/Animations/Elements/ANNSharedElementFlight.mm +59 -24
  93. package/ios/CLAUDE.md +10 -2
  94. package/ios/CORE.md +3 -2
  95. package/ios/Contract/ANNCommandAnswer.h +4 -3
  96. package/ios/Contract/ANNCommandAnswer.mm +12 -2
  97. package/ios/Contract/ANNCommandRunner.h +2 -2
  98. package/ios/Contract/ANNCommandRunner.mm +1 -1
  99. package/ios/Contract/ANNErrorTexts.h +6 -2
  100. package/ios/Contract/ANNErrorTexts.mm +18 -10
  101. package/ios/Core/ANNDeepLinkQueue.h +20 -1
  102. package/ios/Core/ANNDeepLinkQueue.mm +107 -12
  103. package/ios/Core/ANNErrorHandler.h +8 -3
  104. package/ios/Core/ANNErrorHandler.mm +17 -1
  105. package/ios/Core/ANNEventContract.h +246 -0
  106. package/ios/Core/ANNEventContract.mm +225 -0
  107. package/ios/Core/ANNEventEmitter.h +5 -1
  108. package/ios/Core/ANNEventEmitter.mm +117 -80
  109. package/ios/Core/ANNHinge.mm +6 -4
  110. package/ios/Core/ANNSceneDelegate.mm +25 -15
  111. package/ios/Core/ANNSystemUI.h +3 -2
  112. package/ios/Core/ANNSystemUI.mm +7 -0
  113. package/ios/Layouts/Arrangement/ANNArrangementViewController.mm +6 -4
  114. package/ios/Layouts/Modal/ANNModalCoordinator.mm +1 -1
  115. package/ios/Layouts/Modal/ANNModalPresentation.h +8 -5
  116. package/ios/Layouts/Modal/ANNModalPresentation.mm +12 -12
  117. package/ios/Layouts/Modal/ANNSheetPresenter.mm +83 -1
  118. package/ios/Layouts/Root/ANNRootCoordinator.mm +12 -3
  119. package/ios/Layouts/Screen/ANNComponentPresenter.h +11 -3
  120. package/ios/Layouts/Screen/ANNComponentPresenter.mm +34 -6
  121. package/ios/Layouts/Screen/ANNComponentViewController.mm +28 -7
  122. package/ios/Layouts/Screen/ANNNativeScreenContext.mm +7 -5
  123. package/ios/Layouts/Screen/ANNScreenInsets.mm +6 -4
  124. package/ios/Layouts/Screen/ANNScreenRegions.mm +9 -6
  125. package/ios/Layouts/SplitView/ANNSplitViewController.mm +4 -7
  126. package/ios/Options/ANNSheetOptions.h +4 -1
  127. package/ios/Public/ApexNavigation/ANNSceneDelegate.h +3 -0
  128. package/ios/RECIPES.md +2 -1
  129. package/ios/TurboModules/ANNTurboEventEmitter.mm +4 -35
  130. package/ios/TurboModules/ANNTurboModule.mm +5 -2
  131. package/lib/module/ARCHITECTURE.md +7 -4
  132. package/lib/module/api/app/createNavigation.js +2 -0
  133. package/lib/module/api/app/createNavigation.js.map +1 -1
  134. package/lib/module/api/hooks/useNavigation.js +8 -3
  135. package/lib/module/api/hooks/useNavigation.js.map +1 -1
  136. package/lib/module/api/linking/dispatchedLinks.js +23 -0
  137. package/lib/module/api/linking/dispatchedLinks.js.map +1 -0
  138. package/lib/module/interfaces/contract/errors.json +275 -0
  139. package/lib/typescript/api/app/createNavigation.d.ts.map +1 -1
  140. package/lib/typescript/api/hooks/useNavigation.d.ts.map +1 -1
  141. package/lib/typescript/api/linking/dispatchedLinks.d.ts +7 -0
  142. package/lib/typescript/api/linking/dispatchedLinks.d.ts.map +1 -0
  143. package/lib/typescript/interfaces/Events.d.ts +9 -0
  144. package/lib/typescript/interfaces/Events.d.ts.map +1 -1
  145. package/lib/typescript/interfaces/options/animations.d.ts +27 -18
  146. package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
  147. package/lib/typescript/interfaces/options/modals.d.ts +33 -24
  148. package/lib/typescript/interfaces/options/modals.d.ts.map +1 -1
  149. package/lib/typescript/interfaces/options/sideMenu.d.ts +7 -6
  150. package/lib/typescript/interfaces/options/sideMenu.d.ts.map +1 -1
  151. package/lib/typescript/interfaces/options/system.d.ts +10 -6
  152. package/lib/typescript/interfaces/options/system.d.ts.map +1 -1
  153. package/package.json +1 -1
  154. package/src/ARCHITECTURE.md +7 -4
  155. package/src/api/app/createNavigation.ts +2 -0
  156. package/src/api/hooks/useNavigation.ts +8 -3
  157. package/src/api/linking/dispatchedLinks.ts +18 -0
  158. package/src/interfaces/Events.ts +10 -0
  159. package/src/interfaces/contract/errors.json +275 -0
  160. package/src/interfaces/options/animations.ts +27 -18
  161. package/src/interfaces/options/modals.ts +33 -24
  162. package/src/interfaces/options/sideMenu.ts +7 -6
  163. package/src/interfaces/options/system.ts +10 -6
@@ -0,0 +1,108 @@
1
+ // @ts-check
2
+ 'use strict';
3
+
4
+ /** @typedef {import('./types').StepResult} StepResult */
5
+
6
+ const HOW_TO = `Let ANNSceneDelegate take the app's scene: it moves the navigation's window into the scene and passes the
7
+ scene's links to the app delegate. React Native starts in ANNAppDelegate, not here.
8
+
9
+ import ApexNavigation
10
+ import UIKit
11
+
12
+ class SceneDelegate: ANNSceneDelegate {}
13
+
14
+ A scene delegate of the app's own calls ApexNavigation.attach(to:) in scene(_:willConnectTo:options:) instead, as the
15
+ deep links guide shows.`;
16
+
17
+ const SET_UP = `import ApexNavigation
18
+ import UIKit
19
+
20
+ /// ANNSceneDelegate moves the navigation's window into the app's scene and passes the scene's links to the app delegate.
21
+ class SceneDelegate: ANNSceneDelegate {}
22
+ `;
23
+
24
+ // React Native 0.88's template, without its spaces: the scene delegate that makes React Native's factory and starts
25
+ // it in the scene's window.
26
+ const TEMPLATE = `import React
27
+ import React_RCTAppDelegate
28
+ import ReactAppDependencyProvider
29
+ import UIKit
30
+
31
+ class SceneDelegate: RCTDefaultReactNativeFactoryDelegate, UIWindowSceneDelegate {
32
+ var window: UIWindow?
33
+ var reactNativeFactory: RCTReactNativeFactory?
34
+
35
+ func scene(
36
+ _ scene: UIScene,
37
+ willConnectTo session: UISceneSession,
38
+ options connectionOptions: UIScene.ConnectionOptions
39
+ ) {
40
+ guard let windowScene = scene as? UIWindowScene else {
41
+ return
42
+ }
43
+
44
+ dependencyProvider = RCTAppDependencyProvider()
45
+ reactNativeFactory = RCTReactNativeFactory(delegate: self)
46
+ window = UIWindow(windowScene: windowScene)
47
+
48
+ #if DEBUG
49
+ let devMenuConfiguration = RCTDevMenuConfiguration(
50
+ devMenuEnabled: true,
51
+ shakeGestureEnabled: true,
52
+ keyboardShortcutsEnabled: true
53
+ )
54
+ reactNativeFactory?.devMenuConfiguration = devMenuConfiguration
55
+ #endif
56
+
57
+ reactNativeFactory?.startReactNative(
58
+ withModuleName: "",
59
+ in: window,
60
+ connectionOptions: connectionOptions
61
+ )
62
+ }
63
+
64
+ func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
65
+ RCTLinkingManager.scene(scene, openURLContexts: URLContexts)
66
+ }
67
+
68
+ func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
69
+ RCTLinkingManager.scene(scene, continue: userActivity)
70
+ }
71
+
72
+ override func bundleURL() -> URL? {
73
+ #if DEBUG
74
+ RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
75
+ #else
76
+ Bundle.main.url(forResource: "main", withExtension: "jsbundle")
77
+ #endif
78
+ }
79
+ }
80
+ `;
81
+
82
+ /**
83
+ * The text without its spaces and the app's name, to compare with the template.
84
+ * @param {string} text
85
+ */
86
+ function shape(text) {
87
+ return text.replace(/withModuleName:\s*"[^"]*"/, 'withModuleName:""').replace(/\s+/g, '');
88
+ }
89
+
90
+ /**
91
+ * SceneDelegate.swift of React Native 0.88 and newer: the scene delegate becomes an ANNSceneDelegate, since
92
+ * ANNAppDelegate starts React Native and owns the window. Only the template's file changes; one with code of the app's
93
+ * own is changed by hand.
94
+ * @param {string} text
95
+ * @returns {StepResult}
96
+ */
97
+ function planSceneDelegate(text) {
98
+ if (
99
+ /class\s+\w+\s*:\s*ANNSceneDelegate\b/.test(text) ||
100
+ /\bApexNavigation\.attach\s*\(/.test(text)
101
+ )
102
+ return { status: 'done', note: 'already set up' };
103
+ if (shape(text) !== shape(TEMPLATE))
104
+ return { status: 'manual', note: 'changed by hand, see below', howTo: HOW_TO };
105
+ return { status: 'change', next: SET_UP, note: 'ANNSceneDelegate' };
106
+ }
107
+
108
+ module.exports = { planSceneDelegate };
@@ -46,6 +46,8 @@ navigation.showModal('Player', {
46
46
  });
47
47
  ```
48
48
 
49
+ The top bar of a pushed screen goes with the screen on Android and stays in place on iOS: see [the bar in a transition of your own](#custom-bar).
50
+
49
51
  `presentation` of `createNavigation` gives every push or modal of the app its transition: see [Defaults for the whole app](../fundamentals/moving-between-screens#presentation). New roots have presets of their own: see [How a new root comes in](../fundamentals/switching-roots#transitions).
50
52
 
51
53
  ## Transitions out of a view
@@ -78,7 +80,7 @@ navigation.push('Person', {
78
80
  });
79
81
  ```
80
82
 
81
- The avatar of the row moves and grows into the photo of the profile, and the name into its larger title. The views can differ in size and style. `type` and `duration` next to `shared` change how the rest of the screen comes, `'fade'` by default.
83
+ The avatar of the row moves and grows into the photo of the profile, and the name into its larger title. The views can differ in size and style. A pair that isn't found is skipped (the transition waits for it first); one that fails on the way is skipped with a warning in the log. Either way its views show with their screens, and the other pairs move. `type` and `duration` next to `shared` change how the rest of the screen comes, `'fade'` by default.
82
84
 
83
85
  Both platforms. (Android) The next screen waits until it is drawn, so its views are there to move into; a `type` other than a fade gives way to a fade.
84
86
 
@@ -146,6 +148,10 @@ Details rises 300 points as it fades in. Each property animates from a value to
146
148
 
147
149
  The same goes for `pop`, `showModal`, `dismissModal`, `setStackRoot` and `setRoot`. The swipe back is the exception: (iOS) it always follows the finger with the system's slide, so a custom `pop` plays for the back button and `pop()` only.
148
150
 
151
+ ### The top bar
152
+
153
+ The top bar goes its platform's way in a preset or an animation of your own. (Android) It goes with its screen, as if it were part of it: with `slideUp` the new screen comes up with its bar over the old one and its bar, and on the way back it goes down with its bar, uncovering the bar below. With `fade` or `scale` the bar fades or grows with its screen. (iOS) UIKit keeps the bar in place and changes its title and buttons as the transition starts; only the screens under it move.
154
+
149
155
  ## Which to pick
150
156
 
151
157
  | The move | Pick | What it costs |
@@ -70,6 +70,8 @@ navigation.showModal('Compose', { style: 'fullScreen' });
70
70
 
71
71
  Without `style`, a modal is a sheet on (iOS) and covers the whole screen on (Android).
72
72
 
73
+ `'pageSheet'` and `'formSheet'` are sheets on both platforms: on (Android) the Material bottom sheet over the dimmed screen, at the full height under the status bar and at most 640 dp wide. `'fullScreen'` and `'overFullScreen'` come up from the bottom over the whole screen; on Android with the system's motion of a window opening over another, without a fade.
74
+
73
75
  `'overFullScreen'` keeps the screen under the modal in place, so it shows through wherever the modal draws nothing: a dialog of your own, a card over a dimmed app. Give the modal's screen `backgroundColor('transparent')` in its options, so its own background doesn't hide the screen under it.
74
76
 
75
77
  ### A form on iPad
@@ -83,7 +85,7 @@ navigation.showModal('EditProfile', {
83
85
  });
84
86
  ```
85
87
 
86
- On iPhone a form sheet is a sheet, and `contentSize` does nothing.
88
+ On iPhone and on Android a form sheet is a sheet, and `contentSize` does nothing.
87
89
 
88
90
  ### A popover
89
91
 
@@ -95,7 +97,7 @@ navigation.showModal('Filters', {
95
97
  });
96
98
  ```
97
99
 
98
- On iPhone it becomes a sheet, unless `adaptive: false` keeps it a popover. (Android) has no popovers: the modal covers the whole screen, as without `style`.
100
+ On iPhone it becomes a sheet, unless `adaptive: false` keeps it a popover. (Android) has no popovers: the modal is the bottom sheet of `'pageSheet'`.
99
101
 
100
102
  ## Sheets
101
103
 
@@ -24,7 +24,9 @@ This page answers:
24
24
 
25
25
  A push or a modal moves the user somewhere. An overlay doesn't move anyone: it puts a screen **on top of everything**, and the app goes on under it.
26
26
 
27
- An overlay is a screen of `screens` like any other: a component, its props, its options. It shows above the tabs, the bars and the modals; on (iOS) it is in a window of its own. Its background is transparent, so the app shows wherever the overlay draws nothing. What overlays are for:
27
+ An overlay is a screen of `screens` like any other: a component, its props, its options. It shows above the tabs, the bars and the modals; on (iOS) it is in a window of its own. Its background is transparent, so the app shows wherever the overlay draws nothing.
28
+
29
+ An overlay covers the whole window, **the status bar included**: a dialog's dimmed backdrop dims the status bar too, as a system dialog does. Its safe area keeps the content off the bars, so a toast at the top of the overlay sits below the status bar. On (Android) a status bar with a color of its own (`statusBar.backgroundColor`) stays over the backdrop, and `statusBar.drawBehind: false` starts the overlay below the status bar. What overlays are for:
28
30
 
29
31
  ## Showing and closing
30
32
 
@@ -74,7 +74,7 @@ A tap on the button opens the menu. A button of the options gets the navigation
74
74
 
75
75
  ## A pick in the menu
76
76
 
77
- A menu is a screen without a stack, so **its commands act on the center**, and the menu closes once the center has changed. A pick usually switches the section, so the center starts over with it:
77
+ A menu is a screen without a stack, so **its commands act on the center**, and the menu closes as the center changes: it starts to slide away at the tap, and a push slides in as it goes. The command resolves once both are done. A pick usually switches the section, so the center starts over with it:
78
78
 
79
79
  ```tsx
80
80
  import { Button, View } from 'react-native';
@@ -94,7 +94,7 @@ function Menu() {
94
94
  }
95
95
  ```
96
96
 
97
- A tap on Settings shows Settings in the center, and the menu closes. Which command to use depends on the way back:
97
+ A tap on Settings closes the menu and shows Settings in the center as it goes. Which command to use depends on the way back:
98
98
 
99
99
  | From the menu | The center |
100
100
  | --- | --- |
@@ -108,6 +108,8 @@ A tap on Settings shows Settings in the center, and the menu closes. Which comma
108
108
  | (iOS) | Over the content, which dims: `openMode: 'aboveContent'` of the side, the default. Or under it, with the content moved aside: `'pushContent'`. Then `animation(…)` says how the menu appears: `'parallax'`, `'slide'`, `'slide-and-scale'` or `'door'`. |
109
109
  | (Android) | The drawer of the platform: over the content, which dims. |
110
110
 
111
+ The menu's color is its screen's `layout.backgroundColor`: it fills the whole height of the menu, behind the status bar and the navigation bar, while the menu's content keeps clear of them. A background on the React content stops at the safe area.
112
+
111
113
  `.ios()` keeps the settings that only iOS has apart from the others:
112
114
 
113
115
  ```ts
@@ -90,6 +90,8 @@ This is the app's `navigation`: its `push` goes to the stack the user sees, now
90
90
 
91
91
  Inbox shows, and Chat comes in over it, in the stack of the Inbox tab.
92
92
 
93
+ On (Android) the content changes with Material's fade through, as in the system's Phone app: the tab that goes fades out, then the new one fades in and grows to its size, in 300 ms; the tab bar stays. On (iOS) the new tab shows at once, as the system's tab bar does.
94
+
93
95
  ### A press on a tab
94
96
 
95
97
  A second tap on the selected tab is a convention of both platforms: **back to the start of the tab**. The navigation does it without code:
@@ -299,7 +299,7 @@ navigation.showModal('EditProfile', {
299
299
  });
300
300
  ```
301
301
 
302
- On iPhone a form sheet is the same sheet as before, and on Android the modal is unchanged.
302
+ On iPhone a form sheet is the same sheet as before. On Android it is the Material bottom sheet, where the modal without a style covered the whole screen.
303
303
 
304
304
  ## The whole code
305
305
 
@@ -522,57 +522,39 @@ The launch link is read once per launch of the app, and the cases differ:
522
522
 
523
523
  ## An app with its own scene delegate
524
524
 
525
- (iOS) `ANNAppDelegate` gives the app's scene `ANNSceneDelegate`, which moves the navigation's window into the scene and passes the scene's links on. An app whose `Info.plist` names a scene delegate of its own does both itself. The links that launched the app come with the options of `scene(_:willConnectTo:options:)`, and they count as the launch link when they're passed on in that same call, with the window moved into the scene:
525
+ (iOS) `ANNAppDelegate` gives the app's scene `ANNSceneDelegate`, which moves the navigation's window into the scene and passes the scene's links on: to the app delegate's methods, then to `Linking`. An app whose `Info.plist` names a scene delegate of its own, as React Native 0.88's template does, extends `ANNSceneDelegate`; a method it overrides calls `super`:
526
526
 
527
527
  ```swift
528
528
  import ApexNavigation
529
529
  import UIKit
530
530
 
531
- class SceneDelegate: UIResponder, UIWindowSceneDelegate {
532
- var window: UIWindow?
533
-
534
- func scene(
531
+ class SceneDelegate: ANNSceneDelegate {
532
+ override func scene(
535
533
  _ scene: UIScene,
536
534
  willConnectTo session: UISceneSession,
537
535
  options connectionOptions: UIScene.ConnectionOptions
538
536
  ) {
539
- guard let windowScene = scene as? UIWindowScene else { return }
540
- window = ApexNavigation.attach(to: windowScene)
541
- // The links that launched the app: passed on in this same
542
- // call, so the first root can come from one of them.
543
- for context in connectionOptions.urlContexts {
544
- openLink(context.url)
545
- }
546
- for activity in connectionOptions.userActivities {
547
- if let url = activity.webpageURL { openLink(url) }
548
- }
537
+ // Moves the window into the scene and passes on the links
538
+ // that launched the app.
539
+ super.scene(scene, willConnectTo: session, options: connectionOptions)
540
+ window?.tintColor = .systemIndigo
549
541
  }
550
542
 
551
- // Links while the app runs.
552
- func scene(
543
+ override func scene(
553
544
  _ scene: UIScene,
554
545
  openURLContexts URLContexts: Set<UIOpenURLContext>
555
546
  ) {
556
- for context in URLContexts {
557
- openLink(context.url)
558
- }
559
- }
560
-
561
- func scene(
562
- _ scene: UIScene,
563
- continue userActivity: NSUserActivity
564
- ) {
565
- if let url = userActivity.webpageURL { openLink(url) }
566
- }
567
-
568
- private func openLink(_ url: URL) {
569
- let app = UIApplication.shared.delegate as? ANNAppDelegate
570
- app?.dispatchDeepLinkURL(url)
547
+ print("Opened:", URLContexts.map(\.url))
548
+ super.scene(scene, openURLContexts: URLContexts)
571
549
  }
572
550
  }
573
551
  ```
574
552
 
575
- `ApexNavigation.attach(to:)` is `attachToWindowScene:` in Objective-C.
553
+ `super` passes each link on with its scene: React Native 0.88 and newer take a scene's links only with it. `setup` turns the scene delegate of React Native 0.88's template into `class SceneDelegate: ANNSceneDelegate {}`.
554
+
555
+ A scene delegate that extends another class calls `ApexNavigation.attach(to:)` (`attachToWindowScene:` in Objective-C) in `scene(_:willConnectTo:options:)` and passes the URLs to `dispatchDeepLinkURL(_:)` of the app delegate.
556
+
557
+ A link the app dispatches itself with `dispatchDeepLinkURL(_:)`, such as the link of a push notification, comes with no scene. Where React Native's `Linking` doesn't take it from the native side (React Native 0.88 and newer, in an app with a scene delegate), the navigation hands it to `Linking` from JavaScript, as a `url` event like any other.
576
558
 
577
559
  ## Testing
578
560
 
@@ -266,7 +266,7 @@ Two screens shown as one entry of a stack: the primary, whose bar shows, and the
266
266
 
267
267
  ### Popover
268
268
 
269
- (iPadOS) A modal that points at the bar button or the view that opened it. On iPhone it becomes a sheet; on Android the modal covers the screen. More: [A popover](../containers/modals#popover).
269
+ (iPadOS) A modal that points at the bar button or the view that opened it. On iPhone and on Android it becomes a sheet. More: [A popover](../containers/modals#popover).
270
270
 
271
271
  ### Presentation
272
272
 
@@ -974,6 +974,10 @@
974
974
  "heading": "The system back of Android",
975
975
  "anchor": "system-back"
976
976
  },
977
+ {
978
+ "heading": "The preview of a pop",
979
+ "anchor": "pop-preview"
980
+ },
977
981
  {
978
982
  "heading": "Back to the home screen",
979
983
  "anchor": "system-animation"
@@ -1323,6 +1327,10 @@
1323
1327
  "heading": "Animations of your own",
1324
1328
  "anchor": "custom"
1325
1329
  },
1330
+ {
1331
+ "heading": "The top bar",
1332
+ "anchor": "custom-bar"
1333
+ },
1326
1334
  {
1327
1335
  "heading": "Which to pick",
1328
1336
  "anchor": "which"
@@ -100,6 +100,12 @@ export const MapScreen = defineScreen({
100
100
  | The first screen of a tab | What `Tabs().backBehavior(…)` says: leave the app, go to the first tab, or to the tab before. See [Back on Android](../containers/tabs#back). |
101
101
  | The first screen of the app | Leaves the app, as every Android app does, with the system's own animation ([below](#system-animation)). `usePreventLeave` doesn't hold it; to ask first, see [Asking before the user leaves the app](/api/back-and-gestures/usePreventLeave#leaving-the-app). |
102
102
 
103
+ ### The preview of a pop
104
+
105
+ (Android) 14+ The back gesture on a pushed screen shows where it goes before the user lets go, as the system does between apps' screens: the screen shrinks toward the side of the swipe with rounded corners and follows the finger, and the screen under it shows behind it, dimmed, with its own bar. Letting go pops the screen; swiping back cancels, and the screen grows back into place.
106
+
107
+ The preview shows when back pops the screen with the default transition. It doesn't when back does something else first, or when the app takes it: a menu or a search that closes, a screen that holds back (`hardwareBackButton.popStackOnPress: false`, a [`BackHandler` listener](#back-handler), `usePreventLeave`), or a pop with an animation of its own (`animations.pop`). On Android 13 and older the screen pops when the swipe ends.
108
+
103
109
  ### Back to the home screen
104
110
 
105
111
  (Android) On the first screen of the app, with nothing left to go back to, the navigation leaves the back to the system. The system then plays its predictive back animation: as the user swipes, the app shrinks and the home screen shows behind it, and the user can still change their mind halfway. The system decides this as the swipe starts, so the navigation takes the back only while it has something to do with it: a screen to pop, a modal, a menu or a search to close, a tab to go to (`backBehavior`), or a [`BackHandler` listener](#back-handler) of the app.
@@ -249,7 +249,7 @@ iPhone Duo decides by itself too: folded, the outer display shows the app in por
249
249
 
250
250
  ## Backgrounds
251
251
 
252
- `backgroundColor` colors the screen, behind its React content. Without it, the screen has the background of the [theme](../architecture/theme-and-context).
252
+ `backgroundColor` colors the screen, behind its React content. Without it, the screen has the background of the [theme](../architecture/theme-and-context), or the system's when the theme has none: `systemBackground` on (iOS), `android:colorBackground` of the app's theme on (Android) (a screen in a sheet shows the sheet's surface; a native screen paints its own view). So a React screen is never see-through by accident; `'transparent'` makes it so on purpose, as overlays are.
253
253
 
254
254
  ```ts
255
255
  s.backgroundColor('#F3E9D2');
@@ -136,6 +136,7 @@ In an app as the React Native template makes it, the window shows **one React vi
136
136
  | File | What setup does | Why |
137
137
  | --- | --- | --- |
138
138
  | `AppDelegate.swift` | Extends `ANNAppDelegate`. | It creates the window, puts the root of the navigation in it and starts React Native. Deep links that come before JS is ready wait for it. |
139
+ | `SceneDelegate.swift` | Extends `ANNSceneDelegate`. React Native 0.88 and newer only: the earlier templates have no scene delegate. | The template's scene delegate would start React Native a second time. `ANNSceneDelegate` moves the navigation's window into the app's scene and passes the scene's links to the app delegate. |
139
140
  | `Info.plist` | Sets `UIViewControllerBasedStatusBarAppearance` to `true`. | Each screen sets its own status bar from its options; with `false`, only the app as a whole can. |
140
141
 
141
142
  #### JavaScript files
@@ -133,19 +133,16 @@ static void ANNUpdateRootLayoutOf(UIViewController *node) {
133
133
  _began = YES;
134
134
  _wait = nil;
135
135
  UIView *container = _context.containerView;
136
- UIView *leavingRoot = [ANNElementFinder searchRootOf:_plan.fromNode];
137
- UIView *arrivingRoot = [ANNElementFinder searchRootOf:_plan.toNode];
136
+ ANNTransitionViews *views = [[ANNTransitionViews alloc] initWithPairs:_plan.sharedElements
137
+ elements:_plan.elements
138
+ fromNode:_plan.fromNode
139
+ toNode:_plan.toNode];
138
140
  NSMutableArray<id<ANNFrameClient>> *clients = [NSMutableArray new];
139
141
  // The shared pairs first: their geometry and pictures are taken while the screens are still at rest.
140
142
  NSArray<ANNSharedElementFlight *> *flights =
141
- [ANNSharedElementFlight flightsOf:_plan.sharedElements
142
- leavingRoot:leavingRoot
143
- arrivingRoot:arrivingRoot
144
- container:container];
143
+ [ANNSharedElementFlight flightsOf:_plan.sharedElements views:views container:container];
145
144
  NSArray<ANNElementTransition *> *elements =
146
- [ANNElementTransition transitionsOf:_plan.elements
147
- leavingRoot:leavingRoot
148
- arrivingRoot:arrivingRoot];
145
+ [ANNElementTransition transitionsOf:_plan.elements views:views];
149
146
  ANNViewTracks *arrivingTracks = nil;
150
147
  ANNViewTracks *leavingTracks = nil;
151
148
  if (_plan.enter && _arriving) {
@@ -200,8 +197,13 @@ static void ANNUpdateRootLayoutOf(UIViewController *node) {
200
197
  if (leavingTracks.hasTracks) {
201
198
  [clients addObject:leavingTracks];
202
199
  }
203
- [clients addObjectsFromArray:elements];
204
- [clients addObjectsFromArray:flights];
200
+ // Each element plays on its own: one whose frame fails goes back and stops, the others and the screens go on.
201
+ for (ANNElementTransition *element in elements) {
202
+ [clients addObject:[[ANNIsolatedElement alloc] initWithClient:element name:element.name]];
203
+ }
204
+ for (ANNSharedElementFlight *flight in flights) {
205
+ [clients addObject:[[ANNIsolatedElement alloc] initWithClient:flight name:flight.name]];
206
+ }
205
207
  _clients = clients;
206
208
  if ([self tilts]) {
207
209
  [self orderInDepthIn:container flights:flights];
@@ -48,13 +48,12 @@ static const NSTimeInterval ANNReadyCheckDelay = 0.1;
48
48
  }
49
49
 
50
50
  - (BOOL)targetsAreFound {
51
- UIView *leavingRoot = [ANNElementFinder searchRootOf:_plan.fromNode];
52
- UIView *arrivingRoot = [ANNElementFinder searchRootOf:_plan.toNode];
53
- return [ANNSharedElementFlight allTargetsFound:_plan.sharedElements
54
- arrivingRoot:arrivingRoot] &&
55
- [ANNElementTransition allFound:_plan.elements
56
- leavingRoot:leavingRoot
57
- arrivingRoot:arrivingRoot];
51
+ ANNTransitionViews *views = [[ANNTransitionViews alloc] initWithPairs:_plan.sharedElements
52
+ elements:_plan.elements
53
+ fromNode:_plan.fromNode
54
+ toNode:_plan.toNode];
55
+ return [ANNSharedElementFlight allTargetsFound:_plan.sharedElements views:views] &&
56
+ [ANNElementTransition allFound:_plan.elements views:views];
58
57
  }
59
58
 
60
59
  - (BOOL)isReady {
@@ -1,5 +1,10 @@
1
+ #import "ANNFrameDriver.h"
1
2
  #import <UIKit/UIKit.h>
2
3
 
4
+ @class ANNElementTransitionOptions;
5
+ @class ANNSharedElementTransitionOptions;
6
+ @class Text;
7
+
3
8
  NS_ASSUME_NONNULL_BEGIN
4
9
 
5
10
  /// Finds the React views of the elements of a transition by their `nativeID`: depth first, the first match, in the
@@ -21,6 +26,58 @@ NS_ASSUME_NONNULL_BEGIN
21
26
  + (nullable UIView *)viewWithNativeID:(nullable NSString *)nativeID
22
27
  inNode:(nullable UIViewController *)node;
23
28
 
29
+ /// The views with the `nativeID`s of `nativeIDs` in `root` and its subviews, in one walk: for each id the view
30
+ /// `viewWithNativeID:inView:` finds; an id no view has is left out. The walk stops once every id is found.
31
+ + (NSDictionary<NSString *, UIView *> *)viewsWithNativeIDs:(NSSet<NSString *> *)nativeIDs
32
+ inView:(nullable UIView *)root;
33
+
34
+ @end
35
+
36
+ /// The views of a transition's elements in its two screens: each screen is walked once, at its first lookup, for the
37
+ /// ids looked for there — the leaving one for the pairs' `fromId`s and the elements' ids, the arriving one for the
38
+ /// pairs' `toId`s and the elements' ids the leaving one doesn't have. A lookup finds what
39
+ /// `+[ANNElementFinder viewWithNativeID:inView:]` finds in that screen as it was at its walk. Main queue.
40
+ @interface ANNTransitionViews : NSObject
41
+
42
+ - (instancetype)initWithPairs:(nullable NSArray<ANNSharedElementTransitionOptions *> *)pairs
43
+ elements:(nullable NSArray<ANNElementTransitionOptions *> *)elements
44
+ fromNode:(nullable UIViewController *)fromNode
45
+ toNode:(nullable UIViewController *)toNode NS_DESIGNATED_INITIALIZER;
46
+ - (instancetype)init NS_UNAVAILABLE;
47
+ + (instancetype)new NS_UNAVAILABLE;
48
+
49
+ /// The React views of the screens the elements are looked for in.
50
+ @property(nonatomic, strong, readonly, nullable) UIView *leavingRoot;
51
+ @property(nonatomic, strong, readonly, nullable) UIView *arrivingRoot;
52
+
53
+ /// The view with `nativeID` in the leaving screen; nil when none has it or the id isn't one of the transition's.
54
+ - (nullable UIView *)leavingViewWithNativeID:(nullable NSString *)nativeID;
55
+
56
+ /// The view with `nativeID` in the arriving screen; nil when none has it or the id isn't one of the transition's.
57
+ - (nullable UIView *)arrivingViewWithNativeID:(nullable NSString *)nativeID;
58
+
59
+ @end
60
+
61
+ /// The `nativeID` an element's option holds; nil when it holds none (not a string, an empty one).
62
+ FOUNDATION_EXPORT NSString *_Nullable ANNNativeIDOf(Text *_Nullable value);
63
+
64
+ /// The isolation of every element of the core's transitions: `work`, a step of the element named `element` (its
65
+ /// `nativeID`, or a pair's two), runs; an exception in it skips that element only — one line in the log
66
+ /// (`ANNElementSkipped`), and NO — while the rest of the transition plays and its command completes.
67
+ FOUNDATION_EXPORT BOOL ANNElementStep(NSString *element, NS_NOESCAPE dispatch_block_t work);
68
+
69
+ /// The line in the log for the element named `element`, skipped for `reason`: it shows and hides with its screen.
70
+ FOUNDATION_EXPORT void ANNElementSkipped(NSString *element, NSString *reason);
71
+
72
+ /// An element's frames, each in `ANNElementStep`: after a frame that fails, the element's values go back
73
+ /// (`-restore`), and it gets no frame more.
74
+ @interface ANNIsolatedElement : NSObject <ANNFrameClient>
75
+
76
+ - (instancetype)initWithClient:(id<ANNFrameClient>)client
77
+ name:(NSString *)name NS_DESIGNATED_INITIALIZER;
78
+ - (instancetype)init NS_UNAVAILABLE;
79
+ + (instancetype)new NS_UNAVAILABLE;
80
+
24
81
  @end
25
82
 
26
83
  NS_ASSUME_NONNULL_END